chore: switch database engine to sqlite (fixes #9954) (#9965)

Switch the database from LevelDB to SQLite, for greater stability and
simpler code.

Co-authored-by: Tommy van der Vorst <tommy@pixelspark.nl>
Co-authored-by: bt90 <btom1990@googlemail.com>
This commit is contained in:
Jakob Borg
2025-03-29 13:50:08 +01:00
committed by GitHub
co-authored by Tommy van der Vorst bt90
parent b1c8f88a44
commit 025905fcdf
146 changed files with 8315 additions and 11984 deletions
+3 -11
View File
@@ -12,6 +12,7 @@ import (
"sync"
"time"
"github.com/syncthing/syncthing/internal/timeutil"
"github.com/syncthing/syncthing/lib/protocol"
protocolmocks "github.com/syncthing/syncthing/lib/protocol/mocks"
"github.com/syncthing/syncthing/lib/rand"
@@ -81,7 +82,7 @@ func (f *fakeConnection) addFileLocked(name string, flags uint32, ftype protocol
Name: name,
Type: ftype,
Version: version,
Sequence: time.Now().UnixNano(),
Sequence: timeutil.StrictlyMonotonicNanos(),
LocalFlags: localFlags,
}
switch ftype {
@@ -108,15 +109,6 @@ func (f *fakeConnection) addFileLocked(name string, flags uint32, ftype protocol
f.fileData[name] = data
}
func (f *fakeConnection) addFileWithLocalFlags(name string, ftype protocol.FileInfoType, localFlags uint32) {
f.mut.Lock()
defer f.mut.Unlock()
var version protocol.Vector
version = version.Update(f.id.Short())
f.addFileLocked(name, 0, ftype, nil, version, localFlags)
}
func (f *fakeConnection) addFile(name string, flags uint32, ftype protocol.FileInfoType, data []byte) {
f.mut.Lock()
defer f.mut.Unlock()
@@ -148,7 +140,7 @@ func (f *fakeConnection) deleteFile(name string) {
fi.Deleted = true
fi.ModifiedS = time.Now().Unix()
fi.Version = fi.Version.Update(f.id.Short())
fi.Sequence = time.Now().UnixNano()
fi.Sequence = timeutil.StrictlyMonotonicNanos()
fi.Blocks = nil
f.files = append(append(f.files[:i], f.files[i+1:]...), fi)
+90
View File
@@ -0,0 +1,90 @@
// Copyright (C) 2021 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package model
import (
"github.com/syncthing/syncthing/lib/protocol"
"google.golang.org/protobuf/proto"
)
// How many files to send in each Index/IndexUpdate message.
const (
MaxBatchSizeBytes = 250 * 1024 // Aim for making index messages no larger than 250 KiB (uncompressed)
MaxBatchSizeFiles = 1000 // Either way, don't include more files than this
)
// FileInfoBatch is a utility to do file operations on the database in suitably
// sized batches.
type FileInfoBatch struct {
infos []protocol.FileInfo
size int
flushFn func([]protocol.FileInfo) error
error error
}
// NewFileInfoBatch returns a new FileInfoBatch that calls fn when it's time
// to flush. Errors from the flush function are considered non-recoverable;
// once an error is returned the flush function wil not be called again, and
// any further calls to Flush will return the same error (unless Reset is
// called).
func NewFileInfoBatch(fn func([]protocol.FileInfo) error) *FileInfoBatch {
return &FileInfoBatch{flushFn: fn}
}
func (b *FileInfoBatch) SetFlushFunc(fn func([]protocol.FileInfo) error) {
b.flushFn = fn
}
func (b *FileInfoBatch) Append(f protocol.FileInfo) {
if b.error != nil {
panic("bug: calling append on a failed batch")
}
if b.infos == nil {
b.infos = make([]protocol.FileInfo, 0, MaxBatchSizeFiles)
}
b.infos = append(b.infos, f)
b.size += proto.Size(f.ToWire(true))
}
func (b *FileInfoBatch) Full() bool {
return len(b.infos) >= MaxBatchSizeFiles || b.size >= MaxBatchSizeBytes
}
func (b *FileInfoBatch) FlushIfFull() error {
if b.error != nil {
return b.error
}
if b.Full() {
return b.Flush()
}
return nil
}
func (b *FileInfoBatch) Flush() error {
if b.error != nil {
return b.error
}
if len(b.infos) == 0 {
return nil
}
if err := b.flushFn(b.infos); err != nil {
b.error = err
return err
}
b.Reset()
return nil
}
func (b *FileInfoBatch) Reset() {
b.infos = nil
b.error = nil
b.size = 0
}
func (b *FileInfoBatch) Size() int {
return b.size
}
+64
View File
@@ -0,0 +1,64 @@
// Copyright (C) 2018 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 model
import (
"errors"
"testing"
"github.com/syncthing/syncthing/lib/protocol"
)
func TestFileInfoBatchError(t *testing.T) {
// Verify behaviour of the flush function returning an error.
var errReturn error
var called int
b := NewFileInfoBatch(func([]protocol.FileInfo) error {
called += 1
return errReturn
})
// Flush should work when the flush function error is nil
b.Append(protocol.FileInfo{Name: "test"})
if err := b.Flush(); err != nil {
t.Fatalf("expected nil, got %v", err)
}
if called != 1 {
t.Fatalf("expected 1, got %d", called)
}
// Flush should fail with an error retur
errReturn = errors.New("problem")
b.Append(protocol.FileInfo{Name: "test"})
if err := b.Flush(); err != errReturn {
t.Fatalf("expected %v, got %v", errReturn, err)
}
if called != 2 {
t.Fatalf("expected 2, got %d", called)
}
// Flush function should not be called again when it's already errored,
// same error should be returned by Flush()
if err := b.Flush(); err != errReturn {
t.Fatalf("expected %v, got %v", errReturn, err)
}
if called != 2 {
t.Fatalf("expected 2, got %d", called)
}
// Reset should clear the error (and the file list)
errReturn = nil
b.Reset()
b.Append(protocol.FileInfo{Name: "test"})
if err := b.Flush(); err != nil {
t.Fatalf("expected nil, got %v", err)
}
if called != 3 {
t.Fatalf("expected 3, got %d", called)
}
}
+131 -118
View File
@@ -15,8 +15,9 @@ import (
"sort"
"time"
"github.com/syncthing/syncthing/internal/db"
"github.com/syncthing/syncthing/internal/itererr"
"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"
@@ -46,7 +47,7 @@ type folder struct {
model *model
shortID protocol.ShortID
fset *db.FileSet
db db.DB
ignores *ignore.Matcher
mtimefs fs.Filesystem
modTimeWindow time.Duration
@@ -96,18 +97,18 @@ type puller interface {
pull() (bool, error) // true when successful and should not be retried
}
func newFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, evLogger events.Logger, ioLimiter *semaphore.Semaphore, ver versioner.Versioner) folder {
func newFolder(model *model, ignores *ignore.Matcher, cfg config.FolderConfiguration, evLogger events.Logger, ioLimiter *semaphore.Semaphore, ver versioner.Versioner) folder {
f := folder{
stateTracker: newStateTracker(cfg.ID, evLogger),
FolderConfiguration: cfg,
FolderStatisticsReference: stats.NewFolderStatisticsReference(model.db, cfg.ID),
FolderStatisticsReference: stats.NewFolderStatisticsReference(db.NewTyped(model.sdb, "folderstats/"+cfg.ID)),
ioLimiter: ioLimiter,
model: model,
shortID: model.shortID,
fset: fset,
db: model.sdb,
ignores: ignores,
mtimefs: cfg.Filesystem(fset),
mtimefs: cfg.Filesystem(fs.NewMtimeOption(model.sdb, cfg.ID)),
modTimeWindow: cfg.ModTimeWindow(),
done: make(chan struct{}),
@@ -367,17 +368,11 @@ func (f *folder) pull() (success bool, err error) {
}()
// If there is nothing to do, don't even enter sync-waiting state.
abort := true
snap, err := f.dbSnapshot()
needCount, err := f.db.CountNeed(f.folderID, protocol.LocalDeviceID)
if err != nil {
return false, err
}
snap.WithNeed(protocol.LocalDeviceID, func(intf protocol.FileInfo) bool {
abort = false
return false
})
snap.Release()
if abort {
if needCount.TotalItems() == 0 {
// Clears pull failures on items that were needed before, but aren't anymore.
f.errorsMut.Lock()
f.pullErrors = nil
@@ -484,15 +479,10 @@ 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.
snap, err := f.dbSnapshot()
if err != nil {
return err
}
subDirs = unifySubs(subDirs, func(file string) bool {
_, ok := snap.Get(protocol.LocalDeviceID, file)
return ok
_, ok, err := f.db.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file)
return err == nil && ok
})
snap.Release()
f.setState(FolderScanning)
f.clearScanErrors(subDirs)
@@ -546,7 +536,7 @@ const maxToRemove = 1000
type scanBatch struct {
f *folder
updateBatch *db.FileInfoBatch
updateBatch *FileInfoBatch
toRemove []string
}
@@ -555,7 +545,7 @@ func (f *folder) newScanBatch() *scanBatch {
f: f,
toRemove: make([]string, 0, maxToRemove),
}
b.updateBatch = db.NewFileInfoBatch(func(fs []protocol.FileInfo) error {
b.updateBatch = NewFileInfoBatch(func(fs []protocol.FileInfo) error {
if err := b.f.getHealthErrorWithoutIgnores(); err != nil {
l.Debugf("Stopping scan of folder %s due to: %s", b.f.Description(), err)
return err
@@ -570,46 +560,56 @@ func (b *scanBatch) Remove(item string) {
b.toRemove = append(b.toRemove, item)
}
func (b *scanBatch) flushToRemove() {
func (b *scanBatch) flushToRemove() error {
if len(b.toRemove) > 0 {
b.f.fset.RemoveLocalItems(b.toRemove)
if err := b.f.db.DropFilesNamed(b.f.folderID, protocol.LocalDeviceID, b.toRemove); err != nil {
return err
}
b.toRemove = b.toRemove[:0]
}
return nil
}
func (b *scanBatch) Flush() error {
b.flushToRemove()
if err := b.flushToRemove(); err != nil {
return err
}
return b.updateBatch.Flush()
}
func (b *scanBatch) FlushIfFull() error {
if len(b.toRemove) >= maxToRemove {
b.flushToRemove()
if err := b.flushToRemove(); err != nil {
return err
}
}
return b.updateBatch.FlushIfFull()
}
// Update adds the fileinfo to the batch for updating, and does a few checks.
// It returns false if the checks result in the file not going to be updated or removed.
func (b *scanBatch) Update(fi protocol.FileInfo, snap *db.Snapshot) bool {
func (b *scanBatch) Update(fi protocol.FileInfo) (bool, error) {
// Check for a "virtual" parent directory of encrypted files. We don't track
// it, but check if anything still exists within and delete it otherwise.
if b.f.Type == config.FolderTypeReceiveEncrypted && fi.IsDirectory() && protocol.IsEncryptedParent(fs.PathComponents(fi.Name)) {
if names, err := b.f.mtimefs.DirNames(fi.Name); err == nil && len(names) == 0 {
b.f.mtimefs.Remove(fi.Name)
}
return false
return false, nil
}
// Resolve receive-only items which are identical with the global state or
// the global item is our own receive-only item.
switch gf, ok := snap.GetGlobal(fi.Name); {
switch gf, ok, err := b.f.db.GetGlobalFile(b.f.folderID, fi.Name); {
case err != nil:
return false, err
case !ok:
case gf.IsReceiveOnlyChanged():
if fi.IsDeleted() {
// Our item is deleted and the global item is our own receive only
// file. No point in keeping track of that.
b.Remove(fi.Name)
return true
l.Debugf("%v scanning: deleting deleted receive-only local-changed file: %v", b.f, fi)
return true, nil
}
case (b.f.Type == config.FolderTypeReceiveOnly || b.f.Type == config.FolderTypeReceiveEncrypted) &&
gf.IsEquivalentOptional(fi, protocol.FileInfoComparison{
@@ -621,20 +621,15 @@ func (b *scanBatch) Update(fi protocol.FileInfo, snap *db.Snapshot) bool {
IgnoreXattrs: !b.f.SyncXattrs && !b.f.SendXattrs,
}):
// What we have locally is equivalent to the global file.
l.Debugf("%v scanning: Merging identical locally changed item with global", b.f, fi)
l.Debugf("%v scanning: Merging identical locally changed item with global: %v", b.f, fi)
fi = gf
}
b.updateBatch.Append(fi)
return true
return true, nil
}
func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *scanBatch) (int, error) {
changes := 0
snap, err := f.dbSnapshot()
if err != nil {
return changes, err
}
defer snap.Release()
// If we return early e.g. due to a folder health error, the scan needs
// to be cancelled.
@@ -646,7 +641,7 @@ func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *scanBatch) (i
Subs: subDirs,
Matcher: f.ignores,
TempLifetime: time.Duration(f.model.cfg.Options().KeepTemporariesH) * time.Hour,
CurrentFiler: cFiler{snap},
CurrentFiler: cFiler{db: f.db, folder: f.folderID},
Filesystem: f.mtimefs,
IgnorePerms: f.IgnorePerms,
AutoNormalize: f.AutoNormalize,
@@ -683,15 +678,19 @@ func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *scanBatch) (i
return changes, err
}
if batch.Update(res.File, snap) {
if ok, err := batch.Update(res.File); err != nil {
return 0, err
} else if ok {
changes++
}
switch f.Type {
case config.FolderTypeReceiveOnly, config.FolderTypeReceiveEncrypted:
default:
if nf, ok := f.findRename(snap, res.File, alreadyUsedOrExisting); ok {
if batch.Update(nf, snap) {
if nf, ok := f.findRename(res.File, alreadyUsedOrExisting); ok {
if ok, err := batch.Update(nf); err != nil {
return 0, err
} else if ok {
changes++
}
}
@@ -705,25 +704,22 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch
var toIgnore []protocol.FileInfo
ignoredParent := ""
changes := 0
snap, err := f.dbSnapshot()
if err != nil {
return 0, err
}
defer snap.Release()
outer:
for _, sub := range subDirs {
var iterError error
for fi, err := range itererr.Zip(f.db.AllLocalFilesWithPrefix(f.folderID, protocol.LocalDeviceID, sub)) {
if err != nil {
return changes, err
}
snap.WithPrefixedHaveTruncated(protocol.LocalDeviceID, sub, func(fi protocol.FileInfo) bool {
select {
case <-f.ctx.Done():
return false
break outer
default:
}
if err := batch.FlushIfFull(); err != nil {
iterError = err
return false
return 0, err
}
if ignoredParent != "" && !fs.IsParent(fi.Name, ignoredParent) {
@@ -731,12 +727,13 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch
l.Debugln("marking file as ignored", file)
nf := file
nf.SetIgnored()
if batch.Update(nf, snap) {
if ok, err := batch.Update(nf); err != nil {
return 0, err
} else if ok {
changes++
}
if err := batch.FlushIfFull(); err != nil {
iterError = err
return false
return 0, err
}
}
toIgnore = toIgnore[:0]
@@ -745,7 +742,7 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch
switch ignored := f.ignores.Match(fi.Name).IsIgnored(); {
case fi.IsIgnored() && ignored:
return true
continue
case !fi.IsIgnored() && ignored:
// File was not ignored at last pass but has been ignored.
if fi.IsDirectory() {
@@ -756,13 +753,15 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch
// this path as the "highest" ignored parent
ignoredParent = fi.Name
}
return true
continue
}
l.Debugln("marking file as ignored", fi)
nf := fi
nf.SetIgnored()
if batch.Update(nf, snap) {
if ok, err := batch.Update(nf); err != nil {
return 0, err
} else if ok {
changes++
}
@@ -781,7 +780,7 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch
toIgnore = toIgnore[:0]
ignoredParent = ""
}
return true
continue
}
nf := fi
nf.SetDeleted(f.shortID)
@@ -793,13 +792,17 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch
nf.Version = protocol.Vector{}
}
l.Debugln("marking file as deleted", nf)
if batch.Update(nf, snap) {
if ok, err := batch.Update(nf); err != nil {
return 0, err
} else if ok {
changes++
}
case fi.IsDeleted() && fi.IsReceiveOnlyChanged():
switch f.Type {
case config.FolderTypeReceiveOnly, config.FolderTypeReceiveEncrypted:
switch gf, ok := snap.GetGlobal(fi.Name); {
switch gf, ok, err := f.db.GetGlobalFile(f.folderID, fi.Name); {
case err != nil:
return 0, err
case !ok:
case gf.IsReceiveOnlyChanged():
l.Debugln("removing deleted, receive-only item that is globally receive-only from db", fi)
@@ -810,7 +813,9 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch
// pretend it is a normal deleted file (nobody cares about that).
l.Debugf("%v scanning: Marking globally deleted item as not locally changed: %v", f, fi.Name)
fi.LocalFlags &^= protocol.FlagLocalReceiveOnly
if batch.Update(fi, snap) {
if ok, err := batch.Update(fi); err != nil {
return 0, err
} else if ok {
changes++
}
}
@@ -819,14 +824,14 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch
// deleted and just the folder type/local flags changed.
fi.LocalFlags &^= protocol.FlagLocalReceiveOnly
l.Debugln("removing receive-only flag on deleted item", fi)
if batch.Update(fi, snap) {
if ok, err := batch.Update(fi); err != nil {
return 0, err
} else if ok {
changes++
}
}
}
return true
})
}
select {
case <-f.ctx.Done():
@@ -834,30 +839,28 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch
default:
}
if iterError == nil && len(toIgnore) > 0 {
if len(toIgnore) > 0 {
for _, file := range toIgnore {
l.Debugln("marking file as ignored", file)
nf := file
nf.SetIgnored()
if batch.Update(nf, snap) {
if ok, err := batch.Update(nf); err != nil {
return 0, err
} else if ok {
changes++
}
if iterError = batch.FlushIfFull(); iterError != nil {
break
if err := batch.FlushIfFull(); err != nil {
return 0, err
}
}
toIgnore = toIgnore[:0]
}
if iterError != nil {
return changes, iterError
}
}
return changes, nil
}
func (f *folder) findRename(snap *db.Snapshot, file protocol.FileInfo, alreadyUsedOrExisting map[string]struct{}) (protocol.FileInfo, bool) {
func (f *folder) findRename(file protocol.FileInfo, alreadyUsedOrExisting map[string]struct{}) (protocol.FileInfo, bool) {
if len(file.Blocks) == 0 || file.Size == 0 {
return protocol.FileInfo{}, false
}
@@ -865,49 +868,58 @@ func (f *folder) findRename(snap *db.Snapshot, file protocol.FileInfo, alreadyUs
found := false
nf := protocol.FileInfo{}
snap.WithBlocksHash(file.BlocksHash, func(fi protocol.FileInfo) bool {
loop:
for fi, err := range itererr.Zip(f.db.AllLocalFilesWithBlocksHash(f.folderID, file.BlocksHash)) {
if err != nil {
return protocol.FileInfo{}, false
}
select {
case <-f.ctx.Done():
return false
break loop
default:
}
if fi.Name == file.Name {
alreadyUsedOrExisting[fi.Name] = struct{}{}
return true
continue
}
if _, ok := alreadyUsedOrExisting[fi.Name]; ok {
return true
continue
}
if fi.ShouldConflict() {
return true
continue
}
if f.ignores.Match(fi.Name).IsIgnored() {
return true
continue
}
// Only check the size.
// No point checking block equality, as that uses BlocksHash comparison if that is set (which it will be).
// No point checking BlocksHash comparison as WithBlocksHash already does that.
if file.Size != fi.Size {
return true
continue
}
alreadyUsedOrExisting[fi.Name] = struct{}{}
if !osutil.IsDeleted(f.mtimefs, fi.Name) {
return true
continue
}
nf = fi
var ok bool
nf, ok, err = f.db.GetDeviceFile(f.folderID, protocol.LocalDeviceID, fi.Name)
if err != nil || !ok || nf.Sequence != fi.Sequence {
continue
}
nf.SetDeleted(f.shortID)
nf.LocalFlags = f.localFlags
found = true
return false
})
break
}
return nf, found
}
@@ -1216,20 +1228,26 @@ func (f *folder) ScheduleForceRescan(path string) {
}
}
func (f *folder) updateLocalsFromScanning(fs []protocol.FileInfo) {
f.updateLocals(fs)
func (f *folder) updateLocalsFromScanning(fs []protocol.FileInfo) error {
if err := f.updateLocals(fs); err != nil {
return err
}
f.emitDiskChangeEvents(fs, events.LocalChangeDetected)
return nil
}
func (f *folder) updateLocalsFromPulling(fs []protocol.FileInfo) {
f.updateLocals(fs)
func (f *folder) updateLocalsFromPulling(fs []protocol.FileInfo) error {
if err := f.updateLocals(fs); err != nil {
return err
}
f.emitDiskChangeEvents(fs, events.RemoteChangeDetected)
return nil
}
func (f *folder) updateLocals(fs []protocol.FileInfo) {
f.fset.Update(protocol.LocalDeviceID, fs)
func (f *folder) updateLocals(fs []protocol.FileInfo) error {
if err := f.db.Update(f.folderID, protocol.LocalDeviceID, fs); err != nil {
return err
}
filenames := make([]string, len(fs))
f.forcedRescanPathsMut.Lock()
@@ -1240,7 +1258,10 @@ func (f *folder) updateLocals(fs []protocol.FileInfo) {
}
f.forcedRescanPathsMut.Unlock()
seq := f.fset.Sequence(protocol.LocalDeviceID)
seq, err := f.db.GetDeviceSequence(f.folderID, protocol.LocalDeviceID)
if err != nil {
return err
}
f.evLogger.Log(events.LocalIndexUpdated, map[string]interface{}{
"folder": f.ID,
"items": len(fs),
@@ -1248,6 +1269,7 @@ func (f *folder) updateLocals(fs []protocol.FileInfo) {
"sequence": seq,
"version": seq, // legacy for sequence
})
return nil
}
func (f *folder) emitDiskChangeEvents(fs []protocol.FileInfo, typeOfEvent events.EventType) {
@@ -1294,23 +1316,19 @@ func (f *folder) handleForcedRescans() error {
return nil
}
batch := db.NewFileInfoBatch(func(fs []protocol.FileInfo) error {
f.fset.Update(protocol.LocalDeviceID, fs)
return nil
batch := NewFileInfoBatch(func(fs []protocol.FileInfo) error {
return f.db.Update(f.folderID, protocol.LocalDeviceID, fs)
})
snap, err := f.dbSnapshot()
if err != nil {
return err
}
defer snap.Release()
for _, path := range paths {
if err := batch.FlushIfFull(); err != nil {
return err
}
fi, ok := snap.Get(protocol.LocalDeviceID, path)
fi, ok, err := f.db.GetDeviceFile(f.folderID, protocol.LocalDeviceID, path)
if err != nil {
return err
}
if !ok {
continue
}
@@ -1318,23 +1336,13 @@ func (f *folder) handleForcedRescans() error {
batch.Append(fi)
}
if err = batch.Flush(); err != nil {
if err := batch.Flush(); err != nil {
return err
}
return f.scanSubdirs(paths)
}
// dbSnapshots gets a snapshot from the fileset, and wraps any error
// in a svcutil.FatalErr.
func (f *folder) dbSnapshot() (*db.Snapshot, error) {
snap, err := f.fset.Snapshot()
if err != nil {
return nil, svcutil.AsFatalErr(err, svcutil.ExitError)
}
return snap, nil
}
// The exists function is expected to return true for all known paths
// (excluding "" and ".")
func unifySubs(dirs []string, exists func(dir string) bool) []string {
@@ -1370,10 +1378,15 @@ func unifySubs(dirs []string, exists func(dir string) bool) []string {
}
type cFiler struct {
*db.Snapshot
db db.DB
folder string
}
// Implements scanner.CurrentFiler
func (cf cFiler) CurrentFile(file string) (protocol.FileInfo, bool) {
return cf.Get(protocol.LocalDeviceID, file)
fi, ok, err := cf.db.GetDeviceFile(cf.folder, protocol.LocalDeviceID, file)
if err != nil || !ok {
return protocol.FileInfo{}, false
}
return fi, true
}
+17 -25
View File
@@ -10,8 +10,8 @@ import (
"fmt"
"sort"
"github.com/syncthing/syncthing/internal/itererr"
"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"
@@ -28,8 +28,8 @@ type receiveEncryptedFolder struct {
*sendReceiveFolder
}
func newReceiveEncryptedFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *semaphore.Semaphore) service {
f := &receiveEncryptedFolder{newSendReceiveFolder(model, fset, ignores, cfg, ver, evLogger, ioLimiter).(*sendReceiveFolder)}
func newReceiveEncryptedFolder(model *model, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *semaphore.Semaphore) service {
f := &receiveEncryptedFolder{newSendReceiveFolder(model, ignores, cfg, ver, evLogger, ioLimiter).(*sendReceiveFolder)}
f.localFlags = protocol.FlagLocalReceiveOnly // gets propagated to the scanner, and set on locally changed files
return f
}
@@ -44,30 +44,27 @@ func (f *receiveEncryptedFolder) revert() error {
f.setState(FolderScanning)
defer f.setState(FolderIdle)
batch := db.NewFileInfoBatch(func(fs []protocol.FileInfo) error {
batch := NewFileInfoBatch(func(fs []protocol.FileInfo) error {
f.updateLocalsFromScanning(fs)
return nil
})
snap, err := f.dbSnapshot()
if err != nil {
return err
}
defer snap.Release()
var iterErr error
var dirs []string
snap.WithHaveTruncated(protocol.LocalDeviceID, func(fi protocol.FileInfo) bool {
if iterErr = batch.FlushIfFull(); iterErr != nil {
return false
for fi, err := range itererr.Zip(f.db.AllLocalFiles(f.folderID, protocol.LocalDeviceID)) {
if err != nil {
return err
}
if err := batch.FlushIfFull(); err != nil {
return err
}
if !fi.IsReceiveOnlyChanged() || fi.IsDeleted() {
return true
continue
}
if fi.IsDirectory() {
dirs = append(dirs, fi.Name)
return true
continue
}
if err := f.inWritableDir(f.mtimefs.Remove, fi.Name); err != nil && !fs.IsNotExist(err) {
@@ -84,15 +81,10 @@ func (f *receiveEncryptedFolder) revert() error {
// deleted, it will not show up as an unexpected file in the UI
// anymore.
batch.Append(fi)
return true
})
f.revertHandleDirs(dirs, snap)
if iterErr != nil {
return iterErr
}
f.revertHandleDirs(dirs)
if err := batch.Flush(); err != nil {
return err
}
@@ -103,7 +95,7 @@ func (f *receiveEncryptedFolder) revert() error {
return nil
}
func (f *receiveEncryptedFolder) revertHandleDirs(dirs []string, snap *db.Snapshot) {
func (f *receiveEncryptedFolder) revertHandleDirs(dirs []string) {
if len(dirs) == 0 {
return
}
@@ -114,7 +106,7 @@ func (f *receiveEncryptedFolder) revertHandleDirs(dirs []string, snap *db.Snapsh
sort.Sort(sort.Reverse(sort.StringSlice(dirs)))
for _, dir := range dirs {
if err := f.deleteDirOnDisk(dir, snap, scanChan); err != nil {
if err := f.deleteDirOnDisk(dir, scanChan); err != nil {
f.newScanError(dir, fmt.Errorf("deleting unexpected dir: %w", err))
}
scanChan <- dir
+30 -29
View File
@@ -10,8 +10,8 @@ import (
"sort"
"time"
"github.com/syncthing/syncthing/internal/itererr"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/ignore"
"github.com/syncthing/syncthing/lib/protocol"
@@ -57,8 +57,8 @@ type receiveOnlyFolder struct {
*sendReceiveFolder
}
func newReceiveOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *semaphore.Semaphore) service {
sr := newSendReceiveFolder(model, fset, ignores, cfg, ver, evLogger, ioLimiter).(*sendReceiveFolder)
func newReceiveOnlyFolder(model *model, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *semaphore.Semaphore) service {
sr := newSendReceiveFolder(model, ignores, cfg, ver, evLogger, ioLimiter).(*sendReceiveFolder)
sr.localFlags = protocol.FlagLocalReceiveOnly // gets propagated to the scanner, and set on locally changed files
return &receiveOnlyFolder{sr}
}
@@ -83,30 +83,31 @@ func (f *receiveOnlyFolder) revert() error {
scanChan: scanChan,
}
batch := db.NewFileInfoBatch(func(files []protocol.FileInfo) error {
batch := NewFileInfoBatch(func(files []protocol.FileInfo) error {
f.updateLocalsFromScanning(files)
return nil
})
snap, err := f.dbSnapshot()
if err != nil {
return err
}
defer snap.Release()
snap.WithHave(protocol.LocalDeviceID, func(fi protocol.FileInfo) bool {
for fi, err := range itererr.Zip(f.db.AllLocalFiles(f.folderID, protocol.LocalDeviceID)) {
if err != nil {
return err
}
if !fi.IsReceiveOnlyChanged() {
// We're only interested in files that have changed locally in
// receive only mode.
return true
continue
}
fi.LocalFlags &^= protocol.FlagLocalReceiveOnly
switch gf, ok := snap.GetGlobal(fi.Name); {
switch gf, ok, err := f.db.GetGlobalFile(f.folderID, fi.Name); {
case err != nil:
return err
case !ok:
msg := "Unexpected global file that we have locally"
msg := "Unexpectedly missing global file that we have locally"
l.Debugf("%v revert: %v: %v", f, msg, fi.Name)
f.evLogger.Log(events.Failure, msg)
return true
continue
case gf.IsReceiveOnlyChanged():
// The global file is our own. A revert then means to delete it.
// We'll delete files directly, directories get queued and
@@ -115,13 +116,13 @@ func (f *receiveOnlyFolder) revert() error {
fi.Version = protocol.Vector{} // if this file ever resurfaces anywhere we want our delete to be strictly older
break
}
handled, err := delQueue.handle(fi, snap)
l.Debugf("Revert: deleting %s: %v\n", fi.Name, err)
handled, err := delQueue.handle(fi)
if err != nil {
l.Infof("Revert: deleting %s: %v\n", fi.Name, err)
return true // continue
continue
}
if !handled {
return true // continue
continue
}
fi.SetDeleted(f.shortID)
fi.Version = protocol.Vector{} // if this file ever resurfaces anywhere we want our delete to be strictly older
@@ -144,13 +145,13 @@ func (f *receiveOnlyFolder) revert() error {
batch.Append(fi)
_ = batch.FlushIfFull()
return true
})
_ = batch.Flush()
}
if err := batch.Flush(); err != nil {
return err
}
// Handle any queued directories
deleted, err := delQueue.flush(snap)
deleted, err := delQueue.flush()
if err != nil {
l.Infoln("Revert:", err)
}
@@ -179,15 +180,15 @@ func (f *receiveOnlyFolder) revert() error {
// directories for last.
type deleteQueue struct {
handler interface {
deleteItemOnDisk(item protocol.FileInfo, snap *db.Snapshot, scanChan chan<- string) error
deleteDirOnDisk(dir string, snap *db.Snapshot, scanChan chan<- string) error
deleteItemOnDisk(item protocol.FileInfo, scanChan chan<- string) error
deleteDirOnDisk(dir string, scanChan chan<- string) error
}
ignores *ignore.Matcher
dirs []string
scanChan chan<- string
}
func (q *deleteQueue) handle(fi protocol.FileInfo, snap *db.Snapshot) (bool, error) {
func (q *deleteQueue) handle(fi protocol.FileInfo) (bool, error) {
// Things that are ignored but not marked deletable are not processed.
ign := q.ignores.Match(fi.Name)
if ign.IsIgnored() && !ign.IsDeletable() {
@@ -201,11 +202,11 @@ func (q *deleteQueue) handle(fi protocol.FileInfo, snap *db.Snapshot) (bool, err
}
// Kill it.
err := q.handler.deleteItemOnDisk(fi, snap, q.scanChan)
err := q.handler.deleteItemOnDisk(fi, q.scanChan)
return true, err
}
func (q *deleteQueue) flush(snap *db.Snapshot) ([]string, error) {
func (q *deleteQueue) flush() ([]string, error) {
// Process directories from the leaves inward.
sort.Sort(sort.Reverse(sort.StringSlice(q.dirs)))
@@ -213,7 +214,7 @@ func (q *deleteQueue) flush(snap *db.Snapshot) ([]string, error) {
var deleted []string
for _, dir := range q.dirs {
if err := q.handler.deleteDirOnDisk(dir, snap, q.scanChan); err == nil {
if err := q.handler.deleteDirOnDisk(dir, q.scanChan); err == nil {
deleted = append(deleted, dir)
} else if firstError == nil {
firstError = err
+64 -58
View File
@@ -13,6 +13,7 @@ import (
"testing"
"time"
"github.com/syncthing/syncthing/internal/itererr"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
@@ -28,7 +29,7 @@ func TestRecvOnlyRevertDeletes(t *testing.T) {
m, f, wcfgCancel := setupROFolder(t)
defer wcfgCancel()
ffs := f.Filesystem(nil)
ffs := f.Filesystem()
defer cleanupModel(m)
conn := addFakeConn(m, device1, f.ID)
@@ -46,9 +47,11 @@ func TestRecvOnlyRevertDeletes(t *testing.T) {
// Send and index update for the known stuff
must(t, m.Index(conn, &protocol.Index{Folder: "ro", Files: knownFiles}))
f.updateLocalsFromScanning(knownFiles)
if err := f.updateLocalsFromScanning(knownFiles); err != nil {
t.Fatal(err)
}
size := globalSize(t, m, "ro")
size := mustV(m.GlobalSize("ro"))
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
}
@@ -59,15 +62,15 @@ func TestRecvOnlyRevertDeletes(t *testing.T) {
// We should now have two files and two directories, with global state unchanged.
size = globalSize(t, m, "ro")
size = mustV(m.GlobalSize("ro"))
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Global: expected 2 files and 2 directories: %+v", size)
t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
}
size = localSize(t, m, "ro")
size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
if size.Files != 2 || size.Directories != 2 {
t.Fatalf("Local: expected 2 files and 2 directories: %+v", size)
}
size = receiveOnlyChangedSize(t, m, "ro")
size = mustV(m.ReceiveOnlySize("ro"))
if size.Files+size.Directories == 0 {
t.Fatalf("ROChanged: expected something: %+v", size)
}
@@ -92,11 +95,11 @@ func TestRecvOnlyRevertDeletes(t *testing.T) {
// We should now have one file and directory again.
size = globalSize(t, m, "ro")
size = mustV(m.GlobalSize("ro"))
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Global: expected 1 files and 1 directories: %+v", size)
}
size = localSize(t, m, "ro")
size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Local: expected 1 files and 1 directories: %+v", size)
}
@@ -110,7 +113,7 @@ func TestRecvOnlyRevertNeeds(t *testing.T) {
m, f, wcfgCancel := setupROFolder(t)
defer wcfgCancel()
ffs := f.Filesystem(nil)
ffs := f.Filesystem()
defer cleanupModel(m)
conn := addFakeConn(m, device1, f.ID)
@@ -131,19 +134,19 @@ func TestRecvOnlyRevertNeeds(t *testing.T) {
// Everything should be in sync.
size := globalSize(t, m, "ro")
size := mustV(m.GlobalSize("ro"))
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
}
size = localSize(t, m, "ro")
size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
}
size = needSizeLocal(t, m, "ro")
size = mustV(m.NeedSize("ro", protocol.LocalDeviceID))
if size.Files+size.Directories > 0 {
t.Fatalf("Need: expected nothing: %+v", size)
}
size = receiveOnlyChangedSize(t, m, "ro")
size = mustV(m.ReceiveOnlySize("ro"))
if size.Files+size.Directories > 0 {
t.Fatalf("ROChanged: expected nothing: %+v", size)
}
@@ -159,20 +162,20 @@ func TestRecvOnlyRevertNeeds(t *testing.T) {
// We now have a newer file than the rest of the cluster. Global state should reflect this.
size = globalSize(t, m, "ro")
size = mustV(m.GlobalSize("ro"))
const sizeOfDir = 128
if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) {
t.Fatalf("Global: expected no change due to the new file: %+v", size)
}
size = localSize(t, m, "ro")
size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
t.Fatalf("Local: expected the new file to be reflected: %+v", size)
}
size = needSizeLocal(t, m, "ro")
size = mustV(m.NeedSize("ro", protocol.LocalDeviceID))
if size.Files+size.Directories > 0 {
t.Fatalf("Need: expected nothing: %+v", size)
}
size = receiveOnlyChangedSize(t, m, "ro")
size = mustV(m.ReceiveOnlySize("ro"))
if size.Files+size.Directories == 0 {
t.Fatalf("ROChanged: expected something: %+v", size)
}
@@ -181,15 +184,15 @@ func TestRecvOnlyRevertNeeds(t *testing.T) {
m.Revert("ro")
size = globalSize(t, m, "ro")
size = mustV(m.GlobalSize("ro"))
if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) {
t.Fatalf("Global: expected the global size to revert: %+v", size)
}
size = localSize(t, m, "ro")
size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
t.Fatalf("Local: expected the local size to remain: %+v", size)
}
size = needSizeLocal(t, m, "ro")
size = mustV(m.NeedSize("ro", protocol.LocalDeviceID))
if size.Files != 1 || size.Bytes != int64(len(oldData)) {
t.Fatalf("Local: expected to need the old file data: %+v", size)
}
@@ -200,7 +203,7 @@ func TestRecvOnlyUndoChanges(t *testing.T) {
m, f, wcfgCancel := setupROFolder(t)
defer wcfgCancel()
ffs := f.Filesystem(nil)
ffs := f.Filesystem()
defer cleanupModel(m)
conn := addFakeConn(m, device1, f.ID)
@@ -221,19 +224,19 @@ func TestRecvOnlyUndoChanges(t *testing.T) {
// Everything should be in sync.
size := globalSize(t, m, "ro")
size := mustV(m.GlobalSize("ro"))
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
}
size = localSize(t, m, "ro")
size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
}
size = needSizeLocal(t, m, "ro")
size = mustV(m.NeedSize("ro", protocol.LocalDeviceID))
if size.Files+size.Directories > 0 {
t.Fatalf("Need: expected nothing: %+v", size)
}
size = receiveOnlyChangedSize(t, m, "ro")
size = mustV(m.ReceiveOnlySize("ro"))
if size.Files+size.Directories > 0 {
t.Fatalf("ROChanged: expected nothing: %+v", size)
}
@@ -246,7 +249,7 @@ func TestRecvOnlyUndoChanges(t *testing.T) {
must(t, m.ScanFolder("ro"))
size = receiveOnlyChangedSize(t, m, "ro")
size = mustV(m.ReceiveOnlySize("ro"))
if size.Files != 2 {
t.Fatalf("Receive only: expected 2 files: %+v", size)
}
@@ -259,7 +262,7 @@ func TestRecvOnlyUndoChanges(t *testing.T) {
must(t, m.ScanFolder("ro"))
size = receiveOnlyChangedSize(t, m, "ro")
size = mustV(m.ReceiveOnlySize("ro"))
if size.Files+size.Directories+size.Deleted != 0 {
t.Fatalf("Receive only: expected all zero: %+v", size)
}
@@ -270,7 +273,7 @@ func TestRecvOnlyDeletedRemoteDrop(t *testing.T) {
m, f, wcfgCancel := setupROFolder(t)
defer wcfgCancel()
ffs := f.Filesystem(nil)
ffs := f.Filesystem()
defer cleanupModel(m)
conn := addFakeConn(m, device1, f.ID)
@@ -291,19 +294,19 @@ func TestRecvOnlyDeletedRemoteDrop(t *testing.T) {
// Everything should be in sync.
size := globalSize(t, m, "ro")
size := mustV(m.GlobalSize("ro"))
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
}
size = localSize(t, m, "ro")
size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
}
size = needSizeLocal(t, m, "ro")
size = mustV(m.NeedSize("ro", protocol.LocalDeviceID))
if size.Files+size.Directories > 0 {
t.Fatalf("Need: expected nothing: %+v", size)
}
size = receiveOnlyChangedSize(t, m, "ro")
size = mustV(m.ReceiveOnlySize("ro"))
if size.Files+size.Directories > 0 {
t.Fatalf("ROChanged: expected nothing: %+v", size)
}
@@ -314,17 +317,17 @@ func TestRecvOnlyDeletedRemoteDrop(t *testing.T) {
must(t, m.ScanFolder("ro"))
size = receiveOnlyChangedSize(t, m, "ro")
size = mustV(m.ReceiveOnlySize("ro"))
if size.Deleted != 1 {
t.Fatalf("Receive only: expected 1 deleted: %+v", size)
}
// Drop the remote
f.fset.Drop(device1)
f.db.DropAllFiles("ro", device1)
must(t, m.ScanFolder("ro"))
size = receiveOnlyChangedSize(t, m, "ro")
size = mustV(m.ReceiveOnlySize("ro"))
if size.Deleted != 0 {
t.Fatalf("Receive only: expected no deleted: %+v", size)
}
@@ -335,7 +338,7 @@ func TestRecvOnlyRemoteUndoChanges(t *testing.T) {
m, f, wcfgCancel := setupROFolder(t)
defer wcfgCancel()
ffs := f.Filesystem(nil)
ffs := f.Filesystem()
defer cleanupModel(m)
conn := addFakeConn(m, device1, f.ID)
@@ -356,19 +359,19 @@ func TestRecvOnlyRemoteUndoChanges(t *testing.T) {
// Everything should be in sync.
size := globalSize(t, m, "ro")
size := mustV(m.GlobalSize("ro"))
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
}
size = localSize(t, m, "ro")
size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
}
size = needSizeLocal(t, m, "ro")
size = mustV(m.NeedSize("ro", protocol.LocalDeviceID))
if size.Files+size.Directories > 0 {
t.Fatalf("Need: expected nothing: %+v", size)
}
size = receiveOnlyChangedSize(t, m, "ro")
size = mustV(m.ReceiveOnlySize("ro"))
if size.Files+size.Directories > 0 {
t.Fatalf("ROChanged: expected nothing: %+v", size)
}
@@ -382,7 +385,7 @@ func TestRecvOnlyRemoteUndoChanges(t *testing.T) {
must(t, m.ScanFolder("ro"))
size = receiveOnlyChangedSize(t, m, "ro")
size = mustV(m.ReceiveOnlySize("ro"))
if size.Files != 2 {
t.Fatalf("Receive only: expected 2 files: %+v", size)
}
@@ -390,17 +393,17 @@ func TestRecvOnlyRemoteUndoChanges(t *testing.T) {
// Do the same changes on the remote
files := make([]protocol.FileInfo, 0, 2)
snap := fsetSnapshot(t, f.fset)
snap.WithHave(protocol.LocalDeviceID, func(f protocol.FileInfo) bool {
for f, err := range itererr.Zip(f.db.AllLocalFiles("ro", protocol.LocalDeviceID)) {
if err != nil {
t.Fatal(err)
}
if f.Name != file && f.Name != knownFile {
return true
continue
}
f.LocalFlags = 0
f.Version = protocol.Vector{}.Update(device1.Short())
files = append(files, f)
return true
})
snap.Release()
}
must(t, m.IndexUpdate(conn, &protocol.IndexUpdate{Folder: "ro", Files: files}))
// Ensure the pull to resolve conflicts (content identical) happened
@@ -409,7 +412,10 @@ func TestRecvOnlyRemoteUndoChanges(t *testing.T) {
return nil
}))
size = receiveOnlyChangedSize(t, m, "ro")
size, err := m.ReceiveOnlySize("ro")
if err != nil {
t.Fatal(err)
}
if size.Files+size.Directories+size.Deleted != 0 {
t.Fatalf("Receive only: expected all zero: %+v", size)
}
@@ -424,7 +430,7 @@ func TestRecvOnlyRevertOwnID(t *testing.T) {
m, f, wcfgCancel := setupROFolder(t)
defer wcfgCancel()
ffs := f.Filesystem(nil)
ffs := f.Filesystem()
defer cleanupModel(m)
conn := addFakeConn(m, device1, f.ID)
@@ -484,7 +490,7 @@ func TestRecvOnlyLocalChangeDoesNotCauseConflict(t *testing.T) {
m, f, wcfgCancel := setupROFolder(t)
defer wcfgCancel()
ffs := f.Filesystem(nil)
ffs := f.Filesystem()
defer cleanupModel(m)
conn := addFakeConn(m, device1, f.ID)
@@ -505,19 +511,19 @@ func TestRecvOnlyLocalChangeDoesNotCauseConflict(t *testing.T) {
// Everything should be in sync.
size := globalSize(t, m, "ro")
size := mustV(m.GlobalSize("ro"))
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
}
size = localSize(t, m, "ro")
size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
}
size = needSizeLocal(t, m, "ro")
size = mustV(m.NeedSize("ro", protocol.LocalDeviceID))
if size.Files+size.Directories > 0 {
t.Fatalf("Need: expected nothing: %+v", size)
}
size = receiveOnlyChangedSize(t, m, "ro")
size = mustV(m.ReceiveOnlySize("ro"))
if size.Files+size.Directories > 0 {
t.Fatalf("ROChanged: expected nothing: %+v", size)
}
@@ -528,7 +534,7 @@ func TestRecvOnlyLocalChangeDoesNotCauseConflict(t *testing.T) {
must(t, m.ScanFolder("ro"))
size = receiveOnlyChangedSize(t, m, "ro")
size = mustV(m.ReceiveOnlySize("ro"))
if size.Files != 1 {
t.Fatalf("Receive only: expected 1 file: %+v", size)
}
@@ -541,7 +547,7 @@ func TestRecvOnlyLocalChangeDoesNotCauseConflict(t *testing.T) {
must(t, m.ScanFolder("ro"))
size = needSizeLocal(t, m, "ro")
size = mustV(m.NeedSize("ro", protocol.LocalDeviceID))
if size.Files != 0 {
t.Fatalf("Need: expected nothing: %+v", size)
}
@@ -577,7 +583,7 @@ func setupKnownFiles(t *testing.T, ffs fs.Filesystem, data []byte) []protocol.Fi
ModifiedS: fi.ModTime().Unix(),
ModifiedNs: int32(fi.ModTime().Nanosecond()),
Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 42}}},
Sequence: 42,
Sequence: 43,
Blocks: blocks,
},
}
+40 -37
View File
@@ -7,8 +7,8 @@
package model
import (
"github.com/syncthing/syncthing/internal/itererr"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/ignore"
"github.com/syncthing/syncthing/lib/protocol"
@@ -24,9 +24,9 @@ type sendOnlyFolder struct {
folder
}
func newSendOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, _ versioner.Versioner, evLogger events.Logger, ioLimiter *semaphore.Semaphore) service {
func newSendOnlyFolder(model *model, ignores *ignore.Matcher, cfg config.FolderConfiguration, _ versioner.Versioner, evLogger events.Logger, ioLimiter *semaphore.Semaphore) service {
f := &sendOnlyFolder{
folder: newFolder(model, fset, ignores, cfg, evLogger, ioLimiter, nil),
folder: newFolder(model, ignores, cfg, evLogger, ioLimiter, nil),
}
f.folder.puller = f
return f
@@ -38,36 +38,36 @@ func (*sendOnlyFolder) PullErrors() []FileError {
// pull checks need for files that only differ by metadata (no changes on disk)
func (f *sendOnlyFolder) pull() (bool, error) {
batch := db.NewFileInfoBatch(func(files []protocol.FileInfo) error {
batch := NewFileInfoBatch(func(files []protocol.FileInfo) error {
f.updateLocalsFromPulling(files)
return nil
})
snap, err := f.dbSnapshot()
if err != nil {
return false, err
}
defer snap.Release()
snap.WithNeed(protocol.LocalDeviceID, func(file protocol.FileInfo) bool {
batch.FlushIfFull()
for file, err := range itererr.Zip(f.db.AllNeededGlobalFiles(f.folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0)) {
if err != nil {
return false, err
}
if err := batch.FlushIfFull(); err != nil {
return false, err
}
if f.ignores.Match(file.FileName()).IsIgnored() {
file.SetIgnored()
batch.Append(file)
l.Debugln(f, "Handling ignored file", file)
return true
continue
}
curFile, ok := snap.Get(protocol.LocalDeviceID, file.FileName())
curFile, ok, err := f.db.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.FileName())
if err != nil {
return false, err
}
if !ok {
if file.IsInvalid() {
// Global invalid file just exists for need accounting
if file.IsInvalid() || file.IsDeleted() {
// Accept the file for accounting purposes
batch.Append(file)
} else if file.IsDeleted() {
l.Debugln("Should never get a deleted file as needed when we don't have it")
f.evLogger.Log(events.Failure, "got deleted file that doesn't exist locally as needed when pulling on send-only")
}
return true
continue
}
if !file.IsEquivalentOptional(curFile, protocol.FileInfoComparison{
@@ -76,14 +76,12 @@ func (f *sendOnlyFolder) pull() (bool, error) {
IgnoreOwnership: !f.SyncOwnership,
IgnoreXattrs: !f.SyncXattrs,
}) {
return true
continue
}
batch.Append(file)
l.Debugln(f, "Merging versions of identical file", file)
return true
})
}
batch.Flush()
@@ -100,25 +98,31 @@ func (f *sendOnlyFolder) override() error {
f.setState(FolderScanning)
defer f.setState(FolderIdle)
batch := db.NewFileInfoBatch(func(files []protocol.FileInfo) error {
batch := NewFileInfoBatch(func(files []protocol.FileInfo) error {
f.updateLocalsFromScanning(files)
return nil
})
snap, err := f.dbSnapshot()
if err != nil {
return err
}
defer snap.Release()
snap.WithNeed(protocol.LocalDeviceID, func(need protocol.FileInfo) bool {
_ = batch.FlushIfFull()
have, ok := snap.Get(protocol.LocalDeviceID, need.Name)
for need, err := range itererr.Zip(f.db.AllNeededGlobalFiles(f.folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0)) {
if err != nil {
return err
}
if err := batch.FlushIfFull(); err != nil {
return err
}
have, haveOk, err := f.db.GetDeviceFile(f.folderID, protocol.LocalDeviceID, need.Name)
if err != nil {
return err
}
// Don't override files that are in a bad state (ignored,
// unsupported, must rescan, ...).
if ok && have.IsInvalid() {
return true
if haveOk && have.IsInvalid() {
continue
}
if !ok || have.Name != need.Name {
if !haveOk || have.Name != need.Name {
// We are missing the file
need.SetDeleted(f.shortID)
} else {
@@ -128,7 +132,6 @@ func (f *sendOnlyFolder) override() error {
}
need.Sequence = 0
batch.Append(need)
return true
})
}
return batch.Flush()
}
+147 -124
View File
@@ -19,9 +19,9 @@ import (
"strings"
"time"
"github.com/syncthing/syncthing/internal/itererr"
"github.com/syncthing/syncthing/lib/build"
"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"
@@ -129,9 +129,9 @@ type sendReceiveFolder struct {
tempPullErrors map[string]string // pull errors that might be just transient
}
func newSendReceiveFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *semaphore.Semaphore) service {
func newSendReceiveFolder(model *model, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *semaphore.Semaphore) service {
f := &sendReceiveFolder{
folder: newFolder(model, fset, ignores, cfg, evLogger, ioLimiter, ver),
folder: newFolder(model, ignores, cfg, evLogger, ioLimiter, ver),
queue: newJobQueue(),
blockPullReorderer: newBlockPullReorderer(cfg.BlockPullOrder, model.id, cfg.DeviceIDs()),
writeLimiter: semaphore.New(cfg.MaxConcurrentWrites),
@@ -240,12 +240,6 @@ func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) (int, error)
f.tempPullErrors = make(map[string]string)
f.errorsMut.Unlock()
snap, err := f.dbSnapshot()
if err != nil {
return 0, err
}
defer snap.Release()
pullChan := make(chan pullBlockState)
copyChan := make(chan copyBlocksState)
finisherChan := make(chan *sharedPullerState)
@@ -277,18 +271,18 @@ func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) (int, error)
pullWg.Add(1)
go func() {
// pullerRoutine finishes when pullChan is closed
f.pullerRoutine(snap, pullChan, finisherChan)
f.pullerRoutine(pullChan, finisherChan)
pullWg.Done()
}()
doneWg.Add(1)
// finisherRoutine finishes when finisherChan is closed
go func() {
f.finisherRoutine(snap, finisherChan, dbUpdateChan, scanChan)
f.finisherRoutine(finisherChan, dbUpdateChan, scanChan)
doneWg.Done()
}()
changed, fileDeletions, dirDeletions, err := f.processNeeded(snap, dbUpdateChan, copyChan, scanChan)
changed, fileDeletions, dirDeletions, err := f.processNeeded(dbUpdateChan, copyChan, scanChan)
// Signal copy and puller routines that we are done with the in data for
// this iteration. Wait for them to finish.
@@ -303,7 +297,7 @@ func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) (int, error)
doneWg.Wait()
if err == nil {
f.processDeletions(fileDeletions, dirDeletions, snap, dbUpdateChan, scanChan)
f.processDeletions(fileDeletions, dirDeletions, dbUpdateChan, scanChan)
}
// Wait for db updates and scan scheduling to complete
@@ -315,7 +309,7 @@ func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) (int, error)
return changed, err
}
func (f *sendReceiveFolder) processNeeded(snap *db.Snapshot, dbUpdateChan chan<- dbUpdateJob, copyChan chan<- copyBlocksState, scanChan chan<- string) (int, map[string]protocol.FileInfo, []protocol.FileInfo, error) {
func (f *sendReceiveFolder) processNeeded(dbUpdateChan chan<- dbUpdateJob, copyChan chan<- copyBlocksState, scanChan chan<- string) (int, map[string]protocol.FileInfo, []protocol.FileInfo, error) {
changed := 0
var dirDeletions []protocol.FileInfo
fileDeletions := map[string]protocol.FileInfo{}
@@ -325,16 +319,20 @@ func (f *sendReceiveFolder) processNeeded(snap *db.Snapshot, dbUpdateChan chan<-
// Regular files to pull goes into the file queue, everything else
// (directories, symlinks and deletes) goes into the "process directly"
// pile.
snap.WithNeed(protocol.LocalDeviceID, func(file protocol.FileInfo) bool {
loop:
for file, err := range itererr.Zip(f.model.sdb.AllNeededGlobalFiles(f.folderID, protocol.LocalDeviceID, f.Order, 0, 0)) {
if err != nil {
return changed, nil, nil, err
}
select {
case <-f.ctx.Done():
return false
break loop
default:
}
if f.IgnoreDelete && file.IsDeleted() {
l.Debugln(f, "ignore file deletion (config)", file.FileName())
return true
continue
}
changed++
@@ -366,9 +364,12 @@ func (f *sendReceiveFolder) processNeeded(snap *db.Snapshot, dbUpdateChan chan<-
// files to delete inside them before we get to that point.
dirDeletions = append(dirDeletions, file)
} else if file.IsSymlink() {
f.deleteFile(file, snap, dbUpdateChan, scanChan)
f.deleteFile(file, dbUpdateChan, scanChan)
} else {
df, ok := snap.Get(protocol.LocalDeviceID, file.Name)
df, ok, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name)
if err != nil {
return changed, nil, nil, err
}
// 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
@@ -384,7 +385,10 @@ func (f *sendReceiveFolder) processNeeded(snap *db.Snapshot, dbUpdateChan chan<-
}
case file.Type == protocol.FileInfoTypeFile:
curFile, hasCurFile := snap.Get(protocol.LocalDeviceID, file.Name)
curFile, hasCurFile, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name)
if err != nil {
return changed, nil, nil, err
}
if hasCurFile && file.BlocksEqual(curFile) {
// We are supposed to copy the entire file, and then fetch nothing. We
// are only updating metadata, so we don't actually *need* to make the
@@ -396,7 +400,7 @@ func (f *sendReceiveFolder) processNeeded(snap *db.Snapshot, dbUpdateChan chan<-
}
case (build.IsWindows || build.IsAndroid) && file.IsSymlink():
if err := f.handleSymlinkCheckExisting(file, snap, scanChan); err != nil {
if err := f.handleSymlinkCheckExisting(file, scanChan); err != nil {
f.newPullError(file.Name, fmt.Errorf("handling unsupported symlink: %w", err))
break
}
@@ -407,22 +411,20 @@ func (f *sendReceiveFolder) processNeeded(snap *db.Snapshot, dbUpdateChan chan<-
case file.IsDirectory() && !file.IsSymlink():
l.Debugln(f, "Handling directory", file.Name)
if f.checkParent(file.Name, scanChan) {
f.handleDir(file, snap, dbUpdateChan, scanChan)
f.handleDir(file, dbUpdateChan, scanChan)
}
case file.IsSymlink():
l.Debugln(f, "Handling symlink", file.Name)
if f.checkParent(file.Name, scanChan) {
f.handleSymlink(file, snap, dbUpdateChan, scanChan)
f.handleSymlink(file, dbUpdateChan, scanChan)
}
default:
l.Warnln(file)
panic("unhandleable item type, can't happen")
}
return true
})
}
select {
case <-f.ctx.Done():
@@ -430,23 +432,6 @@ func (f *sendReceiveFolder) processNeeded(snap *db.Snapshot, dbUpdateChan chan<-
default:
}
// Now do the file queue. Reorder it according to configuration.
switch f.Order {
case config.PullOrderRandom:
f.queue.Shuffle()
case config.PullOrderAlphabetic:
// The queue is already in alphabetic order.
case config.PullOrderSmallestFirst:
f.queue.SortSmallestFirst()
case config.PullOrderLargestFirst:
f.queue.SortLargestFirst()
case config.PullOrderOldestFirst:
f.queue.SortOldestFirst()
case config.PullOrderNewestFirst:
f.queue.SortNewestFirst()
}
// Process the file queue.
nextFile:
@@ -462,7 +447,10 @@ nextFile:
break
}
fi, ok := snap.GetGlobal(fileName)
fi, ok, err := f.model.sdb.GetGlobalFile(f.folderID, fileName)
if err != nil {
return changed, nil, nil, err
}
if !ok {
// File is no longer in the index. Mark it as done and drop it.
f.queue.Done(fileName)
@@ -489,7 +477,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, snap, dbUpdateChan, scanChan); err != nil {
if err := f.renameFile(candidate, desired, fi, dbUpdateChan, scanChan); err != nil {
l.Debugf("rename shortcut for %s failed: %s", fi.Name, err.Error())
// Failed to rename, try next one.
continue
@@ -502,9 +490,11 @@ nextFile:
continue nextFile
}
devices := f.model.fileAvailability(f.FolderConfiguration, snap, fi)
devices := f.model.fileAvailability(f.FolderConfiguration, fi)
if len(devices) > 0 {
f.handleFile(fi, snap, copyChan)
if err := f.handleFile(fi, copyChan); err != nil {
f.newPullError(fileName, err)
}
continue
}
f.newPullError(fileName, errNotAvailable)
@@ -524,7 +514,7 @@ func popCandidate(buckets map[string][]protocol.FileInfo, key string) (protocol.
return cands[0], true
}
func (f *sendReceiveFolder) processDeletions(fileDeletions map[string]protocol.FileInfo, dirDeletions []protocol.FileInfo, snap *db.Snapshot, 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():
@@ -532,7 +522,7 @@ func (f *sendReceiveFolder) processDeletions(fileDeletions map[string]protocol.F
default:
}
f.deleteFile(file, snap, dbUpdateChan, scanChan)
f.deleteFile(file, dbUpdateChan, scanChan)
}
// Process in reverse order to delete depth first
@@ -545,12 +535,12 @@ func (f *sendReceiveFolder) processDeletions(fileDeletions map[string]protocol.F
dir := dirDeletions[len(dirDeletions)-i-1]
l.Debugln(f, "Deleting dir", dir.Name)
f.deleteDir(dir, snap, dbUpdateChan, scanChan)
f.deleteDir(dir, dbUpdateChan, scanChan)
}
}
// handleDir creates or updates the given directory
func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, snap *db.Snapshot, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) {
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
@@ -578,7 +568,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, snap *db.Snapshot,
}
if shouldDebug() {
curFile, _ := snap.Get(protocol.LocalDeviceID, file.Name)
curFile, _, _ := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name)
l.Debugf("need dir\n\t%v\n\t%v", file, curFile)
}
@@ -589,7 +579,11 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, snap *db.Snapshot,
// that don't result in a conflict.
case err == nil && !info.IsDir():
// Check that it is what we have in the database.
curFile, hasCurFile := snap.Get(protocol.LocalDeviceID, file.Name)
curFile, hasCurFile, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name)
if err != nil {
f.newPullError(file.Name, fmt.Errorf("handling dir: %w", err))
return
}
if err := f.scanIfItemChanged(file.Name, info, curFile, hasCurFile, false, scanChan); err != nil {
f.newPullError(file.Name, fmt.Errorf("handling dir: %w", err))
return
@@ -606,7 +600,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, snap *db.Snapshot,
return f.moveForConflict(name, file.ModifiedBy.String(), scanChan)
}, curFile.Name)
} else {
err = f.deleteItemOnDisk(curFile, snap, scanChan)
err = f.deleteItemOnDisk(curFile, scanChan)
}
if err != nil {
f.newPullError(file.Name, err)
@@ -715,7 +709,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, snap *db.Snapshot, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) {
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
@@ -738,8 +732,8 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, snap *db.Snaps
}()
if shouldDebug() {
curFile, _ := snap.Get(protocol.LocalDeviceID, file.Name)
l.Debugf("need symlink\n\t%v\n\t%v", file, curFile)
curFile, ok, _ := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name)
l.Debugf("need symlink\n\t%v\n\t%v", file, curFile, ok)
}
if len(file.SymlinkTarget) == 0 {
@@ -749,7 +743,7 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, snap *db.Snaps
return
}
if err = f.handleSymlinkCheckExisting(file, snap, scanChan); err != nil {
if err = f.handleSymlinkCheckExisting(file, scanChan); err != nil {
f.newPullError(file.Name, fmt.Errorf("handling symlink: %w", err))
return
}
@@ -770,7 +764,7 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, snap *db.Snaps
}
}
func (f *sendReceiveFolder) handleSymlinkCheckExisting(file protocol.FileInfo, snap *db.Snapshot, scanChan chan<- string) error {
func (f *sendReceiveFolder) handleSymlinkCheckExisting(file protocol.FileInfo, scanChan chan<- string) error {
// If there is already something under that name, we need to handle that.
info, err := f.mtimefs.Lstat(file.Name)
if err != nil {
@@ -780,7 +774,10 @@ func (f *sendReceiveFolder) handleSymlinkCheckExisting(file protocol.FileInfo, s
return err
}
// Check that it is what we have in the database.
curFile, hasCurFile := snap.Get(protocol.LocalDeviceID, file.Name)
curFile, hasCurFile, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name)
if err != nil {
return err
}
if err := f.scanIfItemChanged(file.Name, info, curFile, hasCurFile, false, scanChan); err != nil {
return err
}
@@ -796,12 +793,12 @@ func (f *sendReceiveFolder) handleSymlinkCheckExisting(file protocol.FileInfo, s
return f.moveForConflict(name, file.ModifiedBy.String(), scanChan)
}, curFile.Name)
} else {
return f.deleteItemOnDisk(curFile, snap, scanChan)
return f.deleteItemOnDisk(curFile, scanChan)
}
}
// deleteDir attempts to remove a directory that was deleted on a remote
func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, snap *db.Snapshot, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) {
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
@@ -826,7 +823,10 @@ func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, snap *db.Snapshot,
})
}()
cur, hasCur := snap.Get(protocol.LocalDeviceID, file.Name)
cur, hasCur, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name)
if err != nil {
return
}
if err = f.checkToBeDeleted(file, cur, hasCur, scanChan); err != nil {
if fs.IsNotExist(err) || fs.IsErrCaseConflict(err) {
@@ -836,7 +836,7 @@ func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, snap *db.Snapshot,
return
}
if err = f.deleteDirOnDisk(file.Name, snap, scanChan); err != nil {
if err = f.deleteDirOnDisk(file.Name, scanChan); err != nil {
return
}
@@ -844,8 +844,12 @@ func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, snap *db.Snapshot,
}
// deleteFile attempts to delete the given file
func (f *sendReceiveFolder) deleteFile(file protocol.FileInfo, snap *db.Snapshot, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) {
cur, hasCur := snap.Get(protocol.LocalDeviceID, file.Name)
func (f *sendReceiveFolder) deleteFile(file protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) {
cur, hasCur, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name)
if err != nil {
f.newPullError(file.Name, fmt.Errorf("delete file: %w", err))
return
}
f.deleteFileWithCurrent(file, cur, hasCur, dbUpdateChan, scanChan)
}
@@ -924,7 +928,7 @@ func (f *sendReceiveFolder) deleteFileWithCurrent(file, cur protocol.FileInfo, h
// 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, snap *db.Snapshot, 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
@@ -966,7 +970,10 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, sn
return err
}
// Check that the target corresponds to what we have in the DB
curTarget, ok := snap.Get(protocol.LocalDeviceID, target.Name)
curTarget, ok, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, target.Name)
if err != nil {
return err
}
switch stat, serr := f.mtimefs.Lstat(target.Name); {
case serr != nil:
var caseErr *fs.ErrCaseConflict
@@ -1040,7 +1047,7 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, sn
// 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(target, curTarget, true, tempName, snap, dbUpdateChan, scanChan); err != nil {
if err = f.performFinish(target, curTarget, true, tempName, dbUpdateChan, scanChan); err != nil {
return err
}
@@ -1085,8 +1092,11 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, sn
// handleFile queues the copies and pulls as necessary for a single new or
// changed file.
func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, snap *db.Snapshot, copyChan chan<- copyBlocksState) {
curFile, hasCurFile := snap.Get(protocol.LocalDeviceID, file.Name)
func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocksState) error {
curFile, hasCurFile, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name)
if err != nil {
return err
}
have, _ := blockDiff(curFile.Blocks, file.Blocks)
@@ -1130,6 +1140,7 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, snap *db.Snapshot
have: len(have),
}
copyChan <- cs
return nil
}
func (f *sendReceiveFolder) reuseBlocks(blocks []protocol.BlockInfo, reused []int, file protocol.FileInfo, tempName string) ([]protocol.BlockInfo, []int) {
@@ -1284,7 +1295,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
// Hope that it's usually in the same folder, so start with that one.
folders := []string{f.folderID}
for folder, cfg := range f.model.cfg.Folders() {
folderFilesystems[folder] = cfg.Filesystem(nil)
folderFilesystems[folder] = cfg.Filesystem()
if folder != f.folderID {
folders = append(folders, folder)
}
@@ -1333,49 +1344,61 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
buf = protocol.BufferPool.Upgrade(buf, int(block.Size))
found := f.model.finder.Iterate(folders, block.Hash, func(folder, path string, index int32) bool {
ffs := folderFilesystems[folder]
fd, err := ffs.Open(path)
found := false
for e, err := range itererr.Zip(f.model.sdb.AllLocalBlocksWithHash(block.Hash)) {
if err != nil {
return false
break
}
defer fd.Close()
srcOffset := int64(state.file.BlockSize()) * int64(index)
_, err = fd.ReadAt(buf, srcOffset)
if err != nil {
return false
}
// Hash is not SHA256 as it's an encrypted hash token. In that
// case we can't verify the block integrity so we'll take it on
// trust. (The other side can and will verify.)
if f.Type != config.FolderTypeReceiveEncrypted {
if err := f.verifyBuffer(buf, block); err != nil {
l.Debugln("Finder failed to verify buffer", err)
return false
it, errFn := f.model.sdb.AllLocalFilesWithBlocksHashAnyFolder(e.BlocklistHash)
for folderID, fi := range it {
ffs := folderFilesystems[folderID]
fd, err := ffs.Open(fi.Name)
if err != nil {
continue
}
}
defer fd.Close()
if f.CopyRangeMethod != config.CopyRangeMethodStandard {
err = f.withLimiter(func() error {
dstFd.mut.Lock()
defer dstFd.mut.Unlock()
return fs.CopyRange(f.CopyRangeMethod.ToFS(), fd, dstFd.fd, srcOffset, block.Offset, int64(block.Size))
})
} else {
err = f.limitedWriteAt(dstFd, buf, block.Offset)
_, err = fd.ReadAt(buf, e.Offset)
if err != nil {
fd.Close()
continue
}
// Hash is not SHA256 as it's an encrypted hash token. In that
// case we can't verify the block integrity so we'll take it on
// trust. (The other side can and will verify.)
if f.Type != config.FolderTypeReceiveEncrypted {
if err := f.verifyBuffer(buf, block); err != nil {
l.Debugln("Finder failed to verify buffer", err)
continue
}
}
if f.CopyRangeMethod != config.CopyRangeMethodStandard {
err = f.withLimiter(func() error {
dstFd.mut.Lock()
defer dstFd.mut.Unlock()
return fs.CopyRange(f.CopyRangeMethod.ToFS(), fd, dstFd.fd, e.Offset, block.Offset, int64(block.Size))
})
} else {
err = f.limitedWriteAt(dstFd, buf, block.Offset)
}
if err != nil {
state.fail(fmt.Errorf("dst write: %w", err))
break
}
if fi.Name == state.file.Name {
state.copiedFromOrigin(block.Size)
} else {
state.copiedFromElsewhere(block.Size)
}
found = true
break
}
if err != nil {
state.fail(fmt.Errorf("dst write: %w", err))
if err := errFn(); err != nil {
l.Warnln(err)
}
if path == state.file.Name {
state.copiedFromOrigin(block.Size)
} else {
state.copiedFromElsewhere(block.Size)
}
return true
})
}
if state.failed() != nil {
break
@@ -1410,7 +1433,7 @@ func (*sendReceiveFolder) verifyBuffer(buf []byte, block protocol.BlockInfo) err
return nil
}
func (f *sendReceiveFolder) pullerRoutine(snap *db.Snapshot, in <-chan pullBlockState, out chan<- *sharedPullerState) {
func (f *sendReceiveFolder) pullerRoutine(in <-chan pullBlockState, out chan<- *sharedPullerState) {
requestLimiter := semaphore.New(f.PullerMaxPendingKiB * 1024)
wg := sync.NewWaitGroup()
@@ -1441,13 +1464,13 @@ func (f *sendReceiveFolder) pullerRoutine(snap *db.Snapshot, in <-chan pullBlock
defer wg.Done()
defer requestLimiter.Give(bytes)
f.pullBlock(state, snap, out)
f.pullBlock(state, out)
}()
}
wg.Wait()
}
func (f *sendReceiveFolder) pullBlock(state pullBlockState, snap *db.Snapshot, out chan<- *sharedPullerState) {
func (f *sendReceiveFolder) pullBlock(state pullBlockState, out chan<- *sharedPullerState) {
// Get an fd to the temporary file. Technically we don't need it until
// after fetching the block, but if we run into an error here there is
// no point in issuing the request to the network.
@@ -1466,7 +1489,7 @@ func (f *sendReceiveFolder) pullBlock(state pullBlockState, snap *db.Snapshot, o
}
var lastError error
candidates := f.model.blockAvailability(f.FolderConfiguration, snap, state.file, state.block)
candidates := f.model.blockAvailability(f.FolderConfiguration, state.file, state.block)
loop:
for {
select {
@@ -1531,7 +1554,7 @@ loop:
out <- state.sharedPullerState
}
func (f *sendReceiveFolder) performFinish(file, curFile protocol.FileInfo, hasCurFile bool, tempName string, snap *db.Snapshot, 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.mtimefs.Chmod(tempName, fs.FileMode(file.Permissions&0o777)); err != nil {
@@ -1562,7 +1585,7 @@ func (f *sendReceiveFolder) performFinish(file, curFile protocol.FileInfo, hasCu
return f.moveForConflict(name, file.ModifiedBy.String(), scanChan)
}, curFile.Name)
} else {
err = f.deleteItemOnDisk(curFile, snap, scanChan)
err = f.deleteItemOnDisk(curFile, scanChan)
}
if err != nil {
return fmt.Errorf("moving for conflict: %w", err)
@@ -1585,7 +1608,7 @@ func (f *sendReceiveFolder) performFinish(file, curFile protocol.FileInfo, hasCu
return nil
}
func (f *sendReceiveFolder) finisherRoutine(snap *db.Snapshot, 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)
@@ -1593,7 +1616,7 @@ func (f *sendReceiveFolder) finisherRoutine(snap *db.Snapshot, in <-chan *shared
f.queue.Done(state.file.Name)
if err == nil {
err = f.performFinish(state.file, state.curFile, state.hasCurFile, state.tempName, snap, dbUpdateChan, scanChan)
err = f.performFinish(state.file, state.curFile, state.hasCurFile, state.tempName, dbUpdateChan, scanChan)
}
if err != nil {
@@ -1646,7 +1669,7 @@ func (f *sendReceiveFolder) dbUpdaterRoutine(dbUpdateChan <-chan dbUpdateJob) {
var lastFile protocol.FileInfo
tick := time.NewTicker(maxBatchTime)
defer tick.Stop()
batch := db.NewFileInfoBatch(func(files []protocol.FileInfo) error {
batch := NewFileInfoBatch(func(files []protocol.FileInfo) error {
// sync directories
for dir := range changedDirs {
delete(changedDirs, dir)
@@ -1830,7 +1853,7 @@ func (f *sendReceiveFolder) newPullError(path string, err error) {
}
// deleteItemOnDisk deletes the file represented by old that is about to be replaced by new.
func (f *sendReceiveFolder) deleteItemOnDisk(item protocol.FileInfo, snap *db.Snapshot, scanChan chan<- string) (err error) {
func (f *sendReceiveFolder) deleteItemOnDisk(item protocol.FileInfo, scanChan chan<- string) (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("%s: %w", contextRemovingOldItem, err)
@@ -1841,7 +1864,7 @@ func (f *sendReceiveFolder) deleteItemOnDisk(item protocol.FileInfo, snap *db.Sn
case item.IsDirectory():
// Directories aren't archived and need special treatment due
// to potential children.
return f.deleteDirOnDisk(item.Name, snap, scanChan)
return f.deleteDirOnDisk(item.Name, scanChan)
case !item.IsSymlink() && f.versioner != nil:
// If we should use versioning, let the versioner archive the
@@ -1857,12 +1880,12 @@ func (f *sendReceiveFolder) deleteItemOnDisk(item protocol.FileInfo, snap *db.Sn
// 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) deleteDirOnDisk(dir string, snap *db.Snapshot, scanChan chan<- string) error {
func (f *sendReceiveFolder) deleteDirOnDisk(dir string, scanChan chan<- string) error {
if err := osutil.TraversesSymlink(f.mtimefs, filepath.Dir(dir)); err != nil {
return err
}
if err := f.deleteDirOnDiskHandleChildren(dir, snap, scanChan); err != nil {
if err := f.deleteDirOnDiskHandleChildren(dir, scanChan); err != nil {
return err
}
@@ -1882,7 +1905,7 @@ func (f *sendReceiveFolder) deleteDirOnDisk(dir string, snap *db.Snapshot, scanC
return err
}
func (f *sendReceiveFolder) deleteDirOnDiskHandleChildren(dir string, snap *db.Snapshot, scanChan chan<- string) error {
func (f *sendReceiveFolder) deleteDirOnDiskHandleChildren(dir string, scanChan chan<- string) error {
var dirsToDelete []string
var hasIgnored, hasKnown, hasToBeScanned, hasReceiveOnlyChanged bool
var delErr error
@@ -1909,7 +1932,7 @@ func (f *sendReceiveFolder) deleteDirOnDiskHandleChildren(dir string, snap *db.S
hasIgnored = true
return nil
}
cf, ok := snap.Get(protocol.LocalDeviceID, path)
cf, ok, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, path)
switch {
case !ok || cf.IsDeleted():
// Something appeared in the dir that we either are not
+56 -66
View File
@@ -19,6 +19,7 @@ import (
"testing"
"time"
"github.com/syncthing/syncthing/internal/itererr"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
@@ -149,7 +150,7 @@ func TestHandleFile(t *testing.T) {
copyChan := make(chan copyBlocksState, 1)
f.handleFile(requiredFile, fsetSnapshot(t, f.fset), copyChan)
f.handleFile(requiredFile, copyChan)
// Receive the results
toCopy := <-copyChan
@@ -189,13 +190,13 @@ func TestHandleFileWithTemp(t *testing.T) {
_, f, wcfgCancel := setupSendReceiveFolder(t, existingFile)
defer wcfgCancel()
if _, err := prepareTmpFile(f.Filesystem(nil)); err != nil {
if _, err := prepareTmpFile(f.Filesystem()); err != nil {
t.Fatal(err)
}
copyChan := make(chan copyBlocksState, 1)
f.handleFile(requiredFile, fsetSnapshot(t, f.fset), copyChan)
f.handleFile(requiredFile, copyChan)
// Receive the results
toCopy := <-copyChan
@@ -239,7 +240,7 @@ func TestCopierFinder(t *testing.T) {
_, f, wcfgCancel := setupSendReceiveFolder(t, existingFile)
defer wcfgCancel()
if _, err := prepareTmpFile(f.Filesystem(nil)); err != nil {
if _, err := prepareTmpFile(f.Filesystem()); err != nil {
t.Fatal(err)
}
@@ -251,7 +252,7 @@ func TestCopierFinder(t *testing.T) {
go f.copierRoutine(copyChan, pullChan, finisherChan)
defer close(copyChan)
f.handleFile(requiredFile, fsetSnapshot(t, f.fset), copyChan)
f.handleFile(requiredFile, copyChan)
timeout := time.After(10 * time.Second)
pulls := make([]pullBlockState, 4)
@@ -272,8 +273,9 @@ func TestCopierFinder(t *testing.T) {
defer cleanupSharedPullerState(finish)
select {
case <-pullChan:
t.Fatal("Pull channel has data to be read")
case v := <-pullChan:
t.Logf("%+v\n", v)
t.Fatal("Pull channel had data to be read")
case <-finisherChan:
t.Fatal("Finisher channel has data to be read")
default:
@@ -299,7 +301,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(), f.ID, f.Filesystem(nil), tempFile, protocol.MinBlockSize, nil)
blks, err := scanner.HashFile(context.TODO(), f.ID, f.Filesystem(), tempFile, protocol.MinBlockSize, nil)
if err != nil {
t.Log(err)
}
@@ -313,10 +315,6 @@ func TestCopierFinder(t *testing.T) {
// Test that updating a file removes its old blocks from the blockmap
func TestCopierCleanup(t *testing.T) {
iterFn := func(folder, file string, index int32) bool {
return true
}
// Create a file
file := setupFile("test", []int{0})
file.Size = 1
@@ -328,11 +326,11 @@ func TestCopierCleanup(t *testing.T) {
// Update index (removing old blocks)
f.updateLocalsFromScanning([]protocol.FileInfo{file})
if m.finder.Iterate(folders, blocks[0].Hash, iterFn) {
if vals, err := itererr.Collect(m.sdb.AllLocalBlocksWithHash(blocks[0].Hash)); err != nil || len(vals) > 0 {
t.Error("Unexpected block found")
}
if !m.finder.Iterate(folders, blocks[1].Hash, iterFn) {
if vals, err := itererr.Collect(m.sdb.AllLocalBlocksWithHash(blocks[1].Hash)); err != nil || len(vals) == 0 {
t.Error("Expected block not found")
}
@@ -341,11 +339,11 @@ func TestCopierCleanup(t *testing.T) {
// Update index (removing old blocks)
f.updateLocalsFromScanning([]protocol.FileInfo{file})
if !m.finder.Iterate(folders, blocks[0].Hash, iterFn) {
if vals, err := itererr.Collect(m.sdb.AllLocalBlocksWithHash(blocks[0].Hash)); err != nil || len(vals) == 0 {
t.Error("Unexpected block found")
}
if m.finder.Iterate(folders, blocks[1].Hash, iterFn) {
if vals, err := itererr.Collect(m.sdb.AllLocalBlocksWithHash(blocks[1].Hash)); err != nil || len(vals) > 0 {
t.Error("Expected block not found")
}
}
@@ -371,10 +369,9 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
finisherBufferChan := make(chan *sharedPullerState, 1)
finisherChan := make(chan *sharedPullerState)
dbUpdateChan := make(chan dbUpdateJob, 1)
snap := fsetSnapshot(t, f.fset)
copyChan, copyWg := startCopier(f, pullChan, finisherBufferChan)
go f.finisherRoutine(snap, finisherChan, dbUpdateChan, make(chan string))
go f.finisherRoutine(finisherChan, dbUpdateChan, make(chan string))
defer func() {
close(copyChan)
@@ -384,7 +381,7 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
close(finisherChan)
}()
f.handleFile(file, snap, copyChan)
f.handleFile(file, copyChan)
// Receive a block at puller, to indicate that at least a single copier
// loop has been performed.
@@ -471,16 +468,15 @@ func TestDeregisterOnFailInPull(t *testing.T) {
finisherBufferChan := make(chan *sharedPullerState)
finisherChan := make(chan *sharedPullerState)
dbUpdateChan := make(chan dbUpdateJob, 1)
snap := fsetSnapshot(t, f.fset)
copyChan, copyWg := startCopier(f, pullChan, finisherBufferChan)
pullWg := sync.NewWaitGroup()
pullWg.Add(1)
go func() {
f.pullerRoutine(snap, pullChan, finisherBufferChan)
f.pullerRoutine(pullChan, finisherBufferChan)
pullWg.Done()
}()
go f.finisherRoutine(snap, finisherChan, dbUpdateChan, make(chan string))
go f.finisherRoutine(finisherChan, dbUpdateChan, make(chan string))
defer func() {
// Unblock copier and puller
go func() {
@@ -495,7 +491,7 @@ func TestDeregisterOnFailInPull(t *testing.T) {
close(finisherChan)
}()
f.handleFile(file, snap, copyChan)
f.handleFile(file, copyChan)
// Receive at finisher, we should error out as puller has nowhere to pull
// from.
@@ -558,7 +554,7 @@ func TestDeregisterOnFailInPull(t *testing.T) {
func TestIssue3164(t *testing.T) {
_, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
ffs := f.Filesystem(nil)
ffs := f.Filesystem()
ignDir := filepath.Join("issue3164", "oktodelete")
subDir := filepath.Join(ignDir, "foobar")
@@ -577,7 +573,7 @@ func TestIssue3164(t *testing.T) {
dbUpdateChan := make(chan dbUpdateJob, 1)
f.deleteDir(file, fsetSnapshot(t, f.fset), dbUpdateChan, make(chan string))
f.deleteDir(file, dbUpdateChan, make(chan string))
if _, err := ffs.Stat("issue3164"); !fs.IsNotExist(err) {
t.Fatal(err)
@@ -648,7 +644,7 @@ func TestDiffEmpty(t *testing.T) {
func TestDeleteIgnorePerms(t *testing.T) {
_, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
ffs := f.Filesystem(nil)
ffs := f.Filesystem()
f.IgnorePerms = true
name := "deleteIgnorePerms"
@@ -692,9 +688,6 @@ func TestCopyOwner(t *testing.T) {
f.folder.FolderConfiguration = newFolderConfiguration(m.cfg, f.ID, f.Label, config.FilesystemTypeFake, "/TestCopyOwner")
f.folder.FolderConfiguration.CopyOwnershipFromParent = true
f.fset = newFileSet(t, f.ID, m.db)
f.mtimefs = f.Filesystem(f.fset)
// Create a parent dir with a certain owner/group.
f.mtimefs.Mkdir("foo", 0o755)
@@ -712,7 +705,7 @@ func TestCopyOwner(t *testing.T) {
dbUpdateChan := make(chan dbUpdateJob, 1)
scanChan := make(chan string)
defer close(dbUpdateChan)
f.handleDir(dir, fsetSnapshot(t, f.fset), dbUpdateChan, scanChan)
f.handleDir(dir, dbUpdateChan, scanChan)
select {
case <-dbUpdateChan: // empty the channel for later
case toScan := <-scanChan:
@@ -742,17 +735,16 @@ func TestCopyOwner(t *testing.T) {
// but it's the way data is passed around. When the database update
// comes the finisher is done.
snap := fsetSnapshot(t, f.fset)
finisherChan := make(chan *sharedPullerState)
copierChan, copyWg := startCopier(f, nil, finisherChan)
go f.finisherRoutine(snap, finisherChan, dbUpdateChan, nil)
go f.finisherRoutine(finisherChan, dbUpdateChan, nil)
defer func() {
close(copierChan)
copyWg.Wait()
close(finisherChan)
}()
f.handleFile(file, snap, copierChan)
f.handleFile(file, copierChan)
<-dbUpdateChan
info, err = f.mtimefs.Lstat("foo/bar/baz")
@@ -771,7 +763,7 @@ func TestCopyOwner(t *testing.T) {
SymlinkTarget: []byte("over the rainbow"),
}
f.handleSymlink(symlink, snap, dbUpdateChan, scanChan)
f.handleSymlink(symlink, dbUpdateChan, scanChan)
select {
case <-dbUpdateChan:
case toScan := <-scanChan:
@@ -792,7 +784,7 @@ func TestCopyOwner(t *testing.T) {
func TestSRConflictReplaceFileByDir(t *testing.T) {
_, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
ffs := f.Filesystem(nil)
ffs := f.Filesystem()
name := "foo"
@@ -810,7 +802,7 @@ func TestSRConflictReplaceFileByDir(t *testing.T) {
dbUpdateChan := make(chan dbUpdateJob, 1)
scanChan := make(chan string, 1)
f.handleDir(file, fsetSnapshot(t, f.fset), dbUpdateChan, scanChan)
f.handleDir(file, dbUpdateChan, scanChan)
if confls := existingConflicts(name, ffs); len(confls) != 1 {
t.Fatal("Expected one conflict, got", len(confls))
@@ -824,7 +816,7 @@ func TestSRConflictReplaceFileByDir(t *testing.T) {
func TestSRConflictReplaceFileByLink(t *testing.T) {
_, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
ffs := f.Filesystem(nil)
ffs := f.Filesystem()
name := "foo"
@@ -843,7 +835,7 @@ func TestSRConflictReplaceFileByLink(t *testing.T) {
dbUpdateChan := make(chan dbUpdateJob, 1)
scanChan := make(chan string, 1)
f.handleSymlink(file, fsetSnapshot(t, f.fset), dbUpdateChan, scanChan)
f.handleSymlink(file, dbUpdateChan, scanChan)
if confls := existingConflicts(name, ffs); len(confls) != 1 {
t.Fatal("Expected one conflict, got", len(confls))
@@ -857,7 +849,7 @@ func TestSRConflictReplaceFileByLink(t *testing.T) {
func TestDeleteBehindSymlink(t *testing.T) {
_, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
ffs := f.Filesystem(nil)
ffs := f.Filesystem()
link := "link"
linkFile := filepath.Join(link, "file")
@@ -873,7 +865,7 @@ func TestDeleteBehindSymlink(t *testing.T) {
fi.Version = fi.Version.Update(device1.Short())
scanChan := make(chan string, 1)
dbUpdateChan := make(chan dbUpdateJob, 1)
f.deleteFile(fi, fsetSnapshot(t, f.fset), dbUpdateChan, scanChan)
f.deleteFile(fi, dbUpdateChan, scanChan)
select {
case f := <-scanChan:
t.Fatalf("Received %v on scanChan", f)
@@ -903,7 +895,7 @@ func TestPullCtxCancel(t *testing.T) {
var cancel context.CancelFunc
f.ctx, cancel = context.WithCancel(context.Background())
go f.pullerRoutine(fsetSnapshot(t, f.fset), pullChan, finisherChan)
go f.pullerRoutine(pullChan, finisherChan)
defer close(pullChan)
emptyState := func() pullBlockState {
@@ -938,7 +930,7 @@ func TestPullCtxCancel(t *testing.T) {
func TestPullDeleteUnscannedDir(t *testing.T) {
_, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
ffs := f.Filesystem(nil)
ffs := f.Filesystem()
dir := "foobar"
must(t, ffs.MkdirAll(dir, 0o777))
@@ -949,7 +941,7 @@ func TestPullDeleteUnscannedDir(t *testing.T) {
scanChan := make(chan string, 1)
dbUpdateChan := make(chan dbUpdateJob, 1)
f.deleteDir(fi, fsetSnapshot(t, f.fset), dbUpdateChan, scanChan)
f.deleteDir(fi, dbUpdateChan, scanChan)
if _, err := ffs.Stat(dir); fs.IsNotExist(err) {
t.Error("directory has been deleted")
@@ -967,7 +959,7 @@ func TestPullDeleteUnscannedDir(t *testing.T) {
func TestPullCaseOnlyPerformFinish(t *testing.T) {
m, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
ffs := f.Filesystem(nil)
ffs := f.Filesystem()
name := "foo"
contents := []byte("contents")
@@ -976,16 +968,17 @@ func TestPullCaseOnlyPerformFinish(t *testing.T) {
var cur protocol.FileInfo
hasCur := false
snap := dbSnapshot(t, m, f.ID)
defer snap.Release()
snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileInfo) bool {
it, errFn := m.LocalFiles(f.ID, protocol.LocalDeviceID)
for i := range it {
if hasCur {
t.Fatal("got more than one file")
}
cur = i
hasCur = true
return true
})
}
if err := errFn(); err != nil {
t.Fatal(err)
}
if !hasCur {
t.Fatal("file is missing")
}
@@ -999,7 +992,7 @@ func TestPullCaseOnlyPerformFinish(t *testing.T) {
scanChan := make(chan string, 1)
dbUpdateChan := make(chan dbUpdateJob, 1)
err := f.performFinish(remote, cur, hasCur, temp, snap, dbUpdateChan, scanChan)
err := f.performFinish(remote, cur, hasCur, temp, dbUpdateChan, scanChan)
select {
case <-dbUpdateChan: // boring case sensitive filesystem
@@ -1029,7 +1022,7 @@ func TestPullCaseOnlySymlink(t *testing.T) {
func testPullCaseOnlyDirOrSymlink(t *testing.T, dir bool) {
m, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
ffs := f.Filesystem(nil)
ffs := f.Filesystem()
name := "foo"
if dir {
@@ -1041,16 +1034,17 @@ func testPullCaseOnlyDirOrSymlink(t *testing.T, dir bool) {
must(t, f.scanSubdirs(nil))
var cur protocol.FileInfo
hasCur := false
snap := dbSnapshot(t, m, f.ID)
defer snap.Release()
snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileInfo) bool {
it, errFn := m.LocalFiles(f.ID, protocol.LocalDeviceID)
for i := range it {
if hasCur {
t.Fatal("got more than one file")
}
cur = i
hasCur = true
return true
})
}
if err := errFn(); err != nil {
t.Fatal(err)
}
if !hasCur {
t.Fatal("file is missing")
}
@@ -1063,9 +1057,9 @@ func testPullCaseOnlyDirOrSymlink(t *testing.T, dir bool) {
remote.Name = strings.ToUpper(cur.Name)
if dir {
f.handleDir(remote, snap, dbUpdateChan, scanChan)
f.handleDir(remote, dbUpdateChan, scanChan)
} else {
f.handleSymlink(remote, snap, dbUpdateChan, scanChan)
f.handleSymlink(remote, dbUpdateChan, scanChan)
}
select {
@@ -1100,7 +1094,7 @@ func TestPullTempFileCaseConflict(t *testing.T) {
fd.Close()
}
f.handleFile(file, fsetSnapshot(t, f.fset), copyChan)
f.handleFile(file, copyChan)
cs := <-copyChan
if _, err := cs.tempFile(); err != nil {
@@ -1142,9 +1136,7 @@ func TestPullCaseOnlyRename(t *testing.T) {
dbUpdateChan := make(chan dbUpdateJob, 2)
scanChan := make(chan string, 2)
snap := fsetSnapshot(t, f.fset)
defer snap.Release()
if err := f.renameFile(cur, deleted, confl, snap, dbUpdateChan, scanChan); err != nil {
if err := f.renameFile(cur, deleted, confl, dbUpdateChan, scanChan); err != nil {
t.Error(err)
}
}
@@ -1219,9 +1211,7 @@ func TestPullDeleteCaseConflict(t *testing.T) {
t.Error("Missing db update for file")
}
snap := fsetSnapshot(t, f.fset)
defer snap.Release()
f.deleteDir(fi, snap, dbUpdateChan, scanChan)
f.deleteDir(fi, dbUpdateChan, scanChan)
select {
case <-dbUpdateChan:
default:
@@ -1249,7 +1239,7 @@ func TestPullDeleteIgnoreChildDir(t *testing.T) {
scanChan := make(chan string, 2)
err := f.deleteDirOnDisk(parent, fsetSnapshot(t, f.fset), scanChan)
err := f.deleteDirOnDisk(parent, scanChan)
if err == nil {
t.Error("no error")
}
+7 -11
View File
@@ -17,8 +17,8 @@ import (
"github.com/thejerf/suture/v4"
"github.com/syncthing/syncthing/internal/db"
"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/svcutil"
@@ -127,16 +127,12 @@ func (c *folderSummaryService) Summary(folder string) (*FolderSummary, error) {
var remoteSeq map[protocol.DeviceID]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(protocol.LocalDeviceID)
ro = snap.ReceiveOnlyChangedSize()
ourSeq = snap.Sequence(protocol.LocalDeviceID)
remoteSeq = snap.RemoteSequences()
snap.Release()
}
global, _ = c.model.GlobalSize(folder)
local, _ = c.model.LocalSize(folder, protocol.LocalDeviceID)
need, _ = c.model.NeedSize(folder, protocol.LocalDeviceID)
ro, _ = c.model.ReceiveOnlySize(folder)
ourSeq, _ = c.model.Sequence(folder, protocol.LocalDeviceID)
remoteSeq, _ = c.model.RemoteSequences(folder)
}
// For API backwards compatibility (SyncTrayzor needs it) an empty folder
// summary is returned for not running folders, an error might actually be
+83 -77
View File
@@ -8,12 +8,14 @@ package model
import (
"context"
"errors"
"fmt"
"sync"
"time"
"github.com/syncthing/syncthing/internal/db"
"github.com/syncthing/syncthing/internal/itererr"
"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/svcutil"
@@ -45,13 +47,19 @@ type indexHandler struct {
cond *sync.Cond
paused bool
fset *db.FileSet
sdb db.DB
runner service
}
func newIndexHandler(conn protocol.Connection, downloads *deviceDownloadState, folder config.FolderConfiguration, fset *db.FileSet, runner service, startInfo *clusterConfigDeviceInfo, evLogger events.Logger) *indexHandler {
myIndexID := fset.IndexID(protocol.LocalDeviceID)
mySequence := fset.Sequence(protocol.LocalDeviceID)
func newIndexHandler(conn protocol.Connection, downloads *deviceDownloadState, folder config.FolderConfiguration, sdb db.DB, runner service, startInfo *clusterConfigDeviceInfo, evLogger events.Logger) (*indexHandler, error) {
myIndexID, err := sdb.GetIndexID(folder.ID, protocol.LocalDeviceID)
if err != nil {
return nil, err
}
mySequence, err := sdb.GetDeviceSequence(folder.ID, protocol.LocalDeviceID)
if err != nil {
return nil, err
}
var startSequence int64
// This is the other side's description of what it knows
@@ -91,14 +99,14 @@ func newIndexHandler(conn protocol.Connection, downloads *deviceDownloadState, f
// otherwise we drop our old index data and expect to get a
// completely new set.
theirIndexID := fset.IndexID(conn.DeviceID())
theirIndexID, _ := sdb.GetIndexID(folder.ID, conn.DeviceID())
if startInfo.remote.IndexID == 0 {
// They're not announcing an index ID. This means they
// do not support delta indexes and we should clear any
// information we have from them before accepting their
// index, which will presumably be a full index.
l.Debugf("Device %v folder %s does not announce an index ID", conn.DeviceID().Short(), folder.Description())
fset.Drop(conn.DeviceID())
sdb.DropAllFiles(folder.ID, conn.DeviceID())
} else if startInfo.remote.IndexID != theirIndexID {
// The index ID we have on file is not what they're
// announcing. They must have reset their database and
@@ -106,8 +114,8 @@ func newIndexHandler(conn protocol.Connection, downloads *deviceDownloadState, f
// information we have and remember this new index ID
// instead.
l.Infof("Device %v folder %s has a new index ID (%v)", conn.DeviceID().Short(), folder.Description(), startInfo.remote.IndexID)
fset.Drop(conn.DeviceID())
fset.SetIndexID(conn.DeviceID(), startInfo.remote.IndexID)
sdb.DropAllFiles(folder.ID, conn.DeviceID())
sdb.SetIndexID(folder.ID, conn.DeviceID(), startInfo.remote.IndexID)
}
return &indexHandler{
@@ -119,27 +127,27 @@ func newIndexHandler(conn protocol.Connection, downloads *deviceDownloadState, f
sentPrevSequence: startSequence,
evLogger: evLogger,
fset: fset,
sdb: sdb,
runner: runner,
cond: sync.NewCond(new(sync.Mutex)),
}
}, nil
}
// waitForFileset waits for the handler to resume and fetches the current fileset.
func (s *indexHandler) waitForFileset(ctx context.Context) (*db.FileSet, error) {
// waitWhilePaused waits for the handler to resume
func (s *indexHandler) waitWhilePaused(ctx context.Context) error {
s.cond.L.Lock()
defer s.cond.L.Unlock()
for s.paused {
select {
case <-ctx.Done():
return nil, ctx.Err()
return ctx.Err()
default:
s.cond.Wait()
}
}
return s.fset, nil
return nil
}
func (s *indexHandler) Serve(ctx context.Context) (err error) {
@@ -162,11 +170,10 @@ func (s *indexHandler) Serve(ctx context.Context) (err error) {
}()
// We need to send one index, regardless of whether there is something to send or not
fset, err := s.waitForFileset(ctx)
if err != nil {
if err := s.waitWhilePaused(ctx); err != nil {
return err
}
err = s.sendIndexTo(ctx, fset)
err = s.sendIndexTo(ctx)
// Subscribe to LocalIndexUpdated (we have new information to send) and
// DeviceDisconnected (it might be us who disconnected, so we should
@@ -179,8 +186,7 @@ func (s *indexHandler) Serve(ctx context.Context) (err error) {
defer ticker.Stop()
for err == nil {
fset, err = s.waitForFileset(ctx)
if err != nil {
if err := s.waitWhilePaused(ctx); err != nil {
return err
}
@@ -188,7 +194,11 @@ func (s *indexHandler) Serve(ctx context.Context) (err error) {
// currently in the database, wait for the local index to update. The
// local index may update for other folders than the one we are
// sending for.
if fset.Sequence(protocol.LocalDeviceID) <= s.localPrevSequence {
seq, err := s.sdb.GetDeviceSequence(s.folder, protocol.LocalDeviceID)
if err != nil {
return err
}
if seq <= s.localPrevSequence {
select {
case <-ctx.Done():
return ctx.Err()
@@ -198,7 +208,7 @@ func (s *indexHandler) Serve(ctx context.Context) (err error) {
continue
}
err = s.sendIndexTo(ctx, fset)
err = s.sendIndexTo(ctx)
// Wait a short amount of time before entering the next loop. If there
// are continuous changes happening to the local index, this gives us
@@ -215,10 +225,9 @@ func (s *indexHandler) Serve(ctx context.Context) (err error) {
// resume might be called because the folder was actually resumed, or just
// because the folder config changed (and thus the runner and potentially fset).
func (s *indexHandler) resume(fset *db.FileSet, runner service) {
func (s *indexHandler) resume(runner service) {
s.cond.L.Lock()
s.paused = false
s.fset = fset
s.runner = runner
s.cond.Broadcast()
s.cond.L.Unlock()
@@ -230,7 +239,6 @@ func (s *indexHandler) pause() {
s.evLogger.Log(events.Failure, "index handler got paused while already paused")
}
s.paused = true
s.fset = nil
s.runner = nil
s.cond.Broadcast()
s.cond.L.Unlock()
@@ -238,9 +246,9 @@ func (s *indexHandler) pause() {
// sendIndexTo sends file infos with a sequence number higher than prevSequence and
// returns the highest sent sequence number.
func (s *indexHandler) sendIndexTo(ctx context.Context, fset *db.FileSet) error {
func (s *indexHandler) sendIndexTo(ctx context.Context) error {
initial := s.localPrevSequence == 0
batch := db.NewFileInfoBatch(nil)
batch := NewFileInfoBatch(nil)
var batchError error
batch.SetFlushFunc(func(fs []protocol.FileInfo) error {
select {
@@ -284,21 +292,26 @@ func (s *indexHandler) sendIndexTo(ctx context.Context, fset *db.FileSet) error
return nil
})
var err error
var f protocol.FileInfo
snap, err := fset.Snapshot()
if err != nil {
return svcutil.AsFatalErr(err, svcutil.ExitError)
}
defer snap.Release()
previousWasDelete := false
snap.WithHaveSequence(s.localPrevSequence+1, func(fi protocol.FileInfo) bool {
t0 := time.Now()
for fi, err := range itererr.Zip(s.sdb.AllLocalFilesBySequence(s.folder, protocol.LocalDeviceID, s.localPrevSequence+1, 5000)) {
if err != nil {
return err
}
// This is to make sure that renames (which is an add followed by a delete) land in the same batch.
// Even if the batch is full, we allow a last delete to slip in, we do this by making sure that
// the batch ends with a non-delete, or that the last item in the batch is already a delete
if batch.Full() && (!fi.IsDeleted() || previousWasDelete) {
if err = batch.Flush(); err != nil {
return false
if err := batch.Flush(); err != nil {
return err
}
if time.Since(t0) > 5*time.Second {
// minor hack -- avoid very long running read transactions
// during index transmission, to help prevent excessive
// growth of database WAL file
break
}
}
@@ -307,6 +320,7 @@ func (s *indexHandler) sendIndexTo(ctx context.Context, fset *db.FileSet) error
"sequence": fi.SequenceNo(),
"start": s.localPrevSequence + 1,
})
return errors.New("database misbehaved")
}
if f.Sequence > 0 && fi.SequenceNo() <= f.Sequence {
@@ -315,27 +329,17 @@ func (s *indexHandler) sendIndexTo(ctx context.Context, fset *db.FileSet) error
"start": s.localPrevSequence + 1,
"previous": f.Sequence,
})
// Abort this round of index sending - the next one will pick
// up from the last successful one with the repeaired db.
defer func() {
if fixed, dbErr := fset.RepairSequence(); dbErr != nil {
l.Warnln("Failed repairing sequence entries:", dbErr)
panic("Failed repairing sequence entries")
} else {
s.evLogger.Log(events.Failure, "detected and repaired non-increasing sequence")
l.Infof("Repaired %v sequence entries in database", fixed)
}
}()
return false
return errors.New("database misbehaved")
}
f = fi
s.localPrevSequence = f.Sequence
// If this is a folder receiving encrypted files only, we
// mustn't ever send locally changed file infos. Those aren't
// encrypted and thus would be a protocol error at the remote.
if s.folderIsReceiveEncrypted && fi.IsReceiveOnlyChanged() {
return true
continue
}
f = prepareFileInfoForIndex(f)
@@ -343,23 +347,11 @@ func (s *indexHandler) sendIndexTo(ctx context.Context, fset *db.FileSet) error
previousWasDelete = f.IsDeleted()
batch.Append(f)
return true
})
if err != nil {
return err
}
if err := batch.Flush(); err != nil {
return err
}
// Use the sequence of the snapshot we iterated as a starting point for the
// next run. Previously we used the sequence of the last file we sent,
// however it's possible that a higher sequence exists, just doesn't need to
// be sent (e.g. in a receive-only folder, when a local change was
// reverted). No point trying to send nothing again.
s.localPrevSequence = snap.Sequence(protocol.LocalDeviceID)
return nil
}
@@ -368,7 +360,6 @@ func (s *indexHandler) receive(fs []protocol.FileInfo, update bool, op string, p
s.cond.L.Lock()
paused := s.paused
fset := s.fset
runner := s.runner
s.cond.L.Unlock()
@@ -382,13 +373,19 @@ func (s *indexHandler) receive(fs []protocol.FileInfo, update bool, op string, p
s.downloads.Update(s.folder, makeForgetUpdate(fs))
if !update {
fset.Drop(deviceID)
if err := s.sdb.DropAllFiles(s.folder, deviceID); err != nil {
return err
}
}
l.Debugf("Received %d files for %s from %s, prevSeq=%d, lastSeq=%d", len(fs), s.folder, deviceID.Short(), prevSequence, lastSequence)
// Verify that the previous sequence number matches what we expected
if exp := fset.Sequence(deviceID); prevSequence > 0 && prevSequence != exp {
exp, err := s.sdb.GetDeviceSequence(s.folder, deviceID)
if err != nil {
return err
}
if prevSequence > 0 && prevSequence != exp {
s.logSequenceAnomaly("index update with unexpected sequence", map[string]any{
"prevSeq": prevSequence,
"lastSeq": lastSequence,
@@ -444,8 +441,13 @@ func (s *indexHandler) receive(fs []protocol.FileInfo, update bool, op string, p
})
}
fset.Update(deviceID, fs)
seq := fset.Sequence(deviceID)
if err := s.sdb.Update(s.folder, deviceID, fs); err != nil {
return err
}
seq, err := s.sdb.GetDeviceSequence(s.folder, deviceID)
if err != nil {
return err
}
// Check that the sequence we get back is what we put in...
if lastSequence > 0 && len(fs) > 0 && seq != lastSequence {
@@ -508,6 +510,7 @@ func (s *indexHandler) String() string {
type indexHandlerRegistry struct {
evLogger events.Logger
conn protocol.Connection
sdb db.DB
downloads *deviceDownloadState
indexHandlers *serviceMap[string, *indexHandler]
startInfos map[string]*clusterConfigDeviceInfo
@@ -517,14 +520,14 @@ type indexHandlerRegistry struct {
type indexHandlerFolderState struct {
cfg config.FolderConfiguration
fset *db.FileSet
runner service
}
func newIndexHandlerRegistry(conn protocol.Connection, downloads *deviceDownloadState, evLogger events.Logger) *indexHandlerRegistry {
func newIndexHandlerRegistry(conn protocol.Connection, sdb db.DB, downloads *deviceDownloadState, evLogger events.Logger) *indexHandlerRegistry {
r := &indexHandlerRegistry{
evLogger: evLogger,
conn: conn,
sdb: sdb,
downloads: downloads,
indexHandlers: newServiceMap[string, *indexHandler](evLogger),
startInfos: make(map[string]*clusterConfigDeviceInfo),
@@ -544,15 +547,19 @@ func (r *indexHandlerRegistry) Serve(ctx context.Context) error {
return r.indexHandlers.Serve(ctx)
}
func (r *indexHandlerRegistry) startLocked(folder config.FolderConfiguration, fset *db.FileSet, runner service, startInfo *clusterConfigDeviceInfo) {
func (r *indexHandlerRegistry) startLocked(folder config.FolderConfiguration, runner service, startInfo *clusterConfigDeviceInfo) error {
r.indexHandlers.RemoveAndWait(folder.ID, 0)
delete(r.startInfos, folder.ID)
is := newIndexHandler(r.conn, r.downloads, folder, fset, runner, startInfo, r.evLogger)
is, err := newIndexHandler(r.conn, r.downloads, folder, r.sdb, runner, startInfo, r.evLogger)
if err != nil {
return err
}
r.indexHandlers.Add(folder.ID, is)
// This new connection might help us get in sync.
runner.SchedulePull()
return nil
}
// AddIndexInfo starts an index handler for given folder, unless it is paused.
@@ -572,7 +579,7 @@ func (r *indexHandlerRegistry) AddIndexInfo(folder string, startInfo *clusterCon
r.startInfos[folder] = startInfo
return
}
r.startLocked(folderState.cfg, folderState.fset, folderState.runner, startInfo)
_ = r.startLocked(folderState.cfg, folderState.runner, startInfo) // XXX error handling...
}
// Remove stops a running index handler or removes one pending to be started.
@@ -612,7 +619,7 @@ func (r *indexHandlerRegistry) RemoveAllExcept(except map[string]remoteFolderSta
// RegisterFolderState must be called whenever something about the folder
// changes. The exception being if the folder is removed entirely, then call
// Remove. The fset and runner arguments may be nil, if given folder is paused.
func (r *indexHandlerRegistry) RegisterFolderState(folder config.FolderConfiguration, fset *db.FileSet, runner service) {
func (r *indexHandlerRegistry) RegisterFolderState(folder config.FolderConfiguration, runner service) {
if !folder.SharedWith(r.conn.DeviceID()) {
r.Remove(folder.ID)
return
@@ -622,7 +629,7 @@ func (r *indexHandlerRegistry) RegisterFolderState(folder config.FolderConfigura
if folder.Paused {
r.folderPausedLocked(folder.ID)
} else {
r.folderRunningLocked(folder, fset, runner)
r.folderRunningLocked(folder, runner)
}
r.mut.Unlock()
}
@@ -643,10 +650,9 @@ func (r *indexHandlerRegistry) folderPausedLocked(folder string) {
// folderRunningLocked resumes an already running index handler or starts it, if it
// was added while paused.
// It is a noop if the folder isn't known.
func (r *indexHandlerRegistry) folderRunningLocked(folder config.FolderConfiguration, fset *db.FileSet, runner service) {
func (r *indexHandlerRegistry) folderRunningLocked(folder config.FolderConfiguration, runner service) {
r.folderStates[folder.ID] = &indexHandlerFolderState{
cfg: folder,
fset: fset,
runner: runner,
}
@@ -656,12 +662,12 @@ func (r *indexHandlerRegistry) folderRunningLocked(folder config.FolderConfigura
r.indexHandlers.RemoveAndWait(folder.ID, 0)
l.Debugf("Removed index handler for device %v and folder %v in resume", r.conn.DeviceID().Short(), folder.ID)
}
r.startLocked(folder, fset, runner, info)
_ = r.startLocked(folder, runner, info) // XXX error handling...
delete(r.startInfos, folder.ID)
l.Debugf("Started index handler for device %v and folder %v in resume", r.conn.DeviceID().Short(), folder.ID)
} else if isOk {
l.Debugf("Resuming index handler for device %v and folder %v", r.conn.DeviceID().Short(), folder)
is.resume(fset, runner)
is.resume(runner)
} else {
l.Debugf("Not resuming index handler for device %v and folder %v as none is paused and there is no start info", r.conn.DeviceID().Short(), folder.ID)
}
+2 -2
View File
@@ -13,7 +13,7 @@ import (
"sync"
"testing"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/model/mocks"
"github.com/syncthing/syncthing/lib/protocol"
protomock "github.com/syncthing/syncthing/lib/protocol/mocks"
@@ -63,7 +63,7 @@ func TestIndexhandlerConcurrency(t *testing.T) {
return nil
})
b1 := db.NewFileInfoBatch(func(fs []protocol.FileInfo) error {
b1 := model.NewFileInfoBatch(func(fs []protocol.FileInfo) error {
return c1.IndexUpdate(ctx, &protocol.IndexUpdate{Folder: "foo", Files: fs})
})
sentEntries := 0
+725 -162
View File
File diff suppressed because it is too large Load Diff
+197 -233
View File
@@ -16,11 +16,13 @@ import (
"errors"
"fmt"
"io"
"iter"
"net"
"os"
"path/filepath"
"reflect"
"runtime"
"slices"
"strings"
stdsync "sync"
"sync/atomic"
@@ -28,10 +30,11 @@ import (
"github.com/thejerf/suture/v4"
"github.com/syncthing/syncthing/internal/db"
"github.com/syncthing/syncthing/internal/itererr"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/connections"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/ignore"
@@ -93,7 +96,16 @@ type Model interface {
GetFolderVersions(folder string) (map[string][]versioner.FileVersion, error)
RestoreFolderVersions(folder string, versions map[string]time.Time) (map[string]error, error)
DBSnapshot(folder string) (*db.Snapshot, error)
LocalFiles(folder string, device protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error)
LocalFilesSequenced(folder string, device protocol.DeviceID, startSet int64) (iter.Seq[protocol.FileInfo], func() error)
LocalSize(folder string, device protocol.DeviceID) (db.Counts, error)
GlobalSize(folder string) (db.Counts, error)
NeedSize(folder string, device protocol.DeviceID) (db.Counts, error)
ReceiveOnlySize(folder string) (db.Counts, error)
Sequence(folder string, device protocol.DeviceID) (int64, error)
AllGlobalFiles(folder string) (iter.Seq[db.FileMetadata], func() error)
RemoteSequences(folder string) (map[protocol.DeviceID]int64, error)
NeedFolderFiles(folder string, page, perpage int) ([]protocol.FileInfo, []protocol.FileInfo, []protocol.FileInfo, error)
RemoteNeedFolderFiles(folder string, device protocol.DeviceID, page, perpage int) ([]protocol.FileInfo, error)
LocalChangedFolderFiles(folder string, page, perpage int) ([]protocol.FileInfo, error)
@@ -101,7 +113,6 @@ type Model interface {
CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool, error)
CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool, error)
GetMtimeMapping(folder string, file string) (fs.MtimeMapping, error)
Availability(folder string, file protocol.FileInfo, block protocol.BlockInfo) ([]Availability, error)
Completion(device protocol.DeviceID, folder string) (FolderCompletion, error)
@@ -127,12 +138,11 @@ type model struct {
// constructor parameters
cfg config.Wrapper
id protocol.DeviceID
db *db.Lowlevel
sdb db.DB
protectedFiles []string
evLogger events.Logger
// constant or concurrency safe fields
finder *db.BlockFinder
progressEmitter *ProgressEmitter
shortID protocol.ShortID
// globalRequestLimiter limits the amount of data in concurrent incoming
@@ -145,11 +155,11 @@ type model struct {
started chan struct{}
keyGen *protocol.KeyGenerator
promotionTimer *time.Timer
observed *db.ObservedDB
// fields protected by mut
mut sync.RWMutex
folderCfgs map[string]config.FolderConfiguration // folder -> cfg
folderFiles map[string]*db.FileSet // folder -> files
deviceStatRefs map[protocol.DeviceID]*stats.DeviceStatisticsReference // deviceID -> statsRef
folderIgnores map[string]*ignore.Matcher // folder -> matcher object
folderRunners *serviceMap[string, service] // folder -> puller or scanner
@@ -173,7 +183,7 @@ type model struct {
var _ config.Verifier = &model{}
type folderFactory func(*model, *db.FileSet, *ignore.Matcher, config.FolderConfiguration, versioner.Versioner, events.Logger, *semaphore.Semaphore) service
type folderFactory func(*model, *ignore.Matcher, config.FolderConfiguration, versioner.Versioner, events.Logger, *semaphore.Semaphore) service
var folderFactories = make(map[config.FolderType]folderFactory)
@@ -202,7 +212,7 @@ 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, ldb *db.Lowlevel, protectedFiles []string, evLogger events.Logger, keyGen *protocol.KeyGenerator) Model {
func NewModel(cfg config.Wrapper, id protocol.DeviceID, sdb db.DB, protectedFiles []string, evLogger events.Logger, keyGen *protocol.KeyGenerator) Model {
spec := svcutil.SpecWithDebugLogger(l)
m := &model{
Supervisor: suture.New("model", spec),
@@ -210,12 +220,11 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, ldb *db.Lowlevel, protec
// constructor parameters
cfg: cfg,
id: id,
db: ldb,
sdb: sdb,
protectedFiles: protectedFiles,
evLogger: evLogger,
// constant or concurrency safe fields
finder: db.NewBlockFinder(ldb),
progressEmitter: NewProgressEmitter(cfg, evLogger),
shortID: id.Short(),
globalRequestLimiter: semaphore.New(1024 * cfg.Options().MaxConcurrentIncomingRequestKiB()),
@@ -224,11 +233,11 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, ldb *db.Lowlevel, protec
started: make(chan struct{}),
keyGen: keyGen,
promotionTimer: time.NewTimer(0),
observed: db.NewObservedDB(sdb),
// fields protected by mut
mut: sync.NewRWMutex(),
folderCfgs: make(map[string]config.FolderConfiguration),
folderFiles: make(map[string]*db.FileSet),
deviceStatRefs: make(map[protocol.DeviceID]*stats.DeviceStatisticsReference),
folderIgnores: make(map[string]*ignore.Matcher),
folderRunners: newServiceMap[string, service](evLogger),
@@ -246,7 +255,7 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, ldb *db.Lowlevel, protec
indexHandlers: newServiceMap[protocol.DeviceID, *indexHandlerRegistry](evLogger),
}
for devID, cfg := range cfg.Devices() {
m.deviceStatRefs[devID] = stats.NewDeviceStatisticsReference(m.db, devID)
m.deviceStatRefs[devID] = stats.NewDeviceStatisticsReference(db.NewTyped(sdb, "devicestats/"+devID.String()))
m.setConnRequestLimitersLocked(cfg)
}
m.Add(m.folderRunners)
@@ -327,21 +336,20 @@ func (m *model) fatal(err error) {
}
// Need to hold lock on m.mut when calling this.
func (m *model) addAndStartFolderLocked(cfg config.FolderConfiguration, fset *db.FileSet, cacheIgnoredFiles bool) {
ignores := ignore.New(cfg.Filesystem(nil), ignore.WithCache(cacheIgnoredFiles))
func (m *model) addAndStartFolderLocked(cfg config.FolderConfiguration, cacheIgnoredFiles bool) {
ignores := ignore.New(cfg.Filesystem(), ignore.WithCache(cacheIgnoredFiles))
if cfg.Type != config.FolderTypeReceiveEncrypted {
if err := ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) {
l.Warnln("Loading ignores:", err)
}
}
m.addAndStartFolderLockedWithIgnores(cfg, fset, ignores)
m.addAndStartFolderLockedWithIgnores(cfg, ignores)
}
// Only needed for testing, use addAndStartFolderLocked instead.
func (m *model) addAndStartFolderLockedWithIgnores(cfg config.FolderConfiguration, fset *db.FileSet, ignores *ignore.Matcher) {
func (m *model) addAndStartFolderLockedWithIgnores(cfg config.FolderConfiguration, ignores *ignore.Matcher) {
m.folderCfgs[cfg.ID] = cfg
m.folderFiles[cfg.ID] = fset
m.folderIgnores[cfg.ID] = ignores
_, ok := m.folderRunners.Get(cfg.ID)
@@ -360,16 +368,19 @@ func (m *model) addAndStartFolderLockedWithIgnores(cfg config.FolderConfiguratio
// 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 fset.ListDevices() {
devs, _ := m.sdb.ListDevicesForFolder(cfg.ID)
for _, available := range devs {
if _, ok := expected[available]; !ok {
l.Debugln("dropping", folder, "state for", available)
fset.Drop(available)
m.sdb.DropAllFiles(folder, available)
}
}
v, ok := fset.Sequence(protocol.LocalDeviceID), true
indexHasFiles := ok && v > 0
if !indexHasFiles {
seq, err := m.sdb.GetDeviceSequence(folder, protocol.LocalDeviceID)
if err != nil {
panic(fmt.Errorf("error getting sequence number: %w", err))
}
if seq == 0 {
// It's a blank folder, so this may the first time we're looking at
// it. Attempt to create and tag with our marker as appropriate. We
// don't really do anything with errors at this point except warn -
@@ -392,7 +403,7 @@ func (m *model) addAndStartFolderLockedWithIgnores(cfg config.FolderConfiguratio
}
// These are our metadata files, and they should always be hidden.
ffs := cfg.Filesystem(nil)
ffs := cfg.Filesystem()
_ = ffs.Hide(config.DefaultMarkerName)
_ = ffs.Hide(versioner.DefaultPath)
_ = ffs.Hide(".stignore")
@@ -409,7 +420,7 @@ func (m *model) addAndStartFolderLockedWithIgnores(cfg config.FolderConfiguratio
m.warnAboutOverwritingProtectedFiles(cfg, ignores)
p := folderFactory(m, fset, ignores, cfg, ver, m.evLogger, m.folderIOLimiter)
p := folderFactory(m, ignores, cfg, ver, m.evLogger, m.folderIOLimiter)
m.folderRunners.Add(folder, p)
l.Infof("Ready to synchronize %s (%s)", cfg.Description(), cfg.Type)
@@ -421,7 +432,7 @@ func (m *model) warnAboutOverwritingProtectedFiles(cfg config.FolderConfiguratio
}
// This is a bit of a hack.
ffs := cfg.Filesystem(nil)
ffs := cfg.Filesystem()
if ffs.Type() != fs.FilesystemTypeBasic {
return
}
@@ -468,7 +479,7 @@ func (m *model) removeFolder(cfg config.FolderConfiguration) {
// otherwise not removable) Syncthing-specific marker files.
if err := cfg.RemoveMarker(); err != nil && !errors.Is(err, os.ErrNotExist) {
moved := config.DefaultMarkerName + time.Now().Format(".removed-20060102-150405")
fs := cfg.Filesystem(nil)
fs := cfg.Filesystem()
_ = fs.Rename(config.DefaultMarkerName, moved)
}
}
@@ -482,7 +493,7 @@ func (m *model) removeFolder(cfg config.FolderConfiguration) {
m.mut.Unlock()
// Remove it from the database
db.DropFolder(m.db, cfg.ID)
m.sdb.DropFolder(cfg.ID)
}
// Need to hold lock on m.mut when calling this.
@@ -490,7 +501,6 @@ func (m *model) cleanupFolderLocked(cfg config.FolderConfiguration) {
// clear up our config maps
m.folderRunners.Remove(cfg.ID)
delete(m.folderCfgs, cfg.ID)
delete(m.folderFiles, cfg.ID)
delete(m.folderIgnores, cfg.ID)
delete(m.folderVersioners, cfg.ID)
delete(m.folderEncryptionPasswordTokens, cfg.ID)
@@ -525,28 +535,14 @@ func (m *model) restartFolder(from, to config.FolderConfiguration, cacheIgnoredF
m.mut.Lock()
defer m.mut.Unlock()
// Cache the (maybe) existing fset before it's removed by cleanupFolderLocked
fset := m.folderFiles[folder]
fsetNil := fset == nil
m.cleanupFolderLocked(from)
if !to.Paused {
if fsetNil {
// Create a new fset. Might take a while and we do it under
// locking, but it's unsafe to create fset:s concurrently so
// that's the price we pay.
var err error
fset, err = db.NewFileSet(folder, m.db)
if err != nil {
return fmt.Errorf("restarting %v: %w", to.Description(), err)
}
}
m.addAndStartFolderLocked(to, fset, cacheIgnoredFiles)
m.addAndStartFolderLocked(to, cacheIgnoredFiles)
}
runner, _ := m.folderRunners.Get(to.ID)
m.indexHandlers.Each(func(_ protocol.DeviceID, r *indexHandlerRegistry) error {
r.RegisterFolderState(to, fset, runner)
r.RegisterFolderState(to, runner)
return nil
})
@@ -568,22 +564,14 @@ func (m *model) newFolder(cfg config.FolderConfiguration, cacheIgnoredFiles bool
m.mut.Lock()
defer m.mut.Unlock()
// Creating the fileset can take a long time (metadata calculation), but
// nevertheless should happen inside the lock (same as when restarting
// a folder).
fset, err := db.NewFileSet(cfg.ID, m.db)
if err != nil {
return fmt.Errorf("adding %v: %w", cfg.Description(), err)
}
m.addAndStartFolderLocked(cfg, fset, cacheIgnoredFiles)
m.addAndStartFolderLocked(cfg, cacheIgnoredFiles)
// Cluster configs might be received and processed before reaching this
// point, i.e. before the folder is started. If that's the case, start
// index senders here.
m.indexHandlers.Each(func(_ protocol.DeviceID, r *indexHandlerRegistry) error {
runner, _ := m.folderRunners.Get(cfg.ID)
r.RegisterFolderState(cfg, fset, runner)
r.RegisterFolderState(cfg, runner)
return nil
})
@@ -923,46 +911,78 @@ func (m *model) Completion(device protocol.DeviceID, folder string) (FolderCompl
func (m *model) folderCompletion(device protocol.DeviceID, folder string) (FolderCompletion, error) {
m.mut.RLock()
err := m.checkFolderRunningRLocked(folder)
rf := m.folderFiles[folder]
m.mut.RUnlock()
if err != nil {
return FolderCompletion{}, err
}
snap, err := rf.Snapshot()
if err != nil {
return FolderCompletion{}, err
}
defer snap.Release()
m.mut.RLock()
state := m.remoteFolderStates[device][folder]
downloaded := m.deviceDownloads[device].BytesDownloaded(folder)
m.mut.RUnlock()
need := snap.NeedSize(device)
need, err := m.sdb.CountNeed(folder, device)
if err != nil {
return FolderCompletion{}, err
}
need.Bytes -= downloaded
// This might be more than it really is, because some blocks can be of a smaller size.
if need.Bytes < 0 {
need.Bytes = 0
}
comp := newFolderCompletion(snap.GlobalSize(), need, snap.Sequence(device), state)
seq, err := m.sdb.GetDeviceSequence(folder, device)
if err != nil {
return FolderCompletion{}, err
}
glob, err := m.sdb.CountGlobal(folder)
if err != nil {
return FolderCompletion{}, err
}
comp := newFolderCompletion(glob, need, seq, state)
l.Debugf("%v Completion(%s, %q): %v", m, device, folder, comp.Map())
return comp, nil
}
// DBSnapshot returns a snapshot of the database content relevant to the given folder.
func (m *model) DBSnapshot(folder string) (*db.Snapshot, error) {
m.mut.RLock()
err := m.checkFolderRunningRLocked(folder)
rf := m.folderFiles[folder]
m.mut.RUnlock()
if err != nil {
return nil, err
}
return rf.Snapshot()
func (m *model) LocalFiles(folder string, device protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error) {
return m.sdb.AllLocalFiles(folder, device)
}
func (m *model) LocalFilesSequenced(folder string, device protocol.DeviceID, startSeq int64) (iter.Seq[protocol.FileInfo], func() error) {
return m.sdb.AllLocalFilesBySequence(folder, device, startSeq, 0)
}
func (m *model) AllForBlocksHash(folder string, h []byte) (iter.Seq[db.FileMetadata], func() error) {
return m.sdb.AllLocalFilesWithBlocksHash(folder, h)
}
func (m *model) LocalSize(folder string, device protocol.DeviceID) (db.Counts, error) {
return m.sdb.CountLocal(folder, device)
}
func (m *model) GlobalSize(folder string) (db.Counts, error) {
return m.sdb.CountGlobal(folder)
}
func (m *model) NeedSize(folder string, device protocol.DeviceID) (db.Counts, error) {
return m.sdb.CountNeed(folder, device)
}
func (m *model) ReceiveOnlySize(folder string) (db.Counts, error) {
return m.sdb.CountReceiveOnlyChanged(folder)
}
func (m *model) Sequence(folder string, device protocol.DeviceID) (int64, error) {
return m.sdb.GetDeviceSequence(folder, device)
}
func (m *model) AllGlobalFiles(folder string) (iter.Seq[db.FileMetadata], func() error) {
return m.sdb.AllGlobalFiles(folder)
}
func (m *model) RemoteSequences(folder string) (map[protocol.DeviceID]int64, error) {
return m.sdb.RemoteSequences(folder)
}
func (m *model) FolderProgressBytesCompleted(folder string) int64 {
@@ -973,20 +993,14 @@ func (m *model) FolderProgressBytesCompleted(folder string) int64 {
// progress, queued, and to be queued on next puller iteration.
func (m *model) NeedFolderFiles(folder string, page, perpage int) ([]protocol.FileInfo, []protocol.FileInfo, []protocol.FileInfo, error) {
m.mut.RLock()
rf, rfOk := m.folderFiles[folder]
runner, runnerOk := m.folderRunners.Get(folder)
cfg := m.folderCfgs[folder]
cfg, cfgOK := m.folderCfgs[folder]
m.mut.RUnlock()
if !rfOk {
if !cfgOK {
return nil, nil, nil, ErrFolderMissing
}
snap, err := rf.Snapshot()
if err != nil {
return nil, nil, nil, err
}
defer snap.Release()
var progress, queued, rest []protocol.FileInfo
var seen map[string]struct{}
@@ -1000,14 +1014,14 @@ func (m *model) NeedFolderFiles(folder string, page, perpage int) ([]protocol.Fi
seen = make(map[string]struct{}, len(progressNames)+len(queuedNames))
for i, name := range progressNames {
if f, ok := snap.GetGlobalTruncated(name); ok {
if f, ok, err := m.sdb.GetGlobalFile(folder, name); err == nil && ok {
progress[i] = f
seen[name] = struct{}{}
}
}
for i, name := range queuedNames {
if f, ok := snap.GetGlobalTruncated(name); ok {
if f, ok, err := m.sdb.GetGlobalFile(folder, name); err == nil && ok {
queued[i] = f
seen[name] = struct{}{}
}
@@ -1020,21 +1034,29 @@ func (m *model) NeedFolderFiles(folder string, page, perpage int) ([]protocol.Fi
p.toSkip -= skipped
}
rest = make([]protocol.FileInfo, 0, perpage)
snap.WithNeedTruncated(protocol.LocalDeviceID, func(f protocol.FileInfo) bool {
if cfg.IgnoreDelete && f.IsDeleted() {
return true
}
if p.get > 0 {
rest = make([]protocol.FileInfo, 0, p.get)
it, errFn := m.sdb.AllNeededGlobalFiles(folder, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0)
for f := range it {
if cfg.IgnoreDelete && f.IsDeleted() {
continue
}
if p.skip() {
return true
if p.skip() {
continue
}
if _, ok := seen[f.Name]; !ok {
rest = append(rest, f)
p.get--
}
if p.get == 0 {
break
}
}
if _, ok := seen[f.Name]; !ok {
rest = append(rest, f)
p.get--
if err := errFn(); err != nil {
return nil, nil, nil, err
}
return p.get > 0
})
}
return progress, queued, rest, nil
}
@@ -1043,63 +1065,56 @@ func (m *model) NeedFolderFiles(folder string, page, perpage int) ([]protocol.Fi
// remote device to become synced with a folder.
func (m *model) RemoteNeedFolderFiles(folder string, device protocol.DeviceID, page, perpage int) ([]protocol.FileInfo, error) {
m.mut.RLock()
rf, ok := m.folderFiles[folder]
_, ok := m.folderCfgs[folder]
m.mut.RUnlock()
if !ok {
return nil, ErrFolderMissing
}
snap, err := rf.Snapshot()
if err != nil {
return nil, err
}
defer snap.Release()
files := make([]protocol.FileInfo, 0, perpage)
p := newPager(page, perpage)
snap.WithNeedTruncated(device, func(f protocol.FileInfo) bool {
if p.skip() {
return true
}
files = append(files, f)
return !p.done()
})
return files, nil
it, errFn := m.sdb.AllNeededGlobalFiles(folder, device, config.PullOrderAlphabetic, perpage, (page-1)*perpage)
files := slices.Collect(it)
return files, errFn()
}
func (m *model) LocalChangedFolderFiles(folder string, page, perpage int) ([]protocol.FileInfo, error) {
m.mut.RLock()
rf, ok := m.folderFiles[folder]
_, ok := m.folderCfgs[folder]
m.mut.RUnlock()
if !ok {
return nil, ErrFolderMissing
}
snap, err := rf.Snapshot()
ros, err := m.sdb.CountReceiveOnlyChanged(folder)
if err != nil {
return nil, err
}
defer snap.Release()
if snap.ReceiveOnlyChangedSize().TotalItems() == 0 {
if ros.TotalItems() == 0 {
return nil, nil
}
p := newPager(page, perpage)
files := make([]protocol.FileInfo, 0, perpage)
snap.WithHaveTruncated(protocol.LocalDeviceID, func(f protocol.FileInfo) bool {
// This could be made more efficient with a specifically targeted DB
// call
it, errFn := m.sdb.AllLocalFiles(folder, protocol.LocalDeviceID)
for f := range it {
if !f.IsReceiveOnlyChanged() {
return true
continue
}
if p.skip() {
return true
continue
}
files = append(files, f)
return !p.done()
})
if p.done() {
break
}
}
if err := errFn(); err != nil {
return nil, err
}
return files, nil
}
@@ -1343,11 +1358,11 @@ func (m *model) ensureIndexHandler(conn protocol.Connection) *indexHandlerRegist
}
// Create a new index handler for this device.
indexHandlerRegistry = newIndexHandlerRegistry(conn, m.deviceDownloads[deviceID], m.evLogger)
indexHandlerRegistry = newIndexHandlerRegistry(conn, m.sdb, m.deviceDownloads[deviceID], m.evLogger)
for id, fcfg := range m.folderCfgs {
l.Debugln("Registering folder", id, "for", deviceID.Short())
runner, _ := m.folderRunners.Get(id)
indexHandlerRegistry.RegisterFolderState(fcfg, m.folderFiles[id], runner)
indexHandlerRegistry.RegisterFolderState(fcfg, runner)
}
m.indexHandlers.Add(deviceID, indexHandlerRegistry)
@@ -1376,7 +1391,7 @@ func (m *model) ccHandleFolders(folders []protocol.Folder, deviceCfg config.Devi
seenFolders := make(map[string]remoteFolderState, len(folders))
updatedPending := make([]updatedPendingFolder, 0, len(folders))
deviceID := deviceCfg.DeviceID
expiredPending, err := m.db.PendingFoldersForDevice(deviceID)
expiredPending, err := m.observed.PendingFoldersForDevice(deviceID)
if err != nil {
l.Infof("Could not get pending folders for cleanup: %v", err)
}
@@ -1398,7 +1413,7 @@ func (m *model) ccHandleFolders(folders []protocol.Folder, deviceCfg config.Devi
of.Label = folder.Label
of.ReceiveEncrypted = len(ccDeviceInfos[folder.ID].local.EncryptionPasswordToken) > 0
of.RemoteEncrypted = len(ccDeviceInfos[folder.ID].remote.EncryptionPasswordToken) > 0
if err := m.db.AddOrUpdatePendingFolder(folder.ID, of, deviceID); err != nil {
if err := m.observed.AddOrUpdatePendingFolder(folder.ID, of, deviceID); err != nil {
l.Warnf("Failed to persist pending folder entry to database: %v", err)
}
if !folder.Paused {
@@ -1485,7 +1500,7 @@ func (m *model) ccHandleFolders(folders []protocol.Folder, deviceCfg config.Devi
expiredPendingList := make([]map[string]string, 0, len(expiredPending))
for folder := range expiredPending {
if err = m.db.RemovePendingFolderForDevice(folder, deviceID); err != nil {
if err = m.observed.RemovePendingFolderForDevice(folder, deviceID); err != nil {
msg := "Failed to remove pending folder-device entry"
l.Warnf("%v (%v, %v): %v", msg, folder, deviceID, err)
m.evLogger.Log(events.Failure, msg)
@@ -2015,7 +2030,7 @@ func (m *model) Request(conn protocol.Connection, req *protocol.Request) (out pr
// Grab the FS after limiting, as it causes I/O and we want to minimize
// the race time between the symlink check and the read.
folderFs := folderCfg.Filesystem(nil)
folderFs := folderCfg.Filesystem()
if err := osutil.TraversesSymlink(folderFs, filepath.Dir(req.Name)); err != nil {
l.Debugf("%v REQ(in) traversal check: %s - %s: %q / %q o=%d s=%d", m, err, deviceID.Short(), req.Folder, req.Name, req.Offset, req.Size)
@@ -2138,46 +2153,11 @@ func (m *model) recheckFile(deviceID protocol.DeviceID, folder, name string, off
}
func (m *model) CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool, error) {
m.mut.RLock()
fs, ok := m.folderFiles[folder]
m.mut.RUnlock()
if !ok {
return protocol.FileInfo{}, false, ErrFolderMissing
}
snap, err := fs.Snapshot()
if err != nil {
return protocol.FileInfo{}, false, err
}
f, ok := snap.Get(protocol.LocalDeviceID, file)
snap.Release()
return f, ok, nil
return m.sdb.GetDeviceFile(folder, protocol.LocalDeviceID, file)
}
func (m *model) CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool, error) {
m.mut.RLock()
ffs, ok := m.folderFiles[folder]
m.mut.RUnlock()
if !ok {
return protocol.FileInfo{}, false, ErrFolderMissing
}
snap, err := ffs.Snapshot()
if err != nil {
return protocol.FileInfo{}, false, err
}
f, ok := snap.GetGlobal(file)
snap.Release()
return f, ok, nil
}
func (m *model) GetMtimeMapping(folder string, file string) (fs.MtimeMapping, error) {
m.mut.RLock()
ffs, ok := m.folderFiles[folder]
fcfg := m.folderCfgs[folder]
m.mut.RUnlock()
if !ok {
return fs.MtimeMapping{}, ErrFolderMissing
}
return fs.GetMtimeMapping(fcfg.Filesystem(ffs), file)
return m.sdb.GetGlobalFile(folder, file)
}
// Connection returns if we are connected to the given device.
@@ -2208,7 +2188,7 @@ func (m *model) LoadIgnores(folder string) ([]string, []string, error) {
}
if !ignoresOk {
ignores = ignore.New(cfg.Filesystem(nil))
ignores = ignore.New(cfg.Filesystem())
}
err := ignores.Load(".stignore")
@@ -2263,7 +2243,7 @@ func (m *model) setIgnores(cfg config.FolderConfiguration, content []string) err
return err
}
if err := ignore.WriteIgnores(cfg.Filesystem(nil), ".stignore", content); err != nil {
if err := ignore.WriteIgnores(cfg.Filesystem(), ".stignore", content); err != nil {
l.Warnln("Saving .stignore:", err)
return err
}
@@ -2282,7 +2262,7 @@ func (m *model) setIgnores(cfg config.FolderConfiguration, content []string) err
// 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.Hello) error {
if _, ok := m.cfg.Device(remoteID); !ok {
if err := m.db.AddOrUpdatePendingDevice(remoteID, hello.DeviceName, addr.String()); err != nil {
if err := m.observed.AddOrUpdatePendingDevice(remoteID, hello.DeviceName, addr.String()); err != nil {
l.Warnf("Failed to persist pending device entry to database: %v", err)
}
m.evLogger.Log(events.PendingDevicesChanged, map[string][]interface{}{
@@ -2611,13 +2591,11 @@ func (m *model) generateClusterConfigRLocked(device protocol.DeviceID) (*protoco
DisableTempIndexes: folderCfg.DisableTempIndexes,
}
fs := m.folderFiles[folderCfg.ID]
// Even if we aren't paused, if we haven't started the folder yet
// pretend we are. Otherwise the remote might get confused about
// the missing index info (and drop all the info). We will send
// another cluster config once the folder is started.
protocolFolder.Paused = folderCfg.Paused || fs == nil
protocolFolder.Paused = folderCfg.Paused
for _, folderDevice := range folderCfg.Devices {
deviceCfg, _ := m.cfg.Device(folderDevice.DeviceID)
@@ -2640,14 +2618,12 @@ func (m *model) generateClusterConfigRLocked(device protocol.DeviceID) (*protoco
}
}
if fs != nil {
if deviceCfg.DeviceID == m.id {
protocolDevice.IndexID = fs.IndexID(protocol.LocalDeviceID)
protocolDevice.MaxSequence = fs.Sequence(protocol.LocalDeviceID)
} else {
protocolDevice.IndexID = fs.IndexID(deviceCfg.DeviceID)
protocolDevice.MaxSequence = fs.Sequence(deviceCfg.DeviceID)
}
if deviceCfg.DeviceID == m.id {
protocolDevice.IndexID, _ = m.sdb.GetIndexID(folderCfg.ID, protocol.LocalDeviceID)
protocolDevice.MaxSequence, _ = m.sdb.GetDeviceSequence(folderCfg.ID, protocol.LocalDeviceID)
} else {
protocolDevice.IndexID, _ = m.sdb.GetIndexID(folderCfg.ID, deviceCfg.DeviceID)
protocolDevice.MaxSequence, _ = m.sdb.GetDeviceSequence(folderCfg.ID, deviceCfg.DeviceID)
}
protocolFolder.Devices = append(protocolFolder.Devices, protocolDevice)
@@ -2744,7 +2720,7 @@ func findByName(slice []*TreeEntry, name string) *TreeEntry {
func (m *model) GlobalDirectoryTree(folder, prefix string, levels int, dirsOnly bool) ([]*TreeEntry, error) {
m.mut.RLock()
files, ok := m.folderFiles[folder]
_, ok := m.folderCfgs[folder]
m.mut.RUnlock()
if !ok {
return nil, ErrFolderMissing
@@ -2760,15 +2736,14 @@ func (m *model) GlobalDirectoryTree(folder, prefix string, levels int, dirsOnly
prefix = prefix + sep
}
snap, err := files.Snapshot()
if err != nil {
return nil, err
}
defer snap.Release()
snap.WithPrefixedGlobalTruncated(prefix, func(f protocol.FileInfo) bool {
for f, err := range itererr.Zip(m.sdb.AllGlobalFilesPrefix(folder, prefix)) {
if err != nil {
return nil, err
}
// Don't include the prefix itself.
if f.IsInvalid() || f.IsDeleted() || strings.HasPrefix(prefix, f.Name) {
return true
if f.Invalid || f.Deleted || strings.HasPrefix(prefix, f.Name) {
continue
}
f.Name = strings.Replace(f.Name, prefix, "", 1)
@@ -2777,7 +2752,7 @@ func (m *model) GlobalDirectoryTree(folder, prefix string, levels int, dirsOnly
base := filepath.Base(f.Name)
if levels > -1 && strings.Count(f.Name, sep) > levels {
return true
continue
}
parent := root
@@ -2785,28 +2760,22 @@ func (m *model) GlobalDirectoryTree(folder, prefix string, levels int, dirsOnly
for _, path := range strings.Split(dir, sep) {
child := findByName(parent.Children, path)
if child == nil {
err = fmt.Errorf("could not find child '%s' for path '%s' in parent '%s'", path, f.Name, parent.Name)
return false
return nil, fmt.Errorf("could not find child '%s' for path '%s' in parent '%s'", path, f.Name, parent.Name)
}
parent = child
}
}
if dirsOnly && !f.IsDirectory() {
return true
continue
}
parent.Children = append(parent.Children, &TreeEntry{
Name: base,
Type: f.Type.String(),
ModTime: f.ModTime(),
Size: f.FileSize(),
Size: f.Size,
})
return true
})
if err != nil {
return nil, err
}
return root.Children, nil
@@ -2860,46 +2829,42 @@ func (m *model) Availability(folder string, file protocol.FileInfo, block protoc
m.mut.RLock()
defer m.mut.RUnlock()
fs, ok := m.folderFiles[folder]
cfg := m.folderCfgs[folder]
cfg, ok := m.folderCfgs[folder]
if !ok {
return nil, ErrFolderMissing
}
snap, err := fs.Snapshot()
if err != nil {
return nil, err
}
defer snap.Release()
return m.blockAvailabilityRLocked(cfg, snap, file, block), nil
return m.blockAvailabilityRLocked(cfg, file, block), nil
}
func (m *model) blockAvailability(cfg config.FolderConfiguration, snap *db.Snapshot, file protocol.FileInfo, block protocol.BlockInfo) []Availability {
func (m *model) blockAvailability(cfg config.FolderConfiguration, file protocol.FileInfo, block protocol.BlockInfo) []Availability {
m.mut.RLock()
defer m.mut.RUnlock()
return m.blockAvailabilityRLocked(cfg, snap, file, block)
return m.blockAvailabilityRLocked(cfg, file, block)
}
func (m *model) blockAvailabilityRLocked(cfg config.FolderConfiguration, snap *db.Snapshot, file protocol.FileInfo, block protocol.BlockInfo) []Availability {
func (m *model) blockAvailabilityRLocked(cfg config.FolderConfiguration, file protocol.FileInfo, block protocol.BlockInfo) []Availability {
var candidates []Availability
candidates = append(candidates, m.fileAvailabilityRLocked(cfg, snap, file)...)
candidates = append(candidates, m.fileAvailabilityRLocked(cfg, file)...)
candidates = append(candidates, m.blockAvailabilityFromTemporaryRLocked(cfg, file, block)...)
return candidates
}
func (m *model) fileAvailability(cfg config.FolderConfiguration, snap *db.Snapshot, file protocol.FileInfo) []Availability {
func (m *model) fileAvailability(cfg config.FolderConfiguration, file protocol.FileInfo) []Availability {
m.mut.RLock()
defer m.mut.RUnlock()
return m.fileAvailabilityRLocked(cfg, snap, file)
return m.fileAvailabilityRLocked(cfg, file)
}
func (m *model) fileAvailabilityRLocked(cfg config.FolderConfiguration, snap *db.Snapshot, file protocol.FileInfo) []Availability {
func (m *model) fileAvailabilityRLocked(cfg config.FolderConfiguration, file protocol.FileInfo) []Availability {
var availabilities []Availability
for _, device := range snap.Availability(file.Name) {
devs, err := m.sdb.GetGlobalAvailability(cfg.ID, file.Name)
if err != nil {
return nil
}
for _, device := range devs {
if _, ok := m.remoteFolderStates[device]; !ok {
continue
}
@@ -2936,15 +2901,14 @@ func (m *model) BringToFront(folder, file string) {
}
func (m *model) ResetFolder(folder string) error {
m.mut.RLock()
defer m.mut.RUnlock()
m.mut.Lock()
defer m.mut.Unlock()
_, ok := m.folderRunners.Get(folder)
if ok {
return errors.New("folder must be paused when resetting")
}
l.Infof("Cleaning metadata for reset folder %q", folder)
db.DropFolder(m.db, folder)
return nil
return m.sdb.DropFolder(folder)
}
func (m *model) String() string {
@@ -3058,7 +3022,7 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool {
for deviceID, toCfg := range toDevices {
fromCfg, ok := fromDevices[deviceID]
if !ok {
sr := stats.NewDeviceStatisticsReference(m.db, deviceID)
sr := stats.NewDeviceStatisticsReference(db.NewTyped(m.sdb, "devicestats/"+deviceID.String()))
m.mut.Lock()
m.deviceStatRefs[deviceID] = sr
m.mut.Unlock()
@@ -3151,7 +3115,7 @@ func (m *model) setConnRequestLimitersLocked(cfg config.DeviceConfiguration) {
func (m *model) cleanPending(existingDevices map[protocol.DeviceID]config.DeviceConfiguration, existingFolders map[string]config.FolderConfiguration, ignoredDevices deviceIDSet, removedFolders map[string]struct{}) {
var removedPendingFolders []map[string]string
pendingFolders, err := m.db.PendingFolders()
pendingFolders, err := m.observed.PendingFolders()
if err != nil {
msg := "Could not iterate through pending folder entries for cleanup"
l.Warnf("%v: %v", msg, err)
@@ -3164,7 +3128,7 @@ func (m *model) cleanPending(existingDevices map[protocol.DeviceID]config.Device
// folders as well, assuming the folder is no longer of interest
// at all (but might become pending again).
l.Debugf("Discarding pending removed folder %v from all devices", folderID)
if err := m.db.RemovePendingFolder(folderID); err != nil {
if err := m.observed.RemovePendingFolder(folderID); err != nil {
msg := "Failed to remove pending folder entry"
l.Warnf("%v (%v): %v", msg, folderID, err)
m.evLogger.Log(events.Failure, msg)
@@ -3191,7 +3155,7 @@ func (m *model) cleanPending(existingDevices map[protocol.DeviceID]config.Device
}
continue
removeFolderForDevice:
if err := m.db.RemovePendingFolderForDevice(folderID, deviceID); err != nil {
if err := m.observed.RemovePendingFolderForDevice(folderID, deviceID); err != nil {
msg := "Failed to remove pending folder-device entry"
l.Warnf("%v (%v, %v): %v", msg, folderID, deviceID, err)
m.evLogger.Log(events.Failure, msg)
@@ -3210,7 +3174,7 @@ func (m *model) cleanPending(existingDevices map[protocol.DeviceID]config.Device
}
var removedPendingDevices []map[string]string
pendingDevices, err := m.db.PendingDevices()
pendingDevices, err := m.observed.PendingDevices()
if err != nil {
msg := "Could not iterate through pending device entries for cleanup"
l.Warnf("%v: %v", msg, err)
@@ -3228,7 +3192,7 @@ func (m *model) cleanPending(existingDevices map[protocol.DeviceID]config.Device
}
continue
removeDevice:
if err := m.db.RemovePendingDevice(deviceID); err != nil {
if err := m.observed.RemovePendingDevice(deviceID); err != nil {
msg := "Failed to remove pending device entry"
l.Warnf("%v: %v", msg, err)
m.evLogger.Log(events.Failure, msg)
@@ -3265,20 +3229,20 @@ func (m *model) checkFolderRunningRLocked(folder string) error {
// PendingDevices lists unknown devices that tried to connect.
func (m *model) PendingDevices() (map[protocol.DeviceID]db.ObservedDevice, error) {
return m.db.PendingDevices()
return m.observed.PendingDevices()
}
// PendingFolders lists folders that we don't yet share with the offering devices. It
// returns the entries grouped by folder and filters for a given device unless the
// argument is specified as EmptyDeviceID.
func (m *model) PendingFolders(device protocol.DeviceID) (map[string]db.PendingFolder, error) {
return m.db.PendingFoldersForDevice(device)
return m.observed.PendingFoldersForDevice(device)
}
// DismissPendingDevices removes the record of a specific pending device.
func (m *model) DismissPendingDevice(device protocol.DeviceID) error {
l.Debugf("Discarding pending device %v", device)
err := m.db.RemovePendingDevice(device)
err := m.observed.RemovePendingDevice(device)
if err != nil {
return err
}
@@ -3298,7 +3262,7 @@ func (m *model) DismissPendingFolder(device protocol.DeviceID, folder string) er
var removedPendingFolders []map[string]string
if device == protocol.EmptyDeviceID {
l.Debugf("Discarding pending removed folder %s from all devices", folder)
err := m.db.RemovePendingFolder(folder)
err := m.observed.RemovePendingFolder(folder)
if err != nil {
return err
}
@@ -3307,7 +3271,7 @@ func (m *model) DismissPendingFolder(device protocol.DeviceID, folder string) er
}
} else {
l.Debugf("Discarding pending folder %s from device %v", folder, device)
err := m.db.RemovePendingFolderForDevice(folder, device)
err := m.observed.RemovePendingFolderForDevice(folder, device)
if err != nil {
return err
}
@@ -3436,7 +3400,7 @@ type storedEncryptionToken struct {
}
func readEncryptionToken(cfg config.FolderConfiguration) ([]byte, error) {
fd, err := cfg.Filesystem(nil).Open(encryptionTokenPath(cfg))
fd, err := cfg.Filesystem().Open(encryptionTokenPath(cfg))
if err != nil {
return nil, err
}
@@ -3450,7 +3414,7 @@ func readEncryptionToken(cfg config.FolderConfiguration) ([]byte, error) {
func writeEncryptionToken(token []byte, cfg config.FolderConfiguration) error {
tokenName := encryptionTokenPath(cfg)
fd, err := cfg.Filesystem(nil).OpenFile(tokenName, fs.OptReadWrite|fs.OptCreate, 0o666)
fd, err := cfg.Filesystem().OpenFile(tokenName, fs.OptReadWrite|fs.OptCreate, 0o666)
if err != nil {
return err
}
+132 -218
View File
@@ -13,6 +13,7 @@ import (
"errors"
"fmt"
"io"
"iter"
mrand "math/rand"
"os"
"path/filepath"
@@ -24,10 +25,10 @@ import (
"testing"
"time"
"github.com/syncthing/syncthing/internal/db"
"github.com/syncthing/syncthing/internal/itererr"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/ignore"
@@ -77,7 +78,7 @@ func addFolderDevicesToClusterConfig(cc *protocol.ClusterConfig, remote protocol
func TestRequest(t *testing.T) {
wrapper, fcfg, cancel := newDefaultCfgWrapper()
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
defer cancel()
m := setupModel(t, wrapper)
defer cleanupModel(m)
@@ -165,7 +166,7 @@ func BenchmarkIndex_100(b *testing.B) {
func benchmarkIndex(b *testing.B, nfiles int) {
m, _, fcfg, wcfgCancel := setupModelWithConnection(b)
defer wcfgCancel()
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
files := genFiles(nfiles)
must(b, m.Index(device1Conn, &protocol.Index{Folder: fcfg.ID, Files: files}))
@@ -192,7 +193,7 @@ func BenchmarkIndexUpdate_10000_1(b *testing.B) {
func benchmarkIndexUpdate(b *testing.B, nfiles, nufiles int) {
m, _, fcfg, wcfgCancel := setupModelWithConnection(b)
defer wcfgCancel()
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
files := genFiles(nfiles)
ufiles := genFiles(nufiles)
@@ -235,7 +236,7 @@ func BenchmarkRequestOut(b *testing.B) {
func BenchmarkRequestInSingleFile(b *testing.B) {
w, cancel := newConfigWrapper(defaultCfg)
defer cancel()
ffs := w.FolderList()[0].Filesystem(nil)
ffs := w.FolderList()[0].Filesystem()
m := setupModel(b, w)
defer cleanupModel(m)
@@ -1195,7 +1196,7 @@ func TestAutoAcceptPrefersLabel(t *testing.T) {
func TestAutoAcceptFallsBackToID(t *testing.T) {
// Prefers label, falls back to ID.
m, cancel := newState(t, defaultAutoAcceptCfg)
ffs := defaultFolderConfig.Filesystem(nil)
ffs := defaultFolderConfig.Filesystem()
id := srand.String(8)
label := srand.String(8)
if err := ffs.MkdirAll(label, 0o777); err != nil {
@@ -1488,7 +1489,7 @@ func changeIgnores(t *testing.T, m *testModel, expected []string) {
func TestIgnores(t *testing.T) {
w, cancel := newConfigWrapper(defaultCfg)
defer cancel()
ffs := w.FolderList()[0].Filesystem(nil)
ffs := w.FolderList()[0].Filesystem()
m := setupModel(t, w)
defer cleanupModel(m)
@@ -1523,7 +1524,7 @@ func TestIgnores(t *testing.T) {
ID: "fresh", Path: "XXX",
FilesystemType: config.FilesystemTypeFake,
}
ignores := ignore.New(fcfg.Filesystem(nil), ignore.WithCache(m.cfg.Options().CacheIgnoredFiles))
ignores := ignore.New(fcfg.Filesystem(), ignore.WithCache(m.cfg.Options().CacheIgnoredFiles))
m.mut.Lock()
m.folderCfgs[fcfg.ID] = fcfg
m.folderIgnores[fcfg.ID] = ignores
@@ -1555,7 +1556,7 @@ func TestIgnores(t *testing.T) {
func TestEmptyIgnores(t *testing.T) {
w, cancel := newConfigWrapper(defaultCfg)
defer cancel()
ffs := w.FolderList()[0].Filesystem(nil)
ffs := w.FolderList()[0].Filesystem()
m := setupModel(t, w)
defer cleanupModel(m)
@@ -1628,12 +1629,11 @@ func TestROScanRecovery(t *testing.T) {
defer cancel()
m := newModel(t, cfg, myID, nil)
set := newFileSet(t, "default", m.db)
set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
m.sdb.Update("default", protocol.LocalDeviceID, []protocol.FileInfo{
{Name: "dummyfile", Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 1}}}},
})
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
// Remove marker to generate an error
ffs.Remove(fcfg.MarkerName)
@@ -1675,12 +1675,11 @@ func TestRWScanRecovery(t *testing.T) {
defer cancel()
m := newModel(t, cfg, myID, nil)
set := newFileSet(t, "default", m.db)
set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
m.sdb.Update("default", protocol.LocalDeviceID, []protocol.FileInfo{
{Name: "dummyfile", Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 1}}}},
})
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
// Generate error
if err := ffs.Remove(config.DefaultMarkerName); err != nil {
@@ -1706,8 +1705,9 @@ func TestRWScanRecovery(t *testing.T) {
func TestGlobalDirectoryTree(t *testing.T) {
m, conn, fcfg, wCancel := setupModelWithConnection(t)
defer wCancel()
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
var seq int64
b := func(isfile bool, path ...string) protocol.FileInfo {
typ := protocol.FileInfoTypeDirectory
var blocks []protocol.BlockInfo
@@ -1716,12 +1716,14 @@ func TestGlobalDirectoryTree(t *testing.T) {
typ = protocol.FileInfoTypeFile
blocks = []protocol.BlockInfo{{Offset: 0x0, Size: 0xa, Hash: []uint8{0x2f, 0x72, 0xcc, 0x11, 0xa6, 0xfc, 0xd0, 0x27, 0x1e, 0xce, 0xf8, 0xc6, 0x10, 0x56, 0xee, 0x1e, 0xb1, 0x24, 0x3b, 0xe3, 0x80, 0x5b, 0xf9, 0xa9, 0xdf, 0x98, 0xf9, 0x2f, 0x76, 0x36, 0xb0, 0x5c}}}
}
seq++
return protocol.FileInfo{
Name: filepath.Join(path...),
Type: typ,
ModifiedS: 0x666,
Blocks: blocks,
Size: 0xa,
Sequence: seq,
}
}
f := func(name string) *TreeEntry {
@@ -1813,13 +1815,13 @@ func TestGlobalDirectoryTree(t *testing.T) {
result, _ := m.GlobalDirectoryTree("default", "", -1, false)
if mm(result) != mm(expectedResult) {
t.Errorf("Does not match:\n%s\n============\n%s", mm(result), mm(expectedResult))
t.Fatalf("Does not match:\n%s\n============\n%s", mm(result), mm(expectedResult))
}
result, _ = m.GlobalDirectoryTree("default", "another", -1, false)
if mm(result) != mm(findByName(expectedResult, "another").Children) {
t.Errorf("Does not match:\n%s\n============\n%s", mm(result), mm(findByName(expectedResult, "another").Children))
t.Fatalf("Does not match:\n%s\n============\n%s", mm(result), mm(findByName(expectedResult, "another").Children))
}
result, _ = m.GlobalDirectoryTree("default", "", 0, false)
@@ -1831,7 +1833,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
}
if mm(result) != mm(currentResult) {
t.Errorf("Does not match:\n%s\n============\n%s", mm(result), mm(currentResult))
t.Fatalf("Does not match:\n%s\n============\n%s", mm(result), mm(currentResult))
}
result, _ = m.GlobalDirectoryTree("default", "", 1, false)
@@ -1852,7 +1854,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
}
if mm(result) != mm(currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result, _ = m.GlobalDirectoryTree("default", "", -1, true)
@@ -1882,7 +1884,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
}
if mm(result) != mm(currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result, _ = m.GlobalDirectoryTree("default", "", 1, true)
@@ -1901,7 +1903,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
}
if mm(result) != mm(currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result, _ = m.GlobalDirectoryTree("default", "another", 0, false)
@@ -1911,7 +1913,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
}
if mm(result) != mm(currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result, _ = m.GlobalDirectoryTree("default", "some/directory", 0, false)
@@ -1920,7 +1922,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
}
if mm(result) != mm(currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result, _ = m.GlobalDirectoryTree("default", "some/directory", 1, false)
@@ -1931,7 +1933,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
}
if mm(result) != mm(currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result, _ = m.GlobalDirectoryTree("default", "some/directory", 2, false)
@@ -1944,7 +1946,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
}
if mm(result) != mm(currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result, _ = m.GlobalDirectoryTree("default", "another", -1, true)
@@ -1957,7 +1959,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
}
if mm(result) != mm(currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
// No prefix matching!
@@ -1965,7 +1967,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
currentResult = []*TreeEntry{}
if mm(result) != mm(currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
}
@@ -2010,7 +2012,7 @@ func BenchmarkTree_100_10(b *testing.B) {
func benchmarkTree(b *testing.B, n1, n2 int) {
m, _, fcfg, wcfgCancel := setupModelWithConnection(b)
defer wcfgCancel()
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
m.ScanFolder(fcfg.ID)
files := genDeepFiles(n1, n2)
@@ -2027,7 +2029,7 @@ func benchmarkTree(b *testing.B, n1, n2 int) {
func TestIssue3028(t *testing.T) {
w, cancel := newConfigWrapper(defaultCfg)
defer cancel()
ffs := w.FolderList()[0].Filesystem(nil)
ffs := w.FolderList()[0].Filesystem()
m := setupModel(t, w)
defer cleanupModel(m)
@@ -2039,8 +2041,8 @@ func TestIssue3028(t *testing.T) {
// Scan, and get a count of how many files are there now
m.ScanFolderSubdirs("default", []string{"testrm", "testrm2"})
locorigfiles := localSize(t, m, "default").Files
globorigfiles := globalSize(t, m, "default").Files
locorigfiles := mustV(m.LocalSize("default", protocol.LocalDeviceID)).Files
globorigfiles := mustV(m.GlobalSize("default")).Files
// Delete
@@ -2051,8 +2053,8 @@ func TestIssue3028(t *testing.T) {
// deleted files increases by two
m.ScanFolderSubdirs("default", []string{"testrm", "testrm2"})
loc := localSize(t, m, "default")
glob := globalSize(t, m, "default")
loc := mustV(m.LocalSize("default", protocol.LocalDeviceID))
glob := mustV(m.GlobalSize("default"))
if loc.Files != locorigfiles-2 {
t.Errorf("Incorrect local accounting; got %d current files, expected %d", loc.Files, locorigfiles-2)
@@ -2127,24 +2129,22 @@ func TestIssue4357(t *testing.T) {
func TestIndexesForUnknownDevicesDropped(t *testing.T) {
m := newModel(t, defaultCfgWrapper, myID, nil)
files := newFileSet(t, "default", m.db)
files.Drop(device1)
files.Update(device1, genFiles(1))
files.Drop(device2)
files.Update(device2, genFiles(1))
m.sdb.DropAllFiles("default", device1)
m.sdb.Update("default", device1, genFiles(1))
m.sdb.DropAllFiles("default", device2)
m.sdb.Update("default", device2, genFiles(1))
if len(files.ListDevices()) != 2 {
if devs, err := m.sdb.ListDevicesForFolder("default"); err != nil || len(devs) != 2 {
t.Log(devs, err)
t.Error("expected two devices")
}
m.newFolder(defaultFolderConfig, false)
defer cleanupModel(m)
// Remote sequence is cached, hence need to recreated.
files = newFileSet(t, "default", m.db)
if l := len(files.ListDevices()); l != 1 {
t.Errorf("Expected one device got %v", l)
if devs, err := m.sdb.ListDevicesForFolder("default"); err != nil || len(devs) != 1 {
t.Log(devs, err)
t.Error("expected one device")
}
}
@@ -2270,7 +2270,7 @@ func TestIssue3829(t *testing.T) {
func TestIssue4573(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
testFs := fcfg.Filesystem(nil)
testFs := fcfg.Filesystem()
defer os.RemoveAll(testFs.URI())
must(t, testFs.MkdirAll("inaccessible", 0o755))
@@ -2300,7 +2300,7 @@ func TestIssue4573(t *testing.T) {
func TestInternalScan(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
testFs := fcfg.Filesystem(nil)
testFs := fcfg.Filesystem()
defer os.RemoveAll(testFs.URI())
testCases := map[string]func(protocol.FileInfo) bool{
@@ -2372,12 +2372,11 @@ func TestCustomMarkerName(t *testing.T) {
})
defer cancel()
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
m := newModel(t, cfg, myID, nil)
set := newFileSet(t, "default", m.db)
set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
m.sdb.Update("default", protocol.LocalDeviceID, []protocol.FileInfo{
{Name: "dummyfile"},
})
@@ -2401,7 +2400,7 @@ func TestCustomMarkerName(t *testing.T) {
func TestRemoveDirWithContent(t *testing.T) {
m, conn, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
tfs := fcfg.Filesystem(nil)
tfs := fcfg.Filesystem()
defer cleanupModelAndRemoveDir(m, tfs.URI())
tfs.MkdirAll("dirwith", 0o755)
@@ -2463,7 +2462,7 @@ func TestIssue4475(t *testing.T) {
m, conn, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
defer cleanupModel(m)
testFs := fcfg.Filesystem(nil)
testFs := fcfg.Filesystem()
// Scenario: Dir is deleted locally and before syncing/index exchange
// happens, a file is create in that dir on the remote.
@@ -2525,7 +2524,7 @@ func TestVersionRestore(t *testing.T) {
fcfg := newFolderConfiguration(defaultCfgWrapper, "default", "default", config.FilesystemTypeFake, srand.String(32))
fcfg.Versioning.Type = "simple"
fcfg.FSWatcherEnabled = false
filesystem := fcfg.Filesystem(nil)
filesystem := fcfg.Filesystem()
rawConfig := config.Configuration{
Version: config.CurrentVersion,
@@ -2759,7 +2758,7 @@ func TestIssue4094(t *testing.T) {
t.Fatalf("failed setting ignores: %v", err)
}
if _, err := fcfg.Filesystem(nil).Lstat(".stignore"); err != nil {
if _, err := fcfg.Filesystem().Lstat(".stignore"); err != nil {
t.Fatalf("failed stating .stignore: %v", err)
}
}
@@ -2788,7 +2787,7 @@ func TestIssue4903(t *testing.T) {
t.Fatalf("expected path missing error, got: %v, debug: %s", err, fcfg.CheckPath())
}
if _, err := fcfg.Filesystem(nil).Lstat("."); !fs.IsNotExist(err) {
if _, err := fcfg.Filesystem().Lstat("."); !fs.IsNotExist(err) {
t.Fatalf("Expected missing path error, got: %v", err)
}
}
@@ -2798,7 +2797,7 @@ func TestIssue5002(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
fd, err := ffs.Create("foo")
must(t, err)
@@ -2827,7 +2826,7 @@ func TestIssue5002(t *testing.T) {
func TestParentOfUnignored(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
must(t, ffs.Mkdir("bar", 0o755))
must(t, ffs.Mkdir("baz", 0o755))
@@ -2906,7 +2905,7 @@ func TestFolderRestartZombies(t *testing.T) {
func TestRequestLimit(t *testing.T) {
wrapper, fcfg, cancel := newDefaultCfgWrapper()
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
file := "tmpfile"
fd, err := ffs.Create(file)
@@ -2966,7 +2965,7 @@ func TestConnCloseOnRestart(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
m := setupModel(t, w)
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
br := &testutil.BlockingRW{}
nw := &testutil.NoopRW{}
@@ -3009,7 +3008,7 @@ func TestModTimeWindow(t *testing.T) {
defer wCancel()
tfs := modtimeTruncatingFS{
trunc: 0,
Filesystem: fcfg.Filesystem(nil),
Filesystem: fcfg.Filesystem(),
}
// fcfg.RawModTimeWindowS = 2
setFolder(t, w, fcfg)
@@ -3069,7 +3068,7 @@ func TestModTimeWindow(t *testing.T) {
func TestDevicePause(t *testing.T) {
m, _, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
sub := m.evLogger.Subscribe(events.DevicePaused)
defer sub.Unsubscribe()
@@ -3099,7 +3098,7 @@ func TestDevicePause(t *testing.T) {
func TestDeviceWasSeen(t *testing.T) {
m, _, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
m.deviceWasSeen(device1)
@@ -3194,7 +3193,7 @@ func TestRenameSequenceOrder(t *testing.T) {
numFiles := 20
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
for i := 0; i < numFiles; i++ {
v := fmt.Sprintf("%d", i)
writeFile(t, ffs, v, []byte(v))
@@ -3202,14 +3201,7 @@ func TestRenameSequenceOrder(t *testing.T) {
m.ScanFolders()
count := 0
snap := dbSnapshot(t, m, "default")
snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileInfo) bool {
count++
return true
})
snap.Release()
count := countIterator[protocol.FileInfo](t)(m.LocalFiles("default", protocol.LocalDeviceID))
if count != numFiles {
t.Errorf("Unexpected count: %d != %d", count, numFiles)
}
@@ -3229,14 +3221,11 @@ func TestRenameSequenceOrder(t *testing.T) {
// Scan
m.ScanFolders()
// Verify sequence of a appearing is followed by c disappearing.
snap = dbSnapshot(t, m, "default")
defer snap.Release()
var firstExpectedSequence int64
var secondExpectedSequence int64
failed := false
snap.WithHaveSequence(0, func(i protocol.FileInfo) bool {
it, errFn := m.LocalFilesSequenced("default", protocol.LocalDeviceID, 0)
for i := range it {
t.Log(i)
if i.FileName() == "17" {
firstExpectedSequence = i.SequenceNo() + 1
@@ -3250,8 +3239,10 @@ func TestRenameSequenceOrder(t *testing.T) {
if i.FileName() == "16" {
failed = i.SequenceNo() != secondExpectedSequence || failed
}
return true
})
}
if err := errFn(); err != nil {
t.Fatal(err)
}
if failed {
t.Fail()
}
@@ -3263,19 +3254,12 @@ func TestRenameSameFile(t *testing.T) {
m := setupModel(t, wcfg)
defer cleanupModel(m)
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
writeFile(t, ffs, "file", []byte("file"))
m.ScanFolders()
count := 0
snap := dbSnapshot(t, m, "default")
snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileInfo) bool {
count++
return true
})
snap.Release()
count := countIterator[protocol.FileInfo](t)(m.LocalFiles("default", protocol.LocalDeviceID))
if count != 1 {
t.Errorf("Unexpected count: %d != %d", count, 1)
}
@@ -3288,12 +3272,10 @@ func TestRenameSameFile(t *testing.T) {
m.ScanFolders()
snap = dbSnapshot(t, m, "default")
defer snap.Release()
prevSeq := int64(0)
seen := false
snap.WithHaveSequence(0, func(i protocol.FileInfo) bool {
it, errFn := m.LocalFilesSequenced("default", protocol.LocalDeviceID, 0)
for i := range it {
if i.SequenceNo() <= prevSeq {
t.Fatalf("non-increasing sequences: %d <= %d", i.SequenceNo(), prevSeq)
}
@@ -3304,84 +3286,9 @@ func TestRenameSameFile(t *testing.T) {
seen = true
}
prevSeq = i.SequenceNo()
return true
})
}
func TestRenameEmptyFile(t *testing.T) {
wcfg, fcfg, wcfgCancel := newDefaultCfgWrapper()
defer wcfgCancel()
m := setupModel(t, wcfg)
defer cleanupModel(m)
ffs := fcfg.Filesystem(nil)
writeFile(t, ffs, "file", []byte("data"))
writeFile(t, ffs, "empty", nil)
m.ScanFolders()
snap := dbSnapshot(t, m, "default")
defer snap.Release()
empty, eok := snap.Get(protocol.LocalDeviceID, "empty")
if !eok {
t.Fatal("failed to find empty file")
}
file, fok := snap.Get(protocol.LocalDeviceID, "file")
if !fok {
t.Fatal("failed to find non-empty file")
}
count := 0
snap.WithBlocksHash(empty.BlocksHash, func(_ protocol.FileInfo) bool {
count++
return true
})
if count != 0 {
t.Fatalf("Found %d entries for empty file, expected 0", count)
}
count = 0
snap.WithBlocksHash(file.BlocksHash, func(_ protocol.FileInfo) bool {
count++
return true
})
if count != 1 {
t.Fatalf("Found %d entries for non-empty file, expected 1", count)
}
must(t, ffs.Rename("file", "new-file"))
must(t, ffs.Rename("empty", "new-empty"))
// Scan
m.ScanFolders()
snap = dbSnapshot(t, m, "default")
defer snap.Release()
count = 0
snap.WithBlocksHash(empty.BlocksHash, func(_ protocol.FileInfo) bool {
count++
return true
})
if count != 0 {
t.Fatalf("Found %d entries for empty file, expected 0", count)
}
count = 0
snap.WithBlocksHash(file.BlocksHash, func(i protocol.FileInfo) bool {
count++
if i.FileName() != "new-file" {
t.Fatalf("unexpected file name %s, expected new-file", i.FileName())
}
return true
})
if count != 1 {
t.Fatalf("Found %d entries for non-empty file, expected 1", count)
if err := errFn(); err != nil {
t.Fatal(err)
}
}
@@ -3391,7 +3298,7 @@ func TestBlockListMap(t *testing.T) {
m := setupModel(t, wcfg)
defer cleanupModel(m)
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
writeFile(t, ffs, "one", []byte("content"))
writeFile(t, ffs, "two", []byte("content"))
writeFile(t, ffs, "three", []byte("content"))
@@ -3400,23 +3307,25 @@ func TestBlockListMap(t *testing.T) {
m.ScanFolders()
snap := dbSnapshot(t, m, "default")
defer snap.Release()
fi, ok := snap.Get(protocol.LocalDeviceID, "one")
fi, ok, err := m.model.CurrentFolderFile("default", "one")
if err != nil {
t.Fatal(err)
}
if !ok {
t.Error("failed to find existing file")
}
var paths []string
snap.WithBlocksHash(fi.BlocksHash, func(fi protocol.FileInfo) bool {
paths = append(paths, fi.FileName())
return true
})
snap.Release()
for fi, err := range itererr.Zip(m.model.AllForBlocksHash(fcfg.ID, fi.BlocksHash)) {
if err != nil {
t.Fatal(err)
}
paths = append(paths, fi.Name)
}
expected := []string{"one", "two", "three", "four", "five"}
if !equalStringsInAnyOrder(paths, expected) {
t.Errorf("expected %q got %q", expected, paths)
t.Fatalf("expected %q got %q", expected, paths)
}
// Fudge the files around
@@ -3437,19 +3346,18 @@ func TestBlockListMap(t *testing.T) {
m.ScanFolders()
// Check we're left with 2 of the 5
snap = dbSnapshot(t, m, "default")
defer snap.Release()
paths = paths[:0]
snap.WithBlocksHash(fi.BlocksHash, func(fi protocol.FileInfo) bool {
paths = append(paths, fi.FileName())
return true
})
snap.Release()
for fi, err := range itererr.Zip(m.model.AllForBlocksHash(fcfg.ID, fi.BlocksHash)) {
if err != nil {
t.Fatal(err)
}
paths = append(paths, fi.Name)
}
expected = []string{"new-three", "five"}
if !equalStringsInAnyOrder(paths, expected) {
t.Errorf("expected %q got %q", expected, paths)
t.Fatalf("expected %q got %q", expected, paths)
}
}
@@ -3459,16 +3367,17 @@ func TestScanRenameCaseOnly(t *testing.T) {
m := setupModel(t, wcfg)
defer cleanupModel(m)
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
name := "foo"
writeFile(t, ffs, name, []byte("contents"))
m.ScanFolders()
snap := dbSnapshot(t, m, fcfg.ID)
defer snap.Release()
found := false
snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileInfo) bool {
for i, err := range itererr.Zip(m.LocalFiles(fcfg.ID, protocol.LocalDeviceID)) {
if err != nil {
t.Fatal(err)
}
if found {
t.Fatal("got more than one file")
}
@@ -3476,21 +3385,20 @@ func TestScanRenameCaseOnly(t *testing.T) {
t.Fatalf("got file %v, expected %v", i.FileName(), name)
}
found = true
return true
})
snap.Release()
}
upper := strings.ToUpper(name)
must(t, ffs.Rename(name, upper))
m.ScanFolders()
snap = dbSnapshot(t, m, fcfg.ID)
defer snap.Release()
found = false
snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileInfo) bool {
for i, err := range itererr.Zip(m.LocalFiles(fcfg.ID, protocol.LocalDeviceID)) {
if err != nil {
t.Fatal(err)
}
if i.FileName() == name {
if i.IsDeleted() {
return true
continue
}
t.Fatal("renamed file not deleted")
}
@@ -3501,8 +3409,7 @@ func TestScanRenameCaseOnly(t *testing.T) {
t.Fatal("got more than the expected files")
}
found = true
return true
})
}
}
func TestClusterConfigOnFolderAdd(t *testing.T) {
@@ -3577,7 +3484,7 @@ func TestAddFolderCompletion(t *testing.T) {
func TestScanDeletedROChangedOnSR(t *testing.T) {
m, conn, fcfg, wCancel := setupModelWithConnection(t)
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
defer wCancel()
defer cleanupModelAndRemoveDir(m, ffs.URI())
fcfg.Type = config.FolderTypeReceiveOnly
@@ -3599,7 +3506,7 @@ func TestScanDeletedROChangedOnSR(t *testing.T) {
must(t, ffs.Remove(name))
m.ScanFolders()
if receiveOnlyChangedSize(t, m, fcfg.ID).Deleted != 1 {
if mustV(m.ReceiveOnlySize(fcfg.ID)).Deleted != 1 {
t.Fatal("expected one receive only changed deleted item")
}
@@ -3607,10 +3514,10 @@ func TestScanDeletedROChangedOnSR(t *testing.T) {
setFolder(t, m.cfg, fcfg)
m.ScanFolders()
if receiveOnlyChangedSize(t, m, fcfg.ID).Deleted != 0 {
if mustV(m.ReceiveOnlySize(fcfg.ID)).Deleted != 0 {
t.Fatal("expected no receive only changed deleted item")
}
if localSize(t, m, fcfg.ID).Deleted != 1 {
if mustV(m.LocalSize(fcfg.ID, protocol.LocalDeviceID)).Deleted != 1 {
t.Fatal("expected one local deleted item")
}
}
@@ -3682,7 +3589,7 @@ func testConfigChangeTriggersClusterConfigs(t *testing.T, expectFirst, expectSec
func TestIssue6961(t *testing.T) {
wcfg, fcfg, wcfgCancel := newDefaultCfgWrapper()
defer wcfgCancel()
tfs := fcfg.Filesystem(nil)
tfs := fcfg.Filesystem()
waiter, err := wcfg.Modify(func(cfg *config.Configuration) {
cfg.SetDevice(newDeviceConfiguration(cfg.Defaults.Device, device2, "device2"))
fcfg.Type = config.FolderTypeReceiveOnly
@@ -3693,11 +3600,6 @@ func TestIssue6961(t *testing.T) {
waiter.Wait()
// Always recalc/repair when opening a fileset.
m := newModel(t, wcfg, myID, nil)
m.db.Close()
m.db, err = db.NewLowlevel(backend.OpenMemory(), m.evLogger, db.WithRecheckInterval(time.Millisecond))
if err != nil {
t.Fatal(err)
}
m.ServeBackground()
defer cleanupModelAndRemoveDir(m, tfs.URI())
conn1 := addFakeConn(m, device1, fcfg.ID)
@@ -3752,11 +3654,9 @@ func TestIssue6961(t *testing.T) {
func TestCompletionEmptyGlobal(t *testing.T) {
m, conn, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
files := []protocol.FileInfo{{Name: "foo", Version: protocol.Vector{}.Update(myID.Short()), Sequence: 1}}
m.mut.Lock()
m.folderFiles[fcfg.ID].Update(protocol.LocalDeviceID, files)
m.mut.Unlock()
m.sdb.Update(fcfg.ID, protocol.LocalDeviceID, files)
files[0].Deleted = true
files[0].Version = files[0].Version.Update(device1.Short())
must(t, m.IndexUpdate(conn, &protocol.IndexUpdate{Folder: fcfg.ID, Files: files}))
@@ -3953,7 +3853,7 @@ func TestCCFolderNotRunning(t *testing.T) {
// Create the folder, but don't start it.
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
tfs := fcfg.Filesystem(nil)
tfs := fcfg.Filesystem()
m := newModel(t, w, myID, nil)
defer cleanupModelAndRemoveDir(m, tfs.URI())
@@ -3990,7 +3890,7 @@ func TestPendingFolder(t *testing.T) {
Time: time.Now().Truncate(time.Second),
Label: pfolder,
}
if err := m.db.AddOrUpdatePendingFolder(pfolder, of, device2); err != nil {
if err := m.observed.AddOrUpdatePendingFolder(pfolder, of, device2); err != nil {
t.Fatal(err)
}
deviceFolders, err := m.PendingFolders(protocol.EmptyDeviceID)
@@ -4009,7 +3909,7 @@ func TestPendingFolder(t *testing.T) {
t.Fatal(err)
}
setDevice(t, w, config.DeviceConfiguration{DeviceID: device3})
if err := m.db.AddOrUpdatePendingFolder(pfolder, of, device3); err != nil {
if err := m.observed.AddOrUpdatePendingFolder(pfolder, of, device3); err != nil {
t.Fatal(err)
}
deviceFolders, err = m.PendingFolders(device2)
@@ -4060,7 +3960,7 @@ func TestDeletedNotLocallyChangedReceiveEncrypted(t *testing.T) {
func deletedNotLocallyChanged(t *testing.T, ft config.FolderType) {
w, fcfg, wCancel := newDefaultCfgWrapper()
tfs := fcfg.Filesystem(nil)
tfs := fcfg.Filesystem()
fcfg.Type = ft
setFolder(t, w, fcfg)
defer wCancel()
@@ -4141,3 +4041,17 @@ type modtimeTruncatingFileInfo struct {
func (fi modtimeTruncatingFileInfo) ModTime() time.Time {
return fi.FileInfo.ModTime().Truncate(fi.trunc)
}
func countIterator[T any](t *testing.T) func(it iter.Seq[T], errFn func() error) int {
return func(it iter.Seq[T], errFn func() error) int {
t.Helper()
count := 0
for range it {
count++
}
if err := errFn(); err != nil {
t.Fatal(err)
}
return count
}
}
-5
View File
@@ -76,7 +76,6 @@ func (t *ProgressEmitter) Serve(ctx context.Context) error {
return nil
case <-t.timer.C:
t.mut.Lock()
l.Debugln("progress emitter: timer - looking after", len(t.registry))
newLastUpdated := lastUpdate
newCount = t.lenRegistryLocked()
@@ -94,8 +93,6 @@ func (t *ProgressEmitter) Serve(ctx context.Context) error {
lastCount = newCount
t.sendDownloadProgressEventLocked()
progressUpdates = t.computeProgressUpdates()
} else {
l.Debugln("progress emitter: nothing new")
}
if newCount != 0 {
@@ -247,7 +244,6 @@ func (t *ProgressEmitter) Register(s *sharedPullerState) {
t.mut.Lock()
defer t.mut.Unlock()
if t.disabled {
l.Debugln("progress emitter: disabled, skip registering")
return
}
l.Debugln("progress emitter: registering", s.folder, s.file.Name)
@@ -266,7 +262,6 @@ func (t *ProgressEmitter) Deregister(s *sharedPullerState) {
defer t.mut.Unlock()
if t.disabled {
l.Debugln("progress emitter: disabled, skip deregistering")
return
}
-51
View File
@@ -7,10 +7,8 @@
package model
import (
"sort"
"time"
"github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/sync"
)
@@ -127,13 +125,6 @@ func (q *jobQueue) Jobs(page, perpage int) ([]string, []string, int) {
return progress, queued, (page - 1) * perpage
}
func (q *jobQueue) Shuffle() {
q.mut.Lock()
defer q.mut.Unlock()
rand.Shuffle(q.queued)
}
func (q *jobQueue) Reset() {
q.mut.Lock()
defer q.mut.Unlock()
@@ -152,45 +143,3 @@ func (q *jobQueue) lenProgress() int {
defer q.mut.Unlock()
return len(q.progress)
}
func (q *jobQueue) SortSmallestFirst() {
q.mut.Lock()
defer q.mut.Unlock()
sort.Sort(smallestFirst(q.queued))
}
func (q *jobQueue) SortLargestFirst() {
q.mut.Lock()
defer q.mut.Unlock()
sort.Sort(sort.Reverse(smallestFirst(q.queued)))
}
func (q *jobQueue) SortOldestFirst() {
q.mut.Lock()
defer q.mut.Unlock()
sort.Sort(oldestFirst(q.queued))
}
func (q *jobQueue) SortNewestFirst() {
q.mut.Lock()
defer q.mut.Unlock()
sort.Sort(sort.Reverse(oldestFirst(q.queued)))
}
// The usual sort.Interface boilerplate
type smallestFirst []jobQueueEntry
func (q smallestFirst) Len() int { return len(q) }
func (q smallestFirst) Less(a, b int) bool { return q[a].size < q[b].size }
func (q smallestFirst) Swap(a, b int) { q[a], q[b] = q[b], q[a] }
type oldestFirst []jobQueueEntry
func (q oldestFirst) Len() int { return len(q) }
func (q oldestFirst) Less(a, b int) bool { return q[a].modified < q[b].modified }
func (q oldestFirst) Swap(a, b int) { q[a], q[b] = q[b], q[a] }
-89
View File
@@ -163,95 +163,6 @@ func TestBringToFront(t *testing.T) {
}
}
func TestShuffle(t *testing.T) {
q := newJobQueue()
q.Push("f1", 0, time.Time{})
q.Push("f2", 0, time.Time{})
q.Push("f3", 0, time.Time{})
q.Push("f4", 0, time.Time{})
// This test will fail once in eight million times (1 / (4!)^5) :)
for i := 0; i < 5; i++ {
q.Shuffle()
_, queued, _ := q.Jobs(1, 100)
if l := len(queued); l != 4 {
t.Fatalf("Weird length %d returned from jobs(1, 100)", l)
}
t.Logf("%v", queued)
if _, equal := messagediff.PrettyDiff([]string{"f1", "f2", "f3", "f4"}, queued); !equal {
// The queue was shuffled
return
}
}
t.Error("Queue was not shuffled after five attempts.")
}
func TestSortBySize(t *testing.T) {
q := newJobQueue()
q.Push("f1", 20, time.Time{})
q.Push("f2", 40, time.Time{})
q.Push("f3", 30, time.Time{})
q.Push("f4", 10, time.Time{})
q.SortSmallestFirst()
_, actual, _ := q.Jobs(1, 100)
if l := len(actual); l != 4 {
t.Fatalf("Weird length %d returned from jobs(1, 100)", l)
}
expected := []string{"f4", "f1", "f3", "f2"}
if diff, equal := messagediff.PrettyDiff(expected, actual); !equal {
t.Errorf("SortSmallestFirst() diff:\n%s", diff)
}
q.SortLargestFirst()
_, actual, _ = q.Jobs(1, 100)
if l := len(actual); l != 4 {
t.Fatalf("Weird length %d returned from jobs(1, 100)", l)
}
expected = []string{"f2", "f3", "f1", "f4"}
if diff, equal := messagediff.PrettyDiff(expected, actual); !equal {
t.Errorf("SortLargestFirst() diff:\n%s", diff)
}
}
func TestSortByAge(t *testing.T) {
q := newJobQueue()
q.Push("f1", 0, time.Unix(20, 0))
q.Push("f2", 0, time.Unix(40, 0))
q.Push("f3", 0, time.Unix(30, 0))
q.Push("f4", 0, time.Unix(10, 0))
q.SortOldestFirst()
_, actual, _ := q.Jobs(1, 100)
if l := len(actual); l != 4 {
t.Fatalf("Weird length %d returned from jobs(1, 100)", l)
}
expected := []string{"f4", "f1", "f3", "f2"}
if diff, equal := messagediff.PrettyDiff(expected, actual); !equal {
t.Errorf("SortOldestFirst() diff:\n%s", diff)
}
q.SortNewestFirst()
_, actual, _ = q.Jobs(1, 100)
if l := len(actual); l != 4 {
t.Fatalf("Weird length %d returned from jobs(1, 100)", l)
}
expected = []string{"f2", "f3", "f1", "f4"}
if diff, equal := messagediff.PrettyDiff(expected, actual); !equal {
t.Errorf("SortNewestFirst() diff:\n%s", diff)
}
}
func BenchmarkJobQueueBump(b *testing.B) {
files := genFiles(10000)
+25 -30
View File
@@ -32,7 +32,7 @@ func TestRequestSimple(t *testing.T) {
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
tfs := fcfg.Filesystem(nil)
tfs := fcfg.Filesystem()
defer cleanupModelAndRemoveDir(m, tfs.URI())
// We listen for incoming index updates and trigger when we see one for
@@ -80,7 +80,7 @@ func TestSymlinkTraversalRead(t *testing.T) {
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
// We listen for incoming index updates and trigger when we see one for
// the expected test file.
@@ -123,7 +123,7 @@ func TestSymlinkTraversalWrite(t *testing.T) {
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
// We listen for incoming index updates and trigger when we see one for
// the expected names.
@@ -182,7 +182,7 @@ func TestRequestCreateTmpSymlink(t *testing.T) {
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
// We listen for incoming index updates and trigger when we see one for
// the expected test file.
@@ -229,7 +229,7 @@ func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
w, wCancel := newConfigWrapper(defaultCfgWrapper.RawCopy())
defer wCancel()
fcfg := w.FolderList()[0]
fss := fcfg.Filesystem(nil)
fss := fcfg.Filesystem()
fcfg.Type = ft
setFolder(t, w, fcfg)
m := setupModel(t, w)
@@ -358,7 +358,7 @@ func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
func TestIssue4841(t *testing.T) {
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
received := make(chan []protocol.FileInfo)
fc.setIndexFn(func(_ context.Context, _ string, fs []protocol.FileInfo) error {
@@ -407,7 +407,7 @@ func TestIssue4841(t *testing.T) {
func TestRescanIfHaveInvalidContent(t *testing.T) {
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
tfs := fcfg.Filesystem(nil)
tfs := fcfg.Filesystem()
defer cleanupModelAndRemoveDir(m, tfs.URI())
payload := []byte("hello")
@@ -465,9 +465,11 @@ func TestRescanIfHaveInvalidContent(t *testing.T) {
}
func TestParentDeletion(t *testing.T) {
t.Skip("flaky")
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
testFs := fcfg.Filesystem(nil)
testFs := fcfg.Filesystem()
defer cleanupModelAndRemoveDir(m, testFs.URI())
parent := "foo"
@@ -546,7 +548,7 @@ func TestRequestSymlinkWindows(t *testing.T) {
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
received := make(chan []protocol.FileInfo)
fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
@@ -623,7 +625,7 @@ func TestRequestRemoteRenameChanged(t *testing.T) {
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
tfs := fcfg.Filesystem(nil)
tfs := fcfg.Filesystem()
defer cleanupModel(m)
received := make(chan []protocol.FileInfo)
@@ -756,7 +758,7 @@ func TestRequestRemoteRenameChanged(t *testing.T) {
func TestRequestRemoteRenameConflict(t *testing.T) {
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
tfs := fcfg.Filesystem(nil)
tfs := fcfg.Filesystem()
defer cleanupModel(m)
recv := make(chan int)
@@ -846,7 +848,7 @@ func TestRequestRemoteRenameConflict(t *testing.T) {
func TestRequestDeleteChanged(t *testing.T) {
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
tfs := fcfg.Filesystem(nil)
tfs := fcfg.Filesystem()
defer cleanupModelAndRemoveDir(m, tfs.URI())
done := make(chan struct{})
@@ -960,7 +962,7 @@ func TestIgnoreDeleteUnignore(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
m := setupModel(t, w)
fss := fcfg.Filesystem(nil)
fss := fcfg.Filesystem()
defer cleanupModel(m)
folderIgnoresAlwaysReload(t, m, fcfg)
@@ -1054,7 +1056,7 @@ func TestIgnoreDeleteUnignore(t *testing.T) {
func TestRequestLastFileProgress(t *testing.T) {
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
tfs := fcfg.Filesystem(nil)
tfs := fcfg.Filesystem()
defer cleanupModelAndRemoveDir(m, tfs.URI())
done := make(chan struct{})
@@ -1089,7 +1091,7 @@ func TestRequestIndexSenderPause(t *testing.T) {
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
tfs := fcfg.Filesystem(nil)
tfs := fcfg.Filesystem()
defer cleanupModelAndRemoveDir(m, tfs.URI())
indexChan := make(chan []protocol.FileInfo)
@@ -1202,7 +1204,7 @@ func TestRequestIndexSenderPause(t *testing.T) {
func TestRequestIndexSenderClusterConfigBeforeStart(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
tfs := fcfg.Filesystem(nil)
tfs := fcfg.Filesystem()
dir1 := "foo"
dir2 := "bar"
@@ -1217,7 +1219,7 @@ func TestRequestIndexSenderClusterConfigBeforeStart(t *testing.T) {
// Add connection (sends incoming cluster config) before starting the new model
m = &testModel{
model: NewModel(m.cfg, m.id, m.db, m.protectedFiles, m.evLogger, protocol.NewKeyGenerator()).(*model),
model: NewModel(m.cfg, m.id, m.sdb, m.protectedFiles, m.evLogger, protocol.NewKeyGenerator()).(*model),
evCancel: m.evCancel,
stopped: make(chan struct{}),
}
@@ -1269,7 +1271,7 @@ func TestRequestReceiveEncrypted(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
tfs := fcfg.Filesystem(nil)
tfs := fcfg.Filesystem()
fcfg.Type = config.FolderTypeReceiveEncrypted
setFolder(t, w, fcfg)
@@ -1281,10 +1283,7 @@ func TestRequestReceiveEncrypted(t *testing.T) {
files := genFiles(2)
files[1].LocalFlags = protocol.FlagLocalReceiveOnly
m.mut.RLock()
fset := m.folderFiles[fcfg.ID]
m.mut.RUnlock()
fset.Update(protocol.LocalDeviceID, files)
m.sdb.Update(fcfg.ID, protocol.LocalDeviceID, files)
indexChan := make(chan []protocol.FileInfo, 10)
done := make(chan struct{})
@@ -1376,7 +1375,7 @@ func TestRequestGlobalInvalidToValid(t *testing.T) {
must(t, err)
waiter.Wait()
conn := addFakeConn(m, device2, fcfg.ID)
tfs := fcfg.Filesystem(nil)
tfs := fcfg.Filesystem()
defer cleanupModelAndRemoveDir(m, tfs.URI())
indexChan := make(chan []protocol.FileInfo, 1)
@@ -1402,7 +1401,7 @@ func TestRequestGlobalInvalidToValid(t *testing.T) {
file.SetIgnored()
m.IndexUpdate(conn, &protocol.IndexUpdate{Folder: fcfg.ID, Files: []protocol.FileInfo{prepareFileInfoForIndex(file)}})
// Wait for the ignored file to be received and possible pulled
// Wait for the ignored file to be received and possibly pulled
timeout := time.After(10 * time.Second)
globalUpdated := false
for {
@@ -1422,13 +1421,9 @@ func TestRequestGlobalInvalidToValid(t *testing.T) {
}
globalUpdated = true
}
snap, err := m.DBSnapshot(fcfg.ID)
if err != nil {
if s, err := m.NeedSize(fcfg.ID, protocol.LocalDeviceID); err != nil {
t.Fatal(err)
}
need := snap.NeedSize(protocol.LocalDeviceID)
snap.Release()
if need.Files == 0 {
} else if s.Files == 0 {
break
}
}
+4 -8
View File
@@ -6,10 +6,6 @@
package model
import (
"github.com/syncthing/syncthing/lib/fs"
)
// fatal is the required common interface between *testing.B and *testing.T
type fatal interface {
Fatal(...interface{})
@@ -23,9 +19,9 @@ func must(f fatal, err error) {
}
}
func mustRemove(f fatal, err error) {
f.Helper()
if err != nil && !fs.IsNotExist(err) {
f.Fatal(err)
func mustV[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
+14 -75
View File
@@ -12,9 +12,8 @@ import (
"testing"
"time"
"github.com/syncthing/syncthing/internal/db/sqlite"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/ignore"
@@ -149,11 +148,14 @@ type testModel struct {
func newModel(t testing.TB, cfg config.Wrapper, id protocol.DeviceID, protectedFiles []string) *testModel {
t.Helper()
evLogger := events.NewLogger()
ldb, err := db.NewLowlevel(backend.OpenMemory(), evLogger)
mdb, err := sqlite.OpenTemp()
if err != nil {
t.Fatal(err)
}
m := NewModel(cfg, id, ldb, protectedFiles, evLogger, protocol.NewKeyGenerator()).(*model)
t.Cleanup(func() {
mdb.Close()
})
m := NewModel(cfg, id, mdb, protectedFiles, evLogger, protocol.NewKeyGenerator()).(*model)
ctx, cancel := context.WithCancel(context.Background())
go evLogger.Serve(ctx)
return &testModel{
@@ -174,12 +176,6 @@ func (m *testModel) ServeBackground() {
<-m.started
}
func (m *testModel) testAvailability(folder string, file protocol.FileInfo, block protocol.BlockInfo) []Availability {
av, err := m.model.Availability(folder, file, block)
must(m.t, err)
return av
}
func (m *testModel) testCurrentFolderFile(folder string, file string) (protocol.FileInfo, bool) {
f, ok, err := m.model.CurrentFolderFile(folder, file)
must(m.t, err)
@@ -198,7 +194,7 @@ func cleanupModel(m *testModel) {
<-m.stopped
}
m.evCancel()
m.db.Close()
m.sdb.Close()
os.Remove(m.cfg.ConfigPath())
}
@@ -240,52 +236,6 @@ func (*alwaysChanged) Changed() bool {
return true
}
func localSize(t *testing.T, m Model, folder string) db.Counts {
t.Helper()
snap := dbSnapshot(t, m, folder)
defer snap.Release()
return snap.LocalSize()
}
func globalSize(t *testing.T, m Model, folder string) db.Counts {
t.Helper()
snap := dbSnapshot(t, m, folder)
defer snap.Release()
return snap.GlobalSize()
}
func receiveOnlyChangedSize(t *testing.T, m Model, folder string) db.Counts {
t.Helper()
snap := dbSnapshot(t, m, folder)
defer snap.Release()
return snap.ReceiveOnlyChangedSize()
}
func needSizeLocal(t *testing.T, m Model, folder string) db.Counts {
t.Helper()
snap := dbSnapshot(t, m, folder)
defer snap.Release()
return snap.NeedSize(protocol.LocalDeviceID)
}
func dbSnapshot(t *testing.T, m Model, folder string) *db.Snapshot {
t.Helper()
snap, err := m.DBSnapshot(folder)
if err != nil {
t.Fatal(err)
}
return snap
}
func fsetSnapshot(t *testing.T, fset *db.FileSet) *db.Snapshot {
t.Helper()
snap, err := fset.Snapshot()
if err != nil {
t.Fatal(err)
}
return snap
}
// Reach in and update the ignore matcher to one that always does
// reloads when asked to, instead of checking file mtimes. This is
// because we will be changing the files on disk often enough that the
@@ -293,10 +243,9 @@ func fsetSnapshot(t *testing.T, fset *db.FileSet) *db.Snapshot {
func folderIgnoresAlwaysReload(t testing.TB, m *testModel, fcfg config.FolderConfiguration) {
t.Helper()
m.removeFolder(fcfg)
fset := newFileSet(t, fcfg.ID, m.db)
ignores := ignore.New(fcfg.Filesystem(nil), ignore.WithCache(true), ignore.WithChangeDetector(newAlwaysChanged()))
ignores := ignore.New(fcfg.Filesystem(), ignore.WithCache(true), ignore.WithChangeDetector(newAlwaysChanged()))
m.mut.Lock()
m.addAndStartFolderLockedWithIgnores(fcfg, fset, ignores)
m.addAndStartFolderLockedWithIgnores(fcfg, ignores)
m.mut.Unlock()
}
@@ -319,12 +268,11 @@ func basicClusterConfig(local, remote protocol.DeviceID, folders ...string) *pro
}
func localIndexUpdate(m *testModel, folder string, fs []protocol.FileInfo) {
m.mut.RLock()
fset := m.folderFiles[folder]
m.mut.RUnlock()
fset.Update(protocol.LocalDeviceID, fs)
seq := fset.Sequence(protocol.LocalDeviceID)
m.sdb.Update(folder, protocol.LocalDeviceID, fs)
seq, err := m.sdb.GetDeviceSequence(folder, protocol.LocalDeviceID)
if err != nil {
panic(err)
}
filenames := make([]string, len(fs))
for i, file := range fs {
filenames[i] = file.Name
@@ -345,15 +293,6 @@ func newDeviceConfiguration(defaultCfg config.DeviceConfiguration, id protocol.D
return cfg
}
func newFileSet(t testing.TB, folder string, ldb *db.Lowlevel) *db.FileSet {
t.Helper()
fset, err := db.NewFileSet(folder, ldb)
if err != nil {
t.Fatal(err)
}
return fset
}
func replace(t testing.TB, w config.Wrapper, to config.Configuration) {
t.Helper()
waiter, err := w.Modify(func(cfg *config.Configuration) {