Compare commits

...
11 Commits
Author SHA1 Message Date
Jakob BorgandGitHub 71de6fe290 lib/upnp: Exit quicker (#6339)
During NAT discovery we block for 10s (NatTimeoutS) before returning.
This is mostly noticeable when Ctrl-C:ing a Syncthing directly after
startup as we wait for those ten seconds before shutting down. This
makes it check the context a little bit more frequently.
2020-02-13 15:39:36 +01:00
Jakob BorgandGitHub 6a840a040b lib/db: Keep metadata better in sync (ref #6335) (#6337)
This adds metadata updates to the same write batch as the underlying
file change. The odds of a metadata update going missing is greatly
reduced.

Bonus change: actually commit the transaction in recalcMeta.
2020-02-13 15:23:08 +01:00
Simon FreiandGitHub c3637f2191 lib: Faster termination on exit (ref #6319) (#6329) 2020-02-13 14:43:00 +01:00
Simon FreiandGitHub ca90f4e6af lib/db: Use flags from arg not LocalFlags() updating meta (#6333) 2020-02-13 14:02:30 +01:00
Jakob BorgandGitHub 51fa36d61f lib/db: Recover sequence number and metadata on startup (fixes #6335) (#6336)
lib/db: Recover sequence number and metadata on startup (fixes #6335)

If we crashed after writing new file entries but before updating
metadata in the database the sequence number and metadata will be wrong.
This fixes that.
2020-02-13 13:05:26 +01:00
Jakob BorgandGitHub d95a087829 lib/db: Don't leak snapshot when closing (#6331)
We could potentially get a snapshot and then fail to get a releaser,
leaking the snapshot. This takes the releaser first and makes sure to
release it on snapshot error.
2020-02-12 12:00:17 +01:00
Jakob BorgandGitHub a728743c86 lib/db: Use Commit() instead of commit() (#6330)
The readWriteTransaction offered both commit() (the one to use) and
Commit() (via embedding) where the latter didn't close the read
transaction. This removes the lower cased variant in order to prevent
the mistake.

The only place where the mistake was made was the new gc runner, where
it would leave a read snapshot open forever.
2020-02-12 11:59:55 +01:00
Simon FreiandGitHub ce27780a4c lib/model: Return empty summary on paused folders (ref #6272) (#6326) 2020-02-12 11:59:12 +01:00
Jakob BorgandGitHub 0df39ddc72 lib/fs, lib/model: Rewrite RecvOnly tests (#6318)
During some other work I discovered these tests weren't great, so I've
rewritten them to be a little better. The real changes here are:

- Don't play games with not starting the folder and such, and don't
  construct a fake folder instance -- just use the one the model has. The
  folder starts and scans but the folder contents are empty at this point
  so that's fine.

- Use a fakefs instead of a temp dir.

- To support the above, implement a fakefs option `?content=true` to
  make the fakefs actually retain written content. Use sparingly,
  obviously, but it means the fakefs can usually be used instead of an
  on disk real directory.
2020-02-12 07:47:05 +01:00
Jakob Borg b84aa114be gui, man, authors: Update docs, translations, and contributors 2020-02-12 07:45:30 +01:00
Simon FreiandGitHub a596e5e2f0 lib/model: Consistent error return values for folder methods on model (#6325) 2020-02-12 07:35:24 +01:00
41 changed files with 457 additions and 212 deletions
+2 -1
View File
@@ -7,6 +7,7 @@
package main
import (
"context"
"crypto/tls"
"errors"
"flag"
@@ -95,7 +96,7 @@ func checkServer(deviceID protocol.DeviceID, server string) checkResult {
})
go func() {
addresses, err := disco.Lookup(deviceID)
addresses, err := disco.Lookup(context.Background(), deviceID)
res <- checkResult{addresses: addresses, error: err}
}()
+2 -1
View File
@@ -7,6 +7,7 @@
package api
import (
"context"
"time"
"github.com/syncthing/syncthing/lib/discover"
@@ -26,7 +27,7 @@ func (m *mockedCachingMux) Stop() {
// from events.Finder
func (m *mockedCachingMux) Lookup(deviceID protocol.DeviceID) (direct []string, err error) {
func (m *mockedCachingMux) Lookup(ctx context.Context, deviceID protocol.DeviceID) (direct []string, err error) {
return nil, nil
}
+7 -1
View File
@@ -360,6 +360,12 @@ func (s *service) connect(ctx context.Context) {
var seen []string
for _, deviceCfg := range cfg.Devices {
select {
case <-ctx.Done():
return
default:
}
deviceID := deviceCfg.DeviceID
if deviceID == s.myID {
continue
@@ -380,7 +386,7 @@ func (s *service) connect(ctx context.Context) {
for _, addr := range deviceCfg.Addresses {
if addr == "dynamic" {
if s.discoverer != nil {
if t, err := s.discoverer.Lookup(deviceID); err == nil {
if t, err := s.discoverer.Lookup(ctx, deviceID); err == nil {
addrs = append(addrs, t...)
}
}
+6 -1
View File
@@ -48,10 +48,15 @@ type ReadTransaction interface {
// purposes of saving memory when transactions are in-RAM. Note that
// transactions may be checkpointed *anyway* even if this is not called, due to
// resource constraints, but this gives you a chance to decide when.
//
// Functions can be passed to Checkpoint. These are run if and only if the
// checkpoint will result in a flush, and will run before the flush. The
// transaction can be accessed via a closure. If an error is returned from
// these functions the flush will be aborted and the error bubbled.
type WriteTransaction interface {
ReadTransaction
Writer
Checkpoint() error
Checkpoint(...func() error) error
Commit() error
}
+18 -11
View File
@@ -37,14 +37,15 @@ func (b *leveldbBackend) NewReadTransaction() (ReadTransaction, error) {
}
func (b *leveldbBackend) newSnapshot() (leveldbSnapshot, error) {
snap, err := b.ldb.GetSnapshot()
if err != nil {
return leveldbSnapshot{}, wrapLeveldbErr(err)
}
rel, err := newReleaser(b.closeWG)
if err != nil {
return leveldbSnapshot{}, err
}
snap, err := b.ldb.GetSnapshot()
if err != nil {
rel.Release()
return leveldbSnapshot{}, wrapLeveldbErr(err)
}
return leveldbSnapshot{
snap: snap,
rel: rel,
@@ -52,14 +53,15 @@ func (b *leveldbBackend) newSnapshot() (leveldbSnapshot, error) {
}
func (b *leveldbBackend) NewWriteTransaction() (WriteTransaction, error) {
snap, err := b.newSnapshot()
if err != nil {
return nil, err // already wrapped
}
rel, err := newReleaser(b.closeWG)
if err != nil {
return nil, err
}
snap, err := b.newSnapshot()
if err != nil {
rel.Release()
return nil, err // already wrapped
}
return &leveldbTransaction{
leveldbSnapshot: snap,
ldb: b.ldb,
@@ -148,8 +150,8 @@ func (t *leveldbTransaction) Put(key, val []byte) error {
return t.checkFlush(dbFlushBatchMax)
}
func (t *leveldbTransaction) Checkpoint() error {
return t.checkFlush(dbFlushBatchMin)
func (t *leveldbTransaction) Checkpoint(preFlush ...func() error) error {
return t.checkFlush(dbFlushBatchMin, preFlush...)
}
func (t *leveldbTransaction) Commit() error {
@@ -165,10 +167,15 @@ func (t *leveldbTransaction) Release() {
}
// checkFlush flushes and resets the batch if its size exceeds the given size.
func (t *leveldbTransaction) checkFlush(size int) error {
func (t *leveldbTransaction) checkFlush(size int, preFlush ...func() error) error {
if len(t.batch.Dump()) < size {
return nil
}
for _, hook := range preFlush {
if err := hook(); err != nil {
return err
}
}
return t.flush()
}
+2 -2
View File
@@ -76,7 +76,7 @@ func addToBlockMap(db *Lowlevel, folder []byte, fs []protocol.FileInfo) error {
}
}
}
return t.commit()
return t.Commit()
}
func discardFromBlockMap(db *Lowlevel, folder []byte, fs []protocol.FileInfo) error {
@@ -101,7 +101,7 @@ func discardFromBlockMap(db *Lowlevel, folder []byte, fs []protocol.FileInfo) er
}
}
}
return t.commit()
return t.Commit()
}
func TestBlockMapAddUpdateWipe(t *testing.T) {
+20 -8
View File
@@ -124,12 +124,18 @@ func (db *Lowlevel) updateRemoteFiles(folder, device []byte, fs []protocol.FileI
return err
}
if err := t.Checkpoint(); err != nil {
if err := t.Checkpoint(func() error {
return meta.toDB(t, folder)
}); err != nil {
return err
}
}
return t.commit()
if err := meta.toDB(t, folder); err != nil {
return err
}
return t.Commit()
}
// updateLocalFiles adds fileinfos to the db, and updates the global versionlist,
@@ -227,12 +233,18 @@ func (db *Lowlevel) updateLocalFiles(folder []byte, fs []protocol.FileInfo, meta
}
}
if err := t.Checkpoint(); err != nil {
if err := t.Checkpoint(func() error {
return meta.toDB(t, folder)
}); err != nil {
return err
}
}
return t.commit()
if err := meta.toDB(t, folder); err != nil {
return err
}
return t.Commit()
}
func (db *Lowlevel) dropFolder(folder []byte) error {
@@ -290,7 +302,7 @@ func (db *Lowlevel) dropFolder(folder []byte) error {
return err
}
return t.commit()
return t.Commit()
}
func (db *Lowlevel) dropDeviceFolder(device, folder []byte, meta *metadataTracker) error {
@@ -343,7 +355,7 @@ func (db *Lowlevel) dropDeviceFolder(device, folder []byte, meta *metadataTracke
return err
}
}
return t.commit()
return t.Commit()
}
func (db *Lowlevel) checkGlobals(folder []byte, meta *metadataTracker) error {
@@ -411,7 +423,7 @@ func (db *Lowlevel) checkGlobals(folder []byte, meta *metadataTracker) error {
}
l.Debugf("db check completed for %q", folder)
return t.commit()
return t.Commit()
}
func (db *Lowlevel) getIndexID(device, folder []byte) (protocol.IndexID, error) {
@@ -469,7 +481,7 @@ func (db *Lowlevel) dropPrefix(prefix []byte) error {
if err := t.deleteKeyPrefix(prefix); err != nil {
return err
}
return t.commit()
return t.Commit()
}
func (db *Lowlevel) gcRunner() {
+16 -12
View File
@@ -28,8 +28,8 @@ type metadataTracker struct {
}
type metaKey struct {
dev protocol.DeviceID
flags uint32
dev protocol.DeviceID
flag uint32
}
func newMetadataTracker() *metadataTracker {
@@ -62,8 +62,8 @@ func (m *metadataTracker) Marshal() ([]byte, error) {
// toDB saves the marshalled metadataTracker to the given db, under the key
// corresponding to the given folder
func (m *metadataTracker) toDB(db *Lowlevel, folder []byte) error {
key, err := db.keyer.GenerateFolderMetaKey(nil, folder)
func (m *metadataTracker) toDB(t readWriteTransaction, folder []byte) error {
key, err := t.keyer.GenerateFolderMetaKey(nil, folder)
if err != nil {
return err
}
@@ -79,7 +79,7 @@ func (m *metadataTracker) toDB(db *Lowlevel, folder []byte) error {
if err != nil {
return err
}
err = db.Put(key, bs)
err = t.Put(key, bs)
if err == nil {
m.dirty = false
}
@@ -103,14 +103,18 @@ func (m *metadataTracker) fromDB(db *Lowlevel, folder []byte) error {
// countsPtr returns a pointer to the corresponding Counts struct, if
// necessary allocating one in the process
func (m *metadataTracker) countsPtr(dev protocol.DeviceID, flags uint32) *Counts {
func (m *metadataTracker) countsPtr(dev protocol.DeviceID, flag uint32) *Counts {
// must be called with the mutex held
key := metaKey{dev, flags}
if bits.OnesCount32(flag) > 1 {
panic("incorrect usage: set at most one bit in flag")
}
key := metaKey{dev, flag}
idx, ok := m.indexes[key]
if !ok {
idx = len(m.counts.Counts)
m.counts.Counts = append(m.counts.Counts, Counts{DeviceID: dev[:], LocalFlags: flags})
m.counts.Counts = append(m.counts.Counts, Counts{DeviceID: dev[:], LocalFlags: flag})
m.indexes[key] = idx
}
return &m.counts.Counts[idx]
@@ -157,8 +161,8 @@ func (m *metadataTracker) updateSeqLocked(dev protocol.DeviceID, f FileIntf) {
}
}
func (m *metadataTracker) addFileLocked(dev protocol.DeviceID, flags uint32, f FileIntf) {
cp := m.countsPtr(dev, flags)
func (m *metadataTracker) addFileLocked(dev protocol.DeviceID, flag uint32, f FileIntf) {
cp := m.countsPtr(dev, flag)
switch {
case f.IsDeleted():
@@ -196,8 +200,8 @@ func (m *metadataTracker) removeFile(dev protocol.DeviceID, f FileIntf) {
}
}
func (m *metadataTracker) removeFileLocked(dev protocol.DeviceID, flags uint32, f FileIntf) {
cp := m.countsPtr(dev, f.FileLocalFlags())
func (m *metadataTracker) removeFileLocked(dev protocol.DeviceID, flag uint32, f FileIntf) {
cp := m.countsPtr(dev, flag)
switch {
case f.IsDeleted():
+7 -7
View File
@@ -204,7 +204,7 @@ func (db *schemaUpdater) updateSchema0to1(_ int) error {
return err
}
}
return t.commit()
return t.Commit()
}
// updateSchema1to2 introduces a sequenceKey->deviceKey bucket for local items
@@ -240,7 +240,7 @@ func (db *schemaUpdater) updateSchema1to2(_ int) error {
return err
}
}
return t.commit()
return t.Commit()
}
// updateSchema2to3 introduces a needKey->nil bucket for locally needed files.
@@ -288,7 +288,7 @@ func (db *schemaUpdater) updateSchema2to3(_ int) error {
return err
}
}
return t.commit()
return t.Commit()
}
// updateSchemaTo5 resets the need bucket due to bugs existing in the v0.14.49
@@ -314,7 +314,7 @@ func (db *schemaUpdater) updateSchemaTo5(prevVersion int) error {
return err
}
}
if err := t.commit(); err != nil {
if err := t.Commit(); err != nil {
return err
}
@@ -361,7 +361,7 @@ func (db *schemaUpdater) updateSchema5to6(_ int) error {
return err
}
}
return t.commit()
return t.Commit()
}
// updateSchema6to7 checks whether all currently locally needed files are really
@@ -418,7 +418,7 @@ func (db *schemaUpdater) updateSchema6to7(_ int) error {
return err
}
}
return t.commit()
return t.Commit()
}
func (db *schemaUpdater) updateSchema7to8(_ int) error {
@@ -453,5 +453,5 @@ func (db *schemaUpdater) updateSchema7to8(_ int) error {
db.recordTime(blockGCTimeKey)
return t.commit()
return t.Commit()
}
+67 -21
View File
@@ -71,7 +71,7 @@ func init() {
}
func NewFileSet(folder string, fs fs.Filesystem, db *Lowlevel) *FileSet {
var s = FileSet{
var s = &FileSet{
folder: folder,
fs: fs,
db: db,
@@ -79,26 +79,34 @@ func NewFileSet(folder string, fs fs.Filesystem, db *Lowlevel) *FileSet {
updateMutex: sync.NewMutex(),
}
if err := s.meta.fromDB(db, []byte(folder)); err != nil {
l.Infof("No stored folder metadata for %q: recalculating", folder)
if err := s.recalcCounts(); backend.IsClosed(err) {
return nil
} else if err != nil {
panic(err)
}
} else if age := time.Since(s.meta.Created()); age > databaseRecheckInterval {
l.Infof("Stored folder metadata for %q is %v old; recalculating", folder, age)
if err := s.recalcCounts(); backend.IsClosed(err) {
recalc := func() *FileSet {
if err := s.recalcMeta(); backend.IsClosed(err) {
return nil
} else if err != nil {
panic(err)
}
return s
}
return &s
if err := s.meta.fromDB(db, []byte(folder)); err != nil {
l.Infof("No stored folder metadata for %q; recalculating", folder)
return recalc()
}
if metaOK := s.verifyLocalSequence(); !metaOK {
l.Infof("Stored folder metadata for %q is out of date after crash; recalculating", folder)
return recalc()
}
if age := time.Since(s.meta.Created()); age > databaseRecheckInterval {
l.Infof("Stored folder metadata for %q is %v old; recalculating", folder, age)
return recalc()
}
return s
}
func (s *FileSet) recalcCounts() error {
func (s *FileSet) recalcMeta() error {
s.meta = newMetadataTracker()
if err := s.db.checkGlobals([]byte(s.folder), s.meta); err != nil {
@@ -109,6 +117,8 @@ func (s *FileSet) recalcCounts() error {
if err != nil {
return err
}
defer t.close()
var deviceID protocol.DeviceID
err = t.withAllFolderTruncated([]byte(s.folder), func(device []byte, f FileInfoTruncated) bool {
copy(deviceID[:], device)
@@ -120,7 +130,36 @@ func (s *FileSet) recalcCounts() error {
}
s.meta.SetCreated()
return s.meta.toDB(s.db, []byte(s.folder))
if err := s.meta.toDB(t, []byte(s.folder)); err != nil {
return err
}
return t.Commit()
}
// Verify the local sequence number from actual sequence entries. Returns
// true if it was all good, or false if a fixup was necessary.
func (s *FileSet) verifyLocalSequence() bool {
// Walk the sequence index from the current (supposedly) highest
// sequence number and raise the alarm if we get anything. This recovers
// from the occasion where we have written sequence entries to disk but
// not yet written new metadata to disk.
//
// Note that we can have the same thing happen for remote devices but
// there it's not a problem -- we'll simply advertise a lower sequence
// number than we've actually seen and receive some duplicate updates
// and then be in sync again.
curSeq := s.meta.Sequence(protocol.LocalDeviceID)
snap := s.Snapshot()
ok := true
snap.WithHaveSequence(curSeq+1, func(fi FileIntf) bool {
ok = false // we got something, which we should not have
return false
})
snap.Release()
return ok
}
func (s *FileSet) Drop(device protocol.DeviceID) {
@@ -150,7 +189,20 @@ func (s *FileSet) Drop(device protocol.DeviceID) {
s.meta.resetAll(device)
}
if err := s.meta.toDB(s.db, []byte(s.folder)); backend.IsClosed(err) {
t, err := s.db.newReadWriteTransaction()
if backend.IsClosed(err) {
return
} else if err != nil {
panic(err)
}
defer t.close()
if err := s.meta.toDB(t, []byte(s.folder)); backend.IsClosed(err) {
return
} else if err != nil {
panic(err)
}
if err := t.Commit(); backend.IsClosed(err) {
return
} else if err != nil {
panic(err)
@@ -168,12 +220,6 @@ func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
defer func() {
if err := s.meta.toDB(s.db, []byte(s.folder)); err != nil && !backend.IsClosed(err) {
panic(err)
}
}()
if device == protocol.LocalDeviceID {
// For the local device we have a bunch of metadata to track.
if err := s.db.updateLocalFiles([]byte(s.folder), fs, s.meta); err != nil && !backend.IsClosed(err) {
+2 -2
View File
@@ -443,7 +443,7 @@ func (db *Lowlevel) newReadWriteTransaction() (readWriteTransaction, error) {
}, nil
}
func (t readWriteTransaction) commit() error {
func (t readWriteTransaction) Commit() error {
t.readOnlyTransaction.close()
return t.WriteTransaction.Commit()
}
@@ -758,7 +758,7 @@ func (t *readWriteTransaction) withAllFolderTruncated(folder []byte, fn func(dev
if err := dbi.Error(); err != nil {
return err
}
return t.commit()
return t.Commit()
}
type marshaller interface {
+3 -2
View File
@@ -7,6 +7,7 @@
package discover
import (
"context"
"sort"
stdsync "sync"
"time"
@@ -73,7 +74,7 @@ func (m *cachingMux) Add(finder Finder, cacheTime, negCacheTime time.Duration) {
// Lookup attempts to resolve the device ID using any of the added Finders,
// while obeying the cache settings.
func (m *cachingMux) Lookup(deviceID protocol.DeviceID) (addresses []string, err error) {
func (m *cachingMux) Lookup(ctx context.Context, deviceID protocol.DeviceID) (addresses []string, err error) {
m.mut.RLock()
for i, finder := range m.finders {
if cacheEntry, ok := m.caches[i].Get(deviceID); ok {
@@ -99,7 +100,7 @@ func (m *cachingMux) Lookup(deviceID protocol.DeviceID) (addresses []string, err
}
// Perform the actual lookup and cache the result.
if addrs, err := finder.Lookup(deviceID); err == nil {
if addrs, err := finder.Lookup(ctx, deviceID); err == nil {
l.Debugln("lookup for", deviceID, "at", finder)
l.Debugln(" addresses:", addrs)
addresses = append(addresses, addrs...)
+8 -5
View File
@@ -7,6 +7,7 @@
package discover
import (
"context"
"reflect"
"testing"
"time"
@@ -39,7 +40,9 @@ func TestCacheUnique(t *testing.T) {
f1 := &fakeDiscovery{addresses0}
c.Add(f1, time.Minute, 0)
addr, err := c.Lookup(protocol.LocalDeviceID)
ctx := context.Background()
addr, err := c.Lookup(ctx, protocol.LocalDeviceID)
if err != nil {
t.Fatal(err)
}
@@ -53,7 +56,7 @@ func TestCacheUnique(t *testing.T) {
f2 := &fakeDiscovery{addresses1}
c.Add(f2, time.Minute, 0)
addr, err = c.Lookup(protocol.LocalDeviceID)
addr, err = c.Lookup(ctx, protocol.LocalDeviceID)
if err != nil {
t.Fatal(err)
}
@@ -66,7 +69,7 @@ type fakeDiscovery struct {
addresses []string
}
func (f *fakeDiscovery) Lookup(deviceID protocol.DeviceID) (addresses []string, err error) {
func (f *fakeDiscovery) Lookup(_ context.Context, deviceID protocol.DeviceID) (addresses []string, err error) {
return f.addresses, nil
}
@@ -96,7 +99,7 @@ func TestCacheSlowLookup(t *testing.T) {
// Start a lookup, which will take at least a second
t0 := time.Now()
go c.Lookup(protocol.LocalDeviceID)
go c.Lookup(context.Background(), protocol.LocalDeviceID)
<-started // The slow lookup method has been called so we're inside the lock
// It should be possible to get ChildErrors while it's running
@@ -116,7 +119,7 @@ type slowDiscovery struct {
started chan struct{}
}
func (f *slowDiscovery) Lookup(deviceID protocol.DeviceID) (addresses []string, err error) {
func (f *slowDiscovery) Lookup(_ context.Context, deviceID protocol.DeviceID) (addresses []string, err error) {
close(f.started)
time.Sleep(f.delay)
return nil, nil
+2 -1
View File
@@ -7,6 +7,7 @@
package discover
import (
"context"
"time"
"github.com/syncthing/syncthing/lib/protocol"
@@ -15,7 +16,7 @@ import (
// A Finder provides lookup services of some kind.
type Finder interface {
Lookup(deviceID protocol.DeviceID) (address []string, err error)
Lookup(ctx context.Context, deviceID protocol.DeviceID) (address []string, err error)
Error() error
String() string
Cache() map[protocol.DeviceID]CacheEntry
+44 -15
View File
@@ -41,8 +41,8 @@ type globalClient struct {
}
type httpClient interface {
Get(url string) (*http.Response, error)
Post(url, ctype string, data io.Reader) (*http.Response, error)
Get(ctx context.Context, url string) (*http.Response, error)
Post(ctx context.Context, url, ctype string, data io.Reader) (*http.Response, error)
}
const (
@@ -89,7 +89,7 @@ func NewGlobal(server string, cert tls.Certificate, addrList AddressLister, evLo
// The http.Client used for announcements. It needs to have our
// certificate to prove our identity, and may or may not verify the server
// certificate depending on the insecure setting.
var announceClient httpClient = &http.Client{
var announceClient httpClient = &contextClient{&http.Client{
Timeout: requestTimeout,
Transport: &http.Transport{
DialContext: dialer.DialContext,
@@ -99,14 +99,14 @@ func NewGlobal(server string, cert tls.Certificate, addrList AddressLister, evLo
Certificates: []tls.Certificate{cert},
},
},
}
}}
if opts.id != "" {
announceClient = newIDCheckingHTTPClient(announceClient, devID)
}
// The http.Client used for queries. We don't need to present our
// certificate here, so lets not include it. May be insecure if requested.
var queryClient httpClient = &http.Client{
var queryClient httpClient = &contextClient{&http.Client{
Timeout: requestTimeout,
Transport: &http.Transport{
DialContext: dialer.DialContext,
@@ -115,7 +115,7 @@ func NewGlobal(server string, cert tls.Certificate, addrList AddressLister, evLo
InsecureSkipVerify: opts.insecure,
},
},
}
}}
if opts.id != "" {
queryClient = newIDCheckingHTTPClient(queryClient, devID)
}
@@ -139,7 +139,7 @@ func NewGlobal(server string, cert tls.Certificate, addrList AddressLister, evLo
}
// Lookup returns the list of addresses where the given device is available
func (c *globalClient) Lookup(device protocol.DeviceID) (addresses []string, err error) {
func (c *globalClient) Lookup(ctx context.Context, device protocol.DeviceID) (addresses []string, err error) {
if c.noLookup {
return nil, lookupError{
error: errors.New("lookups not supported"),
@@ -156,7 +156,7 @@ func (c *globalClient) Lookup(device protocol.DeviceID) (addresses []string, err
q.Set("device", device.String())
qURL.RawQuery = q.Encode()
resp, err := c.queryClient.Get(qURL.String())
resp, err := c.queryClient.Get(ctx, qURL.String())
if err != nil {
l.Debugln("globalClient.Lookup", qURL, err)
return nil, err
@@ -211,7 +211,7 @@ func (c *globalClient) serve(ctx context.Context) {
timer.Reset(2 * time.Second)
case <-timer.C:
c.sendAnnouncement(timer)
c.sendAnnouncement(ctx, timer)
case <-ctx.Done():
return
@@ -219,7 +219,7 @@ func (c *globalClient) serve(ctx context.Context) {
}
}
func (c *globalClient) sendAnnouncement(timer *time.Timer) {
func (c *globalClient) sendAnnouncement(ctx context.Context, timer *time.Timer) {
var ann announcement
if c.addrList != nil {
ann.Addresses = c.addrList.ExternalAddresses()
@@ -239,7 +239,7 @@ func (c *globalClient) sendAnnouncement(timer *time.Timer) {
l.Debugf("Announcement: %s", postData)
resp, err := c.announceClient.Post(c.server, "application/json", bytes.NewReader(postData))
resp, err := c.announceClient.Post(ctx, c.server, "application/json", bytes.NewReader(postData))
if err != nil {
l.Debugln("announce POST:", err)
c.setError(err)
@@ -362,8 +362,8 @@ func (c *idCheckingHTTPClient) check(resp *http.Response) error {
return nil
}
func (c *idCheckingHTTPClient) Get(url string) (*http.Response, error) {
resp, err := c.httpClient.Get(url)
func (c *idCheckingHTTPClient) Get(ctx context.Context, url string) (*http.Response, error) {
resp, err := c.httpClient.Get(ctx, url)
if err != nil {
return nil, err
}
@@ -374,8 +374,8 @@ func (c *idCheckingHTTPClient) Get(url string) (*http.Response, error) {
return resp, nil
}
func (c *idCheckingHTTPClient) Post(url, ctype string, data io.Reader) (*http.Response, error) {
resp, err := c.httpClient.Post(url, ctype, data)
func (c *idCheckingHTTPClient) Post(ctx context.Context, url, ctype string, data io.Reader) (*http.Response, error) {
resp, err := c.httpClient.Post(ctx, url, ctype, data)
if err != nil {
return nil, err
}
@@ -403,3 +403,32 @@ func (e *errorHolder) Error() error {
e.mut.Unlock()
return err
}
type contextClient struct {
*http.Client
}
func (c *contextClient) Get(ctx context.Context, url string) (*http.Response, error) {
// For <go1.13 compatibility. Use the following commented line once that
// isn't required anymore.
// req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Cancel = ctx.Done()
return c.Client.Do(req)
}
func (c *contextClient) Post(ctx context.Context, url, ctype string, data io.Reader) (*http.Response, error) {
// For <go1.13 compatibility. Use the following commented line once that
// isn't required anymore.
// req, err := http.NewRequestWithContext(ctx, "POST", url, data)
req, err := http.NewRequest("POST", url, data)
if err != nil {
return nil, err
}
req.Cancel = ctx.Done()
req.Header.Set("Content-Type", ctype)
return c.Client.Do(req)
}
+2 -1
View File
@@ -7,6 +7,7 @@
package discover
import (
"context"
"crypto/tls"
"io/ioutil"
"net"
@@ -225,7 +226,7 @@ func testLookup(url string) ([]string, error) {
go disco.Serve()
defer disco.Stop()
return disco.Lookup(protocol.LocalDeviceID)
return disco.Lookup(context.Background(), protocol.LocalDeviceID)
}
type fakeDiscoveryServer struct {
+1 -1
View File
@@ -91,7 +91,7 @@ func NewLocal(id protocol.DeviceID, addr string, addrList AddressLister, evLogge
}
// Lookup returns a list of addresses the device is available at.
func (c *localClient) Lookup(device protocol.DeviceID) (addresses []string, err error) {
func (c *localClient) Lookup(_ context.Context, device protocol.DeviceID) (addresses []string, err error) {
if cache, ok := c.Get(device); ok {
if time.Since(cache.when) < CacheLifeTime {
addresses = cache.Addresses
+37 -6
View File
@@ -52,9 +52,10 @@ const randomBlockShift = 14 // 128k
// - Two fakefs:s pointing at the same root path see the same files.
//
type fakefs struct {
mut sync.Mutex
root *fakeEntry
insens bool
mut sync.Mutex
root *fakeEntry
insens bool
withContent bool
}
var (
@@ -93,9 +94,9 @@ func newFakeFilesystem(root string) *fakefs {
sizeavg, _ := strconv.Atoi(params.Get("sizeavg"))
seed, _ := strconv.Atoi(params.Get("seed"))
if params.Get("insens") == "true" {
fs.insens = true
}
fs.insens = params.Get("insens") == "true"
fs.withContent = params.Get("content") == "true"
if sizeavg == 0 {
sizeavg = 1 << 20
}
@@ -151,6 +152,7 @@ type fakeEntry struct {
gid int
mtime time.Time
children map[string]*fakeEntry
content []byte
}
func (fs *fakefs) entryForName(name string) *fakeEntry {
@@ -227,6 +229,10 @@ func (fs *fakefs) create(name string) (*fakeEntry, error) {
entry.size = 0
entry.mtime = time.Now()
entry.mode = 0666
entry.content = nil
if fs.withContent {
entry.content = make([]byte, 0)
}
return entry, nil
}
@@ -246,6 +252,10 @@ func (fs *fakefs) create(name string) (*fakeEntry, error) {
base = UnicodeLowercase(base)
}
if fs.withContent {
new.content = make([]byte, 0)
}
entry.children[base] = new
return new, nil
}
@@ -417,6 +427,9 @@ func (fs *fakefs) OpenFile(name string, flags int, mode FileMode) (File, error)
mode: mode,
mtime: time.Now(),
}
if fs.withContent {
newEntry.content = make([]byte, 0)
}
entry.children[key] = newEntry
return &fakeFile{fakeEntry: newEntry}, nil
@@ -660,6 +673,12 @@ func (f *fakeFile) readShortAt(p []byte, offs int64) (int, error) {
return 0, io.EOF
}
if f.content != nil {
n := copy(p, f.content[int(offs):])
f.offset = offs + int64(n)
return n, nil
}
// Lazily calculate our main seed, a simple 64 bit FNV hash our file
// name.
if f.seed == 0 {
@@ -746,6 +765,15 @@ func (f *fakeFile) WriteAt(p []byte, off int64) (int, error) {
return 0, errors.New("is a directory")
}
if f.content != nil {
if len(f.content) < int(off)+len(p) {
newc := make([]byte, int(off)+len(p))
copy(newc, f.content)
f.content = newc
}
copy(f.content[int(off):], p)
}
f.rng = nil
f.offset = off + int64(len(p))
if f.offset > f.size {
@@ -765,6 +793,9 @@ func (f *fakeFile) Truncate(size int64) error {
f.mut.Lock()
defer f.mut.Unlock()
if f.content != nil {
f.content = f.content[:int(size)]
}
f.rng = nil
f.size = size
if f.offset > size {
+29
View File
@@ -896,6 +896,35 @@ func testFakeFSCreateInsens(t *testing.T, fs Filesystem) {
assertDir(t, fs, "/", []string{"FOO"})
}
func TestReadWriteContent(t *testing.T) {
fs := newFakeFilesystem("foo?content=true")
fd, err := fs.Create("file")
if err != nil {
t.Fatal(err)
}
if _, err := fd.Write([]byte("foo")); err != nil {
t.Fatal(err)
}
if _, err := fd.WriteAt([]byte("bar"), 5); err != nil {
t.Fatal(err)
}
expected := []byte("foo\x00\x00bar")
buf := make([]byte, len(expected)-1)
n, err := fd.ReadAt(buf, 1) // note offset one byte
if err != nil {
t.Fatal(err)
}
if n != len(expected)-1 {
t.Fatal("wrong number of bytes read")
}
if !bytes.Equal(buf[:n], expected[1:]) {
fmt.Printf("%d %q\n", n, buf[:n])
t.Error("wrong data in file")
}
}
func cleanup(fs Filesystem) error {
filenames, _ := fs.DirNames("/")
for _, filename := range filenames {
+54 -55
View File
@@ -9,8 +9,6 @@ package model
import (
"bytes"
"context"
"io/ioutil"
"path/filepath"
"testing"
"time"
@@ -28,18 +26,18 @@ func TestRecvOnlyRevertDeletes(t *testing.T) {
// Get us a model up and running
m, f := setupROFolder()
m, f := setupROFolder(t)
ffs := f.Filesystem()
defer cleanupModelAndRemoveDir(m, ffs.URI())
defer cleanupModel(m)
// Create some test data
for _, dir := range []string{".stfolder", "ignDir", "unknownDir"} {
must(t, ffs.MkdirAll(dir, 0755))
}
must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), "ignDir/ignFile"), []byte("hello\n"), 0644))
must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), "unknownDir/unknownFile"), []byte("hello\n"), 0644))
must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), ".stignore"), []byte("ignDir\n"), 0644))
must(t, writeFile(ffs, "ignDir/ignFile", []byte("hello\n"), 0644))
must(t, writeFile(ffs, "unknownDir/unknownFile", []byte("hello\n"), 0644))
must(t, writeFile(ffs, ".stignore", []byte("ignDir\n"), 0644))
knownFiles := setupKnownFiles(t, ffs, []byte("hello\n"))
@@ -48,15 +46,18 @@ func TestRecvOnlyRevertDeletes(t *testing.T) {
m.Index(device1, "ro", knownFiles)
f.updateLocalsFromScanning(knownFiles)
size := globalSize(t, m, "ro")
m.fmut.RLock()
snap := m.folderFiles["ro"].Snapshot()
m.fmut.RUnlock()
size := snap.GlobalSize()
snap.Release()
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
}
// Start the folder. This will cause a scan, should discover the other stuff in the folder
// Scan, should discover the other stuff in the folder
m.startFolder("ro")
m.ScanFolder("ro")
must(t, m.ScanFolder("ro"))
// We should now have two files and two directories.
@@ -109,9 +110,9 @@ func TestRecvOnlyRevertNeeds(t *testing.T) {
// Get us a model up and running
m, f := setupROFolder()
m, f := setupROFolder(t)
ffs := f.Filesystem()
defer cleanupModelAndRemoveDir(m, ffs.URI())
defer cleanupModel(m)
// Create some test data
@@ -124,10 +125,9 @@ func TestRecvOnlyRevertNeeds(t *testing.T) {
m.Index(device1, "ro", knownFiles)
f.updateLocalsFromScanning(knownFiles)
// Start the folder. This will cause a scan.
// Scan the folder.
m.startFolder("ro")
m.ScanFolder("ro")
must(t, m.ScanFolder("ro"))
// Everything should be in sync.
@@ -151,7 +151,7 @@ func TestRecvOnlyRevertNeeds(t *testing.T) {
// Update the file.
newData := []byte("totally different data\n")
must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), "knownDir/knownFile"), newData, 0644))
must(t, writeFile(ffs, "knownDir/knownFile", newData, 0644))
// Rescan.
@@ -196,13 +196,11 @@ func TestRecvOnlyRevertNeeds(t *testing.T) {
}
func TestRecvOnlyUndoChanges(t *testing.T) {
testOs := &fatalOs{t}
// Get us a model up and running
m, f := setupROFolder()
m, f := setupROFolder(t)
ffs := f.Filesystem()
defer cleanupModelAndRemoveDir(m, ffs.URI())
defer cleanupModel(m)
// Create some test data
@@ -210,20 +208,14 @@ func TestRecvOnlyUndoChanges(t *testing.T) {
oldData := []byte("hello\n")
knownFiles := setupKnownFiles(t, ffs, oldData)
m.fmut.Lock()
fset := m.folderFiles["ro"]
m.fmut.Unlock()
folderFs := fset.MtimeFS()
// Send and index update for the known stuff
// Send an index update for the known stuff
m.Index(device1, "ro", knownFiles)
f.updateLocalsFromScanning(knownFiles)
// Start the folder. This will cause a scan.
// Scan the folder.
m.startFolder("ro")
m.ScanFolder("ro")
must(t, m.ScanFolder("ro"))
// Everything should be in sync.
@@ -246,12 +238,11 @@ func TestRecvOnlyUndoChanges(t *testing.T) {
// Create a file and modify another
file := filepath.Join(ffs.URI(), "foo")
must(t, ioutil.WriteFile(file, []byte("hello\n"), 0644))
const file = "foo"
must(t, writeFile(ffs, file, []byte("hello\n"), 0644))
must(t, writeFile(ffs, "knownDir/knownFile", []byte("bye\n"), 0644))
must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), "knownDir/knownFile"), []byte("bye\n"), 0644))
m.ScanFolder("ro")
must(t, m.ScanFolder("ro"))
size = receiveOnlyChangedSize(t, m, "ro")
if size.Files != 2 {
@@ -260,11 +251,11 @@ func TestRecvOnlyUndoChanges(t *testing.T) {
// Remove the file again and undo the modification
testOs.Remove(file)
must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), "knownDir/knownFile"), oldData, 0644))
folderFs.Chtimes("knownDir/knownFile", knownFiles[1].ModTime(), knownFiles[1].ModTime())
must(t, ffs.Remove(file))
must(t, writeFile(ffs, "knownDir/knownFile", oldData, 0644))
must(t, ffs.Chtimes("knownDir/knownFile", knownFiles[1].ModTime(), knownFiles[1].ModTime()))
m.ScanFolder("ro")
must(t, m.ScanFolder("ro"))
size = receiveOnlyChangedSize(t, m, "ro")
if size.Files+size.Directories+size.Deleted != 0 {
@@ -276,7 +267,7 @@ func setupKnownFiles(t *testing.T, ffs fs.Filesystem, data []byte) []protocol.Fi
t.Helper()
must(t, ffs.MkdirAll("knownDir", 0755))
must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), "knownDir/knownFile"), data, 0644))
must(t, writeFile(ffs, "knownDir/knownFile", data, 0644))
t0 := time.Now().Add(-1 * time.Minute)
must(t, ffs.Chtimes("knownDir/knownFile", t0, t0))
@@ -310,30 +301,38 @@ func setupKnownFiles(t *testing.T, ffs fs.Filesystem, data []byte) []protocol.Fi
return knownFiles
}
func setupROFolder() (*model, *sendOnlyFolder) {
func setupROFolder(t *testing.T) (*model, *receiveOnlyFolder) {
t.Helper()
w := createTmpWrapper(defaultCfg)
fcfg := testFolderConfigTmp()
fcfg := testFolderConfigFake()
fcfg.ID = "ro"
fcfg.Label = "ro"
fcfg.Type = config.FolderTypeReceiveOnly
w.SetFolder(fcfg)
m := newModel(w, myID, "syncthing", "dev", db.NewLowlevel(backend.OpenMemory()), nil)
m.ServeBackground()
// Folder should only be added, not started.
m.removeFolder(fcfg)
m.addFolder(fcfg)
must(t, m.ScanFolder("ro"))
m.fmut.RLock()
f := &sendOnlyFolder{
folder: folder{
stateTracker: newStateTracker(fcfg.ID, m.evLogger),
fset: m.folderFiles[fcfg.ID],
FolderConfiguration: fcfg,
},
}
m.fmut.RUnlock()
defer m.fmut.RUnlock()
f := m.folderRunners["ro"].(*receiveOnlyFolder)
return m, f
}
func writeFile(fs fs.Filesystem, filename string, data []byte, perm fs.FileMode) error {
fd, err := fs.Create(filename)
if err != nil {
return err
}
_, err = fd.Write(data)
if err != nil {
return err
}
if err := fd.Close(); err != nil {
return err
}
return fs.Chmod(filename, perm)
}
+20 -14
View File
@@ -15,6 +15,7 @@ import (
"github.com/thejerf/suture"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
@@ -77,28 +78,37 @@ func (c *folderSummaryService) String() string {
func (c *folderSummaryService) Summary(folder string) (map[string]interface{}, error) {
var res = make(map[string]interface{})
snap, err := c.model.DBSnapshot(folder)
if err != nil {
var local, global, need, ro db.Counts
var ourSeq, remoteSeq int64
errors, err := c.model.FolderErrors(folder)
if err == nil {
var snap *db.Snapshot
if snap, err = c.model.DBSnapshot(folder); err == nil {
global = snap.GlobalSize()
local = snap.LocalSize()
need = snap.NeedSize()
ro = snap.ReceiveOnlyChangedSize()
ourSeq = snap.Sequence(protocol.LocalDeviceID)
remoteSeq = snap.Sequence(protocol.GlobalDeviceID)
snap.Release()
}
}
// For API backwards compatibility (SyncTrayzor needs it) an empty folder
// summary is returned for not running folders, an error might actually be
// more appropriate
if err != nil && err != ErrFolderPaused && err != errFolderNotRunning {
return nil, err
}
errors, err := c.model.FolderErrors(folder)
if err != nil && err != ErrFolderPaused && err != errFolderNotRunning {
// Stats from the db can still be obtained if the folder is just paused/being started
return nil, err
}
res["errors"] = len(errors)
res["pullErrors"] = len(errors) // deprecated
res["invalid"] = "" // Deprecated, retains external API for now
global := snap.GlobalSize()
res["globalFiles"], res["globalDirectories"], res["globalSymlinks"], res["globalDeleted"], res["globalBytes"], res["globalTotalItems"] = global.Files, global.Directories, global.Symlinks, global.Deleted, global.Bytes, global.TotalItems()
local := snap.LocalSize()
res["localFiles"], res["localDirectories"], res["localSymlinks"], res["localDeleted"], res["localBytes"], res["localTotalItems"] = local.Files, local.Directories, local.Symlinks, local.Deleted, local.Bytes, local.TotalItems()
need := snap.NeedSize()
need.Bytes -= c.model.FolderProgressBytesCompleted(folder)
// This may happen if we are in progress of pulling files that were
// deleted globally after the pull started.
@@ -116,7 +126,6 @@ func (c *folderSummaryService) Summary(folder string) (map[string]interface{}, e
if ok && fcfg.Type == config.FolderTypeReceiveOnly {
// Add statistics for things that have changed locally in a receive
// only folder.
ro := snap.ReceiveOnlyChangedSize()
res["receiveOnlyChangedFiles"] = ro.Files
res["receiveOnlyChangedDirectories"] = ro.Directories
res["receiveOnlyChangedSymlinks"] = ro.Symlinks
@@ -132,9 +141,6 @@ func (c *folderSummaryService) Summary(folder string) (map[string]interface{}, e
res["error"] = err.Error()
}
ourSeq := snap.Sequence(protocol.LocalDeviceID)
remoteSeq := snap.Sequence(protocol.GlobalDeviceID)
res["version"] = ourSeq + remoteSeq // legacy
res["sequence"] = ourSeq + remoteSeq // new name
+12 -13
View File
@@ -840,10 +840,11 @@ func (m *model) Completion(device protocol.DeviceID, folder string) FolderComple
// DBSnapshot returns a snapshot of the database content relevant to the given folder.
func (m *model) DBSnapshot(folder string) (*db.Snapshot, error) {
m.fmut.RLock()
rf, ok := m.folderFiles[folder]
err := m.checkFolderRunningLocked(folder)
rf := m.folderFiles[folder]
m.fmut.RUnlock()
if !ok {
return nil, errFolderMissing
if err != nil {
return nil, err
}
return rf.Snapshot(), nil
}
@@ -2319,10 +2320,11 @@ func (m *model) GlobalDirectoryTree(folder, prefix string, levels int, dirsonly
func (m *model) GetFolderVersions(folder string) (map[string][]versioner.FileVersion, error) {
m.fmut.RLock()
ver, ok := m.folderVersioners[folder]
err := m.checkFolderRunningLocked(folder)
ver := m.folderVersioners[folder]
m.fmut.RUnlock()
if !ok {
return nil, errFolderMissing
if err != nil {
return nil, err
}
if ver == nil {
return nil, errNoVersioner
@@ -2332,16 +2334,13 @@ func (m *model) GetFolderVersions(folder string) (map[string][]versioner.FileVer
}
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
}
m.fmut.RLock()
err := m.checkFolderRunningLocked(folder)
fcfg := m.folderCfgs[folder]
ver := m.folderVersioners[folder]
m.fmut.RUnlock()
if !ok {
return nil, errFolderMissing
if err != nil {
return nil, err
}
if ver == nil {
return nil, errNoVersioner
+45
View File
@@ -3479,3 +3479,48 @@ func TestNewLimitedRequestResponse(t *testing.T) {
t.Error("Bytes weren't returned in a timely fashion")
}
}
func TestSummaryPausedNoError(t *testing.T) {
wcfg, fcfg := tmpDefaultWrapper()
fcfg.Paused = true
wcfg.SetFolder(fcfg)
m := setupModel(wcfg)
defer cleanupModel(m)
fss := NewFolderSummaryService(wcfg, m, myID, events.NoopLogger)
if _, err := fss.Summary(fcfg.ID); err != nil {
t.Error("Expected no error getting a summary for a paused folder:", err)
}
}
func TestFolderAPIErrors(t *testing.T) {
wcfg, fcfg := tmpDefaultWrapper()
fcfg.Paused = true
wcfg.SetFolder(fcfg)
m := setupModel(wcfg)
defer cleanupModel(m)
methods := []func(folder string) error{
m.ScanFolder,
func(folder string) error {
return m.ScanFolderSubdirs(folder, nil)
},
func(folder string) error {
_, err := m.GetFolderVersions(folder)
return err
},
func(folder string) error {
_, err := m.RestoreFolderVersions(folder, nil)
return err
},
}
for i, method := range methods {
if err := method(fcfg.ID); err != ErrFolderPaused {
t.Errorf(`Expected "%v", got "%v" (method no %v)`, ErrFolderPaused, err, i)
}
if err := method("notexisting"); err != errFolderMissing {
t.Errorf(`Expected "%v", got "%v" (method no %v)`, errFolderMissing, err, i)
}
}
}
+8
View File
@@ -18,6 +18,7 @@ import (
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/rand"
)
var (
@@ -86,6 +87,13 @@ func testFolderConfig(path string) config.FolderConfiguration {
return cfg
}
func testFolderConfigFake() config.FolderConfiguration {
cfg := config.NewFolderConfiguration(myID, "default", "default", fs.FilesystemTypeFake, rand.String(32)+"?content=true")
cfg.FSWatcherEnabled = false
cfg.Devices = append(cfg.Devices, config.FolderDeviceConfiguration{DeviceID: device1})
return cfg
}
func setupModelWithConnection() (*model, *fakeConnection, config.FolderConfiguration) {
w, fcfg := tmpDefaultWrapper()
m, fc := setupModelWithConnectionFromWrapper(w)
+24 -13
View File
@@ -160,12 +160,6 @@ USER-AGENT: syncthing/1.0
}
defer socket.Close() // Make sure our socket gets closed
err = socket.SetDeadline(time.Now().Add(timeout))
if err != nil {
l.Debugln("UPnP discovery: setting socket deadline:", err)
return
}
l.Debugln("Sending search request for device type", deviceType, "on", intf.Name)
_, err = socket.WriteTo(search, ssdp)
@@ -178,16 +172,33 @@ USER-AGENT: syncthing/1.0
l.Debugln("Listening for UPnP response for device type", deviceType, "on", intf.Name)
// Listen for responses until a timeout is reached
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
// Listen for responses until a timeout is reached or the context is
// cancelled
resp := make([]byte, 65536)
loop:
for {
resp := make([]byte, 65536)
n, _, err := socket.ReadFrom(resp)
if err != nil {
if e, ok := err.(net.Error); !ok || !e.Timeout() {
l.Infoln("UPnP read:", err) //legitimate error, not a timeout.
}
if err := socket.SetDeadline(time.Now().Add(250 * time.Millisecond)); err != nil {
l.Infoln("UPnP socket:", err)
break
}
n, _, err := socket.ReadFrom(resp)
if err != nil {
select {
case <-ctx.Done():
break loop
default:
}
if e, ok := err.(net.Error); ok && e.Timeout() {
continue // continue reading
}
l.Infoln("UPnP read:", err) //legitimate error, not a timeout.
break
}
igds, err := parseResponse(ctx, deviceType, resp[:n])
if err != nil {
switch err.(type) {
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "STDISCOSRV" "1" "Feb 02, 2020" "v1" "Syncthing"
.TH "STDISCOSRV" "1" "Feb 10, 2020" "v1" "Syncthing"
.SH NAME
stdiscosrv \- Syncthing Discovery Server
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "STRELAYSRV" "1" "Feb 02, 2020" "v1" "Syncthing"
.TH "STRELAYSRV" "1" "Feb 10, 2020" "v1" "Syncthing"
.SH NAME
strelaysrv \- Syncthing Relay Server
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-BEP" "7" "Feb 02, 2020" "v1" "Syncthing"
.TH "SYNCTHING-BEP" "7" "Feb 10, 2020" "v1" "Syncthing"
.SH NAME
syncthing-bep \- Block Exchange Protocol v1
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-CONFIG" "5" "Feb 02, 2020" "v1" "Syncthing"
.TH "SYNCTHING-CONFIG" "5" "Feb 10, 2020" "v1" "Syncthing"
.SH NAME
syncthing-config \- Syncthing Configuration
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-DEVICE-IDS" "7" "Feb 02, 2020" "v1" "Syncthing"
.TH "SYNCTHING-DEVICE-IDS" "7" "Feb 10, 2020" "v1" "Syncthing"
.SH NAME
syncthing-device-ids \- Understanding Device IDs
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-EVENT-API" "7" "Feb 02, 2020" "v1" "Syncthing"
.TH "SYNCTHING-EVENT-API" "7" "Feb 10, 2020" "v1" "Syncthing"
.SH NAME
syncthing-event-api \- Event API
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-FAQ" "7" "Feb 02, 2020" "v1" "Syncthing"
.TH "SYNCTHING-FAQ" "7" "Feb 10, 2020" "v1" "Syncthing"
.SH NAME
syncthing-faq \- Frequently Asked Questions
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-GLOBALDISCO" "7" "Feb 02, 2020" "v1" "Syncthing"
.TH "SYNCTHING-GLOBALDISCO" "7" "Feb 10, 2020" "v1" "Syncthing"
.SH NAME
syncthing-globaldisco \- Global Discovery Protocol v3
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-LOCALDISCO" "7" "Feb 02, 2020" "v1" "Syncthing"
.TH "SYNCTHING-LOCALDISCO" "7" "Feb 10, 2020" "v1" "Syncthing"
.SH NAME
syncthing-localdisco \- Local Discovery Protocol v4
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-NETWORKING" "7" "Feb 02, 2020" "v1" "Syncthing"
.TH "SYNCTHING-NETWORKING" "7" "Feb 10, 2020" "v1" "Syncthing"
.SH NAME
syncthing-networking \- Firewall Setup
.
+3 -3
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-RELAY" "7" "Feb 02, 2020" "v1" "Syncthing"
.TH "SYNCTHING-RELAY" "7" "Feb 10, 2020" "v1" "Syncthing"
.SH NAME
syncthing-relay \- Relay Protocol v1
.
@@ -227,7 +227,7 @@ _
.SH SESSION MODE
.sp
The first and only message the client sends in the session mode is the
SessionInvitation message which contains the session key identifying which
JoinSessionRequest message which contains the session key identifying which
session you are trying to join. The relay responds with one of the following
Response messages:
.INDENT 0.0
@@ -578,7 +578,7 @@ Empty/all zero IP should be replaced with the relays public IP address that
was used when establishing the protocol mode connection.
.TP
.B : Port
An optional port on which the relay server is expecting you to connect,
The port on which the relay server is expecting you to connect,
in order to start a connection in session mode.
.TP
.B : Server Socket
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-REST-API" "7" "Feb 02, 2020" "v1" "Syncthing"
.TH "SYNCTHING-REST-API" "7" "Feb 10, 2020" "v1" "Syncthing"
.SH NAME
syncthing-rest-api \- REST API
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-SECURITY" "7" "Feb 02, 2020" "v1" "Syncthing"
.TH "SYNCTHING-SECURITY" "7" "Feb 10, 2020" "v1" "Syncthing"
.SH NAME
syncthing-security \- Security Principles
.
+2 -2
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-STIGNORE" "5" "Feb 02, 2020" "v1" "Syncthing"
.TH "SYNCTHING-STIGNORE" "5" "Feb 10, 2020" "v1" "Syncthing"
.SH NAME
syncthing-stignore \- Prevent files from being synchronized to other nodes
.
@@ -43,7 +43,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.UNINDENT
.SH DESCRIPTION
.sp
If some files should not be synchronized to other devices, a file called
If some files should not be synchronized to (or from) other devices, a file called
\fB\&.stignore\fP can be created containing file patterns to ignore. The
\fB\&.stignore\fP file must be placed in the root of the folder. The
\fB\&.stignore\fP file itself will never be synced to other devices, although it can
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-VERSIONING" "7" "Feb 02, 2020" "v1" "Syncthing"
.TH "SYNCTHING-VERSIONING" "7" "Feb 10, 2020" "v1" "Syncthing"
.SH NAME
syncthing-versioning \- Keep automatic backups of deleted files by other nodes
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING" "1" "Feb 02, 2020" "v1" "Syncthing"
.TH "SYNCTHING" "1" "Feb 10, 2020" "v1" "Syncthing"
.SH NAME
syncthing \- Syncthing
.