all: Transactionalize db.FileSet (fixes #5952) (#6239)

This commit is contained in:
Simon Frei
2020-01-21 18:23:08 +01:00
committed by Jakob Borg
parent d62a0cf692
commit 08f0e125ef
23 changed files with 957 additions and 891 deletions
+21 -7
View File
@@ -145,10 +145,12 @@ func BenchmarkNeedHalf(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
benchS.WithNeed(protocol.LocalDeviceID, func(fi db.FileIntf) bool {
snap := benchS.Snapshot()
snap.WithNeed(protocol.LocalDeviceID, func(fi db.FileIntf) bool {
count++
return true
})
snap.Release()
if count != len(secondHalf) {
b.Errorf("wrong length %d != %d", count, len(secondHalf))
}
@@ -167,10 +169,12 @@ func BenchmarkNeedHalfRemote(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
fset.WithNeed(remoteDevice0, func(fi db.FileIntf) bool {
snap := fset.Snapshot()
snap.WithNeed(remoteDevice0, func(fi db.FileIntf) bool {
count++
return true
})
snap.Release()
if count != len(secondHalf) {
b.Errorf("wrong length %d != %d", count, len(secondHalf))
}
@@ -186,10 +190,12 @@ func BenchmarkHave(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
benchS.WithHave(protocol.LocalDeviceID, func(fi db.FileIntf) bool {
snap := benchS.Snapshot()
snap.WithHave(protocol.LocalDeviceID, func(fi db.FileIntf) bool {
count++
return true
})
snap.Release()
if count != len(firstHalf) {
b.Errorf("wrong length %d != %d", count, len(firstHalf))
}
@@ -205,10 +211,12 @@ func BenchmarkGlobal(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
benchS.WithGlobal(func(fi db.FileIntf) bool {
snap := benchS.Snapshot()
snap.WithGlobal(func(fi db.FileIntf) bool {
count++
return true
})
snap.Release()
if count != len(files) {
b.Errorf("wrong length %d != %d", count, len(files))
}
@@ -224,10 +232,12 @@ func BenchmarkNeedHalfTruncated(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
benchS.WithNeedTruncated(protocol.LocalDeviceID, func(fi db.FileIntf) bool {
snap := benchS.Snapshot()
snap.WithNeedTruncated(protocol.LocalDeviceID, func(fi db.FileIntf) bool {
count++
return true
})
snap.Release()
if count != len(secondHalf) {
b.Errorf("wrong length %d != %d", count, len(secondHalf))
}
@@ -243,10 +253,12 @@ func BenchmarkHaveTruncated(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
benchS.WithHaveTruncated(protocol.LocalDeviceID, func(fi db.FileIntf) bool {
snap := benchS.Snapshot()
snap.WithHaveTruncated(protocol.LocalDeviceID, func(fi db.FileIntf) bool {
count++
return true
})
snap.Release()
if count != len(firstHalf) {
b.Errorf("wrong length %d != %d", count, len(firstHalf))
}
@@ -262,10 +274,12 @@ func BenchmarkGlobalTruncated(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
benchS.WithGlobalTruncated(func(fi db.FileIntf) bool {
snap := benchS.Snapshot()
snap.WithGlobalTruncated(func(fi db.FileIntf) bool {
count++
return true
})
snap.Release()
if count != len(files) {
b.Errorf("wrong length %d != %d", count, len(files))
}
+21 -7
View File
@@ -72,7 +72,9 @@ func TestIgnoredFiles(t *testing.T) {
// Local files should have the "ignored" bit in addition to just being
// generally invalid if we want to look at the simulation of that bit.
fi, ok := fs.Get(protocol.LocalDeviceID, "foo")
snap := fs.Snapshot()
defer snap.Release()
fi, ok := snap.Get(protocol.LocalDeviceID, "foo")
if !ok {
t.Fatal("foo should exist")
}
@@ -83,7 +85,7 @@ func TestIgnoredFiles(t *testing.T) {
t.Error("foo should be ignored")
}
fi, ok = fs.Get(protocol.LocalDeviceID, "bar")
fi, ok = snap.Get(protocol.LocalDeviceID, "bar")
if !ok {
t.Fatal("bar should exist")
}
@@ -97,7 +99,7 @@ func TestIgnoredFiles(t *testing.T) {
// Remote files have the invalid bit as usual, and the IsInvalid() method
// should pick this up too.
fi, ok = fs.Get(protocol.DeviceID{42}, "baz")
fi, ok = snap.Get(protocol.DeviceID{42}, "baz")
if !ok {
t.Fatal("baz should exist")
}
@@ -108,7 +110,7 @@ func TestIgnoredFiles(t *testing.T) {
t.Error("baz should be invalid")
}
fi, ok = fs.Get(protocol.DeviceID{42}, "quux")
fi, ok = snap.Get(protocol.DeviceID{42}, "quux")
if !ok {
t.Fatal("quux should exist")
}
@@ -167,7 +169,11 @@ func TestUpdate0to3(t *testing.T) {
t.Fatal(err)
}
if _, ok, err := db.getFileDirty(folder, protocol.LocalDeviceID[:], []byte(slashPrefixed)); err != nil {
trans, err := db.newReadOnlyTransaction()
if err != nil {
t.Fatal(err)
}
if _, ok, err := trans.getFile(folder, protocol.LocalDeviceID[:], []byte(slashPrefixed)); err != nil {
t.Fatal(err)
} else if ok {
t.Error("File prefixed by '/' was not removed during transition to schema 1")
@@ -186,7 +192,11 @@ func TestUpdate0to3(t *testing.T) {
}
found := false
_ = db.withHaveSequence(folder, 0, func(fi FileIntf) bool {
trans, err = db.newReadOnlyTransaction()
if err != nil {
t.Fatal(err)
}
_ = trans.withHaveSequence(folder, 0, func(fi FileIntf) bool {
f := fi.(protocol.FileInfo)
l.Infoln(f)
if found {
@@ -213,7 +223,11 @@ func TestUpdate0to3(t *testing.T) {
haveUpdate0to3[remoteDevice1][0].Name: haveUpdate0to3[remoteDevice1][0],
haveUpdate0to3[remoteDevice0][2].Name: haveUpdate0to3[remoteDevice0][2],
}
_ = db.withNeed(folder, protocol.LocalDeviceID[:], false, func(fi FileIntf) bool {
trans, err = db.newReadOnlyTransaction()
if err != nil {
t.Fatal(err)
}
_ = trans.withNeed(folder, protocol.LocalDeviceID[:], false, func(fi FileIntf) bool {
e, ok := need[fi.FileName()]
if !ok {
t.Error("Got unexpected needed file:", fi.FileName())
-399
View File
@@ -194,405 +194,6 @@ func (db *Lowlevel) updateLocalFiles(folder []byte, fs []protocol.FileInfo, meta
return t.commit()
}
func (db *Lowlevel) withHave(folder, device, prefix []byte, truncate bool, fn Iterator) error {
t, err := db.newReadOnlyTransaction()
if err != nil {
return err
}
defer t.close()
if len(prefix) > 0 {
unslashedPrefix := prefix
if bytes.HasSuffix(prefix, []byte{'/'}) {
unslashedPrefix = unslashedPrefix[:len(unslashedPrefix)-1]
} else {
prefix = append(prefix, '/')
}
key, err := db.keyer.GenerateDeviceFileKey(nil, folder, device, unslashedPrefix)
if err != nil {
return err
}
if f, ok, err := t.getFileTrunc(key, true); err != nil {
return err
} else if ok && !fn(f) {
return nil
}
}
key, err := db.keyer.GenerateDeviceFileKey(nil, folder, device, prefix)
if err != nil {
return err
}
dbi, err := t.NewPrefixIterator(key)
if err != nil {
return err
}
defer dbi.Release()
for dbi.Next() {
name := db.keyer.NameFromDeviceFileKey(dbi.Key())
if len(prefix) > 0 && !bytes.HasPrefix(name, prefix) {
return nil
}
f, err := unmarshalTrunc(dbi.Value(), truncate)
if err != nil {
l.Debugln("unmarshal error:", err)
continue
}
if !fn(f) {
return nil
}
}
return dbi.Error()
}
func (db *Lowlevel) withHaveSequence(folder []byte, startSeq int64, fn Iterator) error {
t, err := db.newReadOnlyTransaction()
if err != nil {
return err
}
defer t.close()
first, err := db.keyer.GenerateSequenceKey(nil, folder, startSeq)
if err != nil {
return err
}
last, err := db.keyer.GenerateSequenceKey(nil, folder, maxInt64)
if err != nil {
return err
}
dbi, err := t.NewRangeIterator(first, last)
if err != nil {
return err
}
defer dbi.Release()
for dbi.Next() {
f, ok, err := t.getFileByKey(dbi.Value())
if err != nil {
return err
}
if !ok {
l.Debugln("missing file for sequence number", db.keyer.SequenceFromSequenceKey(dbi.Key()))
continue
}
if shouldDebug() {
if seq := db.keyer.SequenceFromSequenceKey(dbi.Key()); f.Sequence != seq {
l.Warnf("Sequence index corruption (folder %v, file %v): sequence %d != expected %d", string(folder), f.Name, f.Sequence, seq)
panic("sequence index corruption")
}
}
if !fn(f) {
return nil
}
}
return dbi.Error()
}
func (db *Lowlevel) withAllFolderTruncated(folder []byte, fn func(device []byte, f FileInfoTruncated) bool) error {
t, err := db.newReadWriteTransaction()
if err != nil {
return err
}
defer t.close()
key, err := db.keyer.GenerateDeviceFileKey(nil, folder, nil, nil)
if err != nil {
return err
}
dbi, err := t.NewPrefixIterator(key.WithoutNameAndDevice())
if err != nil {
return err
}
defer dbi.Release()
var gk, keyBuf []byte
for dbi.Next() {
device, ok := db.keyer.DeviceFromDeviceFileKey(dbi.Key())
if !ok {
// Not having the device in the index is bad. Clear it.
if err := t.Delete(dbi.Key()); err != nil {
return err
}
continue
}
var f FileInfoTruncated
// The iterator function may keep a reference to the unmarshalled
// struct, which in turn references the buffer it was unmarshalled
// from. dbi.Value() just returns an internal slice that it reuses, so
// we need to copy it.
err := f.Unmarshal(append([]byte{}, dbi.Value()...))
if err != nil {
return err
}
switch f.Name {
case "", ".", "..", "/": // A few obviously invalid filenames
l.Infof("Dropping invalid filename %q from database", f.Name)
name := []byte(f.Name)
gk, err = db.keyer.GenerateGlobalVersionKey(gk, folder, name)
if err != nil {
return err
}
keyBuf, err = t.removeFromGlobal(gk, keyBuf, folder, device, name, nil)
if err != nil {
return err
}
if err := t.Delete(dbi.Key()); err != nil {
return err
}
continue
}
if !fn(device, f) {
return nil
}
}
if err := dbi.Error(); err != nil {
return err
}
return t.commit()
}
func (db *Lowlevel) getFileDirty(folder, device, file []byte) (protocol.FileInfo, bool, error) {
t, err := db.newReadOnlyTransaction()
if err != nil {
return protocol.FileInfo{}, false, err
}
defer t.close()
return t.getFile(folder, device, file)
}
func (db *Lowlevel) getGlobalDirty(folder, file []byte, truncate bool) (FileIntf, bool, error) {
t, err := db.newReadOnlyTransaction()
if err != nil {
return nil, false, err
}
defer t.close()
_, f, ok, err := t.getGlobal(nil, folder, file, truncate)
return f, ok, err
}
func (db *Lowlevel) withGlobal(folder, prefix []byte, truncate bool, fn Iterator) error {
t, err := db.newReadOnlyTransaction()
if err != nil {
return err
}
defer t.close()
if len(prefix) > 0 {
unslashedPrefix := prefix
if bytes.HasSuffix(prefix, []byte{'/'}) {
unslashedPrefix = unslashedPrefix[:len(unslashedPrefix)-1]
} else {
prefix = append(prefix, '/')
}
if _, f, ok, err := t.getGlobal(nil, folder, unslashedPrefix, truncate); err != nil {
return err
} else if ok && !fn(f) {
return nil
}
}
key, err := db.keyer.GenerateGlobalVersionKey(nil, folder, prefix)
if err != nil {
return err
}
dbi, err := t.NewPrefixIterator(key)
if err != nil {
return err
}
defer dbi.Release()
var dk []byte
for dbi.Next() {
name := db.keyer.NameFromGlobalVersionKey(dbi.Key())
if len(prefix) > 0 && !bytes.HasPrefix(name, prefix) {
return nil
}
vl, ok := unmarshalVersionList(dbi.Value())
if !ok {
continue
}
dk, err = db.keyer.GenerateDeviceFileKey(dk, folder, vl.Versions[0].Device, name)
if err != nil {
return err
}
f, ok, err := t.getFileTrunc(dk, truncate)
if err != nil {
return err
}
if !ok {
continue
}
if !fn(f) {
return nil
}
}
if err != nil {
return err
}
return dbi.Error()
}
func (db *Lowlevel) availability(folder, file []byte) ([]protocol.DeviceID, error) {
k, err := db.keyer.GenerateGlobalVersionKey(nil, folder, file)
if err != nil {
return nil, err
}
bs, err := db.Get(k)
if backend.IsNotFound(err) {
return nil, nil
}
if err != nil {
return nil, err
}
vl, ok := unmarshalVersionList(bs)
if !ok {
return nil, nil
}
var devices []protocol.DeviceID
for _, v := range vl.Versions {
if !v.Version.Equal(vl.Versions[0].Version) {
break
}
if v.Invalid {
continue
}
n := protocol.DeviceIDFromBytes(v.Device)
devices = append(devices, n)
}
return devices, nil
}
func (db *Lowlevel) withNeed(folder, device []byte, truncate bool, fn Iterator) error {
if bytes.Equal(device, protocol.LocalDeviceID[:]) {
return db.withNeedLocal(folder, truncate, fn)
}
t, err := db.newReadOnlyTransaction()
if err != nil {
return err
}
defer t.close()
key, err := db.keyer.GenerateGlobalVersionKey(nil, folder, nil)
if err != nil {
return err
}
dbi, err := t.NewPrefixIterator(key.WithoutName())
if err != nil {
return err
}
defer dbi.Release()
var dk []byte
devID := protocol.DeviceIDFromBytes(device)
for dbi.Next() {
vl, ok := unmarshalVersionList(dbi.Value())
if !ok {
continue
}
haveFV, have := vl.Get(device)
// XXX: This marks Concurrent (i.e. conflicting) changes as
// needs. Maybe we should do that, but it needs special
// handling in the puller.
if have && haveFV.Version.GreaterEqual(vl.Versions[0].Version) {
continue
}
name := db.keyer.NameFromGlobalVersionKey(dbi.Key())
needVersion := vl.Versions[0].Version
needDevice := protocol.DeviceIDFromBytes(vl.Versions[0].Device)
for i := range vl.Versions {
if !vl.Versions[i].Version.Equal(needVersion) {
// We haven't found a valid copy of the file with the needed version.
break
}
if vl.Versions[i].Invalid {
// The file is marked invalid, don't use it.
continue
}
dk, err = db.keyer.GenerateDeviceFileKey(dk, folder, vl.Versions[i].Device, name)
if err != nil {
return err
}
gf, ok, err := t.getFileTrunc(dk, truncate)
if err != nil {
return err
}
if !ok {
continue
}
if gf.IsDeleted() && !have {
// We don't need deleted files that we don't have
break
}
l.Debugf("need folder=%q device=%v name=%q have=%v invalid=%v haveV=%v globalV=%v globalDev=%v", folder, devID, name, have, haveFV.Invalid, haveFV.Version, needVersion, needDevice)
if !fn(gf) {
return nil
}
// This file is handled, no need to look further in the version list
break
}
}
return dbi.Error()
}
func (db *Lowlevel) withNeedLocal(folder []byte, truncate bool, fn Iterator) error {
t, err := db.newReadOnlyTransaction()
if err != nil {
return err
}
defer t.close()
key, err := db.keyer.GenerateNeedFileKey(nil, folder, nil)
if err != nil {
return err
}
dbi, err := t.NewPrefixIterator(key.WithoutName())
if err != nil {
return err
}
defer dbi.Release()
var keyBuf []byte
var f FileIntf
var ok bool
for dbi.Next() {
keyBuf, f, ok, err = t.getGlobal(keyBuf, folder, db.keyer.NameFromGlobalVersionKey(dbi.Key()), truncate)
if err != nil {
return err
}
if !ok {
continue
}
if !fn(f) {
return nil
}
}
return dbi.Error()
}
func (db *Lowlevel) dropFolder(folder []byte) error {
t, err := db.newReadWriteTransaction()
if err != nil {
+54 -23
View File
@@ -15,12 +15,16 @@ import (
"github.com/syncthing/syncthing/lib/sync"
)
// metadataTracker keeps metadata on a per device, per local flag basis.
type metadataTracker struct {
mut sync.RWMutex
type countsMap struct {
counts CountsSet
indexes map[metaKey]int // device ID + local flags -> index in counts
dirty bool
}
// metadataTracker keeps metadata on a per device, per local flag basis.
type metadataTracker struct {
countsMap
mut sync.RWMutex
dirty bool
}
type metaKey struct {
@@ -30,8 +34,10 @@ type metaKey struct {
func newMetadataTracker() *metadataTracker {
return &metadataTracker{
mut: sync.NewRWMutex(),
indexes: make(map[metaKey]int),
mut: sync.NewRWMutex(),
countsMap: countsMap{
indexes: make(map[metaKey]int),
},
}
}
@@ -49,7 +55,7 @@ func (m *metadataTracker) Unmarshal(bs []byte) error {
return nil
}
// Unmarshal returns the protobuf representation of the metadataTracker
// Marshal returns the protobuf representation of the metadataTracker
func (m *metadataTracker) Marshal() ([]byte, error) {
return m.counts.Marshal()
}
@@ -260,16 +266,11 @@ func (m *metadataTracker) resetCounts(dev protocol.DeviceID) {
m.mut.Unlock()
}
// Counts returns the counts for the given device ID and flag. `flag` should
// be zero or have exactly one bit set.
func (m *metadataTracker) Counts(dev protocol.DeviceID, flag uint32) Counts {
func (m *countsMap) Counts(dev protocol.DeviceID, flag uint32) Counts {
if bits.OnesCount32(flag) > 1 {
panic("incorrect usage: set at most one bit in flag")
}
m.mut.RLock()
defer m.mut.RUnlock()
idx, ok := m.indexes[metaKey{dev, flag}]
if !ok {
return Counts{}
@@ -278,6 +279,37 @@ func (m *metadataTracker) Counts(dev protocol.DeviceID, flag uint32) Counts {
return m.counts.Counts[idx]
}
// Counts returns the counts for the given device ID and flag. `flag` should
// be zero or have exactly one bit set.
func (m *metadataTracker) Counts(dev protocol.DeviceID, flag uint32) Counts {
m.mut.RLock()
defer m.mut.RUnlock()
return m.countsMap.Counts(dev, flag)
}
// Snapshot returns a copy of the metadata for reading.
func (m *metadataTracker) Snapshot() *countsMap {
m.mut.RLock()
defer m.mut.RUnlock()
c := &countsMap{
counts: CountsSet{
Counts: make([]Counts, len(m.counts.Counts)),
Created: m.counts.Created,
},
indexes: make(map[metaKey]int, len(m.indexes)),
}
for k, v := range m.indexes {
c.indexes[k] = v
}
for i := range m.counts.Counts {
c.counts.Counts[i] = m.counts.Counts[i]
}
return c
}
// nextLocalSeq allocates a new local sequence number
func (m *metadataTracker) nextLocalSeq() int64 {
m.mut.Lock()
@@ -291,26 +323,25 @@ func (m *metadataTracker) nextLocalSeq() int64 {
// devices returns the list of devices tracked, excluding the local device
// (which we don't know the ID of)
func (m *metadataTracker) devices() []protocol.DeviceID {
devs := make(map[protocol.DeviceID]struct{}, len(m.counts.Counts))
m.mut.RLock()
defer m.mut.RUnlock()
return m.countsMap.devices()
}
func (m *countsMap) devices() []protocol.DeviceID {
devs := make([]protocol.DeviceID, 0, len(m.counts.Counts))
for _, dev := range m.counts.Counts {
if dev.Sequence > 0 {
id := protocol.DeviceIDFromBytes(dev.DeviceID)
if id == protocol.GlobalDeviceID || id == protocol.LocalDeviceID {
continue
}
devs[id] = struct{}{}
devs = append(devs, id)
}
}
m.mut.RUnlock()
devList := make([]protocol.DeviceID, 0, len(devs))
for dev := range devs {
devList = append(devList, dev)
}
return devList
return devs
}
func (m *metadataTracker) Created() time.Time {
+4 -4
View File
@@ -228,7 +228,7 @@ func (db *schemaUpdater) updateSchema1to2() error {
for _, folderStr := range db.ListFolders() {
folder := []byte(folderStr)
var putErr error
err := db.withHave(folder, protocol.LocalDeviceID[:], nil, true, func(f FileIntf) bool {
err := t.withHave(folder, protocol.LocalDeviceID[:], nil, true, func(f FileIntf) bool {
sk, putErr = db.keyer.GenerateSequenceKey(sk, folder, f.SequenceNo())
if putErr != nil {
return false
@@ -263,7 +263,7 @@ func (db *schemaUpdater) updateSchema2to3() error {
for _, folderStr := range db.ListFolders() {
folder := []byte(folderStr)
var putErr error
err := db.withGlobal(folder, nil, true, func(f FileIntf) bool {
err := t.withGlobal(folder, nil, true, func(f FileIntf) bool {
name := []byte(f.FileName())
dk, putErr = db.keyer.GenerateDeviceFileKey(dk, folder, protocol.LocalDeviceID[:], name)
if putErr != nil {
@@ -339,7 +339,7 @@ func (db *schemaUpdater) updateSchema5to6() error {
for _, folderStr := range db.ListFolders() {
folder := []byte(folderStr)
var putErr error
err := db.withHave(folder, protocol.LocalDeviceID[:], nil, false, func(f FileIntf) bool {
err := t.withHave(folder, protocol.LocalDeviceID[:], nil, false, func(f FileIntf) bool {
if !f.IsInvalid() {
return true
}
@@ -382,7 +382,7 @@ func (db *schemaUpdater) updateSchema6to7() error {
for _, folderStr := range db.ListFolders() {
folder := []byte(folderStr)
var delErr error
err := db.withNeedLocal(folder, false, func(f FileIntf) bool {
err := t.withNeedLocal(folder, false, func(f FileIntf) bool {
name := []byte(f.FileName())
global := f.(protocol.FileInfo)
gk, delErr = db.keyer.GenerateGlobalVersionKey(gk, folder, name)
+144 -34
View File
@@ -105,8 +105,12 @@ func (s *FileSet) recalcCounts() error {
return err
}
t, err := s.db.newReadWriteTransaction()
if err != nil {
return err
}
var deviceID protocol.DeviceID
err := s.db.withAllFolderTruncated([]byte(s.folder), func(device []byte, f FileInfoTruncated) bool {
err = t.withAllFolderTruncated([]byte(s.folder), func(device []byte, f FileInfoTruncated) bool {
copy(deviceID[:], device)
s.meta.addFile(deviceID, f)
return true
@@ -183,75 +187,97 @@ func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
}
}
func (s *FileSet) WithNeed(device protocol.DeviceID, fn Iterator) {
type Snapshot struct {
folder string
t readOnlyTransaction
meta *countsMap
}
func (s *FileSet) Snapshot() *Snapshot {
t, err := s.db.newReadOnlyTransaction()
if err != nil {
panic(err)
}
return &Snapshot{
folder: s.folder,
t: t,
meta: s.meta.Snapshot(),
}
}
func (s *Snapshot) Release() {
s.t.close()
}
func (s *Snapshot) WithNeed(device protocol.DeviceID, fn Iterator) {
l.Debugf("%s WithNeed(%v)", s.folder, device)
if err := s.db.withNeed([]byte(s.folder), device[:], false, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
if err := s.t.withNeed([]byte(s.folder), device[:], false, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
func (s *FileSet) WithNeedTruncated(device protocol.DeviceID, fn Iterator) {
func (s *Snapshot) WithNeedTruncated(device protocol.DeviceID, fn Iterator) {
l.Debugf("%s WithNeedTruncated(%v)", s.folder, device)
if err := s.db.withNeed([]byte(s.folder), device[:], true, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
if err := s.t.withNeed([]byte(s.folder), device[:], true, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
func (s *FileSet) WithHave(device protocol.DeviceID, fn Iterator) {
func (s *Snapshot) WithHave(device protocol.DeviceID, fn Iterator) {
l.Debugf("%s WithHave(%v)", s.folder, device)
if err := s.db.withHave([]byte(s.folder), device[:], nil, false, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
if err := s.t.withHave([]byte(s.folder), device[:], nil, false, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
func (s *FileSet) WithHaveTruncated(device protocol.DeviceID, fn Iterator) {
func (s *Snapshot) WithHaveTruncated(device protocol.DeviceID, fn Iterator) {
l.Debugf("%s WithHaveTruncated(%v)", s.folder, device)
if err := s.db.withHave([]byte(s.folder), device[:], nil, true, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
if err := s.t.withHave([]byte(s.folder), device[:], nil, true, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
func (s *FileSet) WithHaveSequence(startSeq int64, fn Iterator) {
func (s *Snapshot) WithHaveSequence(startSeq int64, fn Iterator) {
l.Debugf("%s WithHaveSequence(%v)", s.folder, startSeq)
if err := s.db.withHaveSequence([]byte(s.folder), startSeq, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
if err := s.t.withHaveSequence([]byte(s.folder), startSeq, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
// Except for an item with a path equal to prefix, only children of prefix are iterated.
// E.g. for prefix "dir", "dir/file" is iterated, but "dir.file" is not.
func (s *FileSet) WithPrefixedHaveTruncated(device protocol.DeviceID, prefix string, fn Iterator) {
func (s *Snapshot) WithPrefixedHaveTruncated(device protocol.DeviceID, prefix string, fn Iterator) {
l.Debugf(`%s WithPrefixedHaveTruncated(%v, "%v")`, s.folder, device, prefix)
if err := s.db.withHave([]byte(s.folder), device[:], []byte(osutil.NormalizedFilename(prefix)), true, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
if err := s.t.withHave([]byte(s.folder), device[:], []byte(osutil.NormalizedFilename(prefix)), true, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
func (s *FileSet) WithGlobal(fn Iterator) {
func (s *Snapshot) WithGlobal(fn Iterator) {
l.Debugf("%s WithGlobal()", s.folder)
if err := s.db.withGlobal([]byte(s.folder), nil, false, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
if err := s.t.withGlobal([]byte(s.folder), nil, false, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
func (s *FileSet) WithGlobalTruncated(fn Iterator) {
func (s *Snapshot) WithGlobalTruncated(fn Iterator) {
l.Debugf("%s WithGlobalTruncated()", s.folder)
if err := s.db.withGlobal([]byte(s.folder), nil, true, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
if err := s.t.withGlobal([]byte(s.folder), nil, true, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
// Except for an item with a path equal to prefix, only children of prefix are iterated.
// E.g. for prefix "dir", "dir/file" is iterated, but "dir.file" is not.
func (s *FileSet) WithPrefixedGlobalTruncated(prefix string, fn Iterator) {
func (s *Snapshot) WithPrefixedGlobalTruncated(prefix string, fn Iterator) {
l.Debugf(`%s WithPrefixedGlobalTruncated("%v")`, s.folder, prefix)
if err := s.db.withGlobal([]byte(s.folder), []byte(osutil.NormalizedFilename(prefix)), true, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
if err := s.t.withGlobal([]byte(s.folder), []byte(osutil.NormalizedFilename(prefix)), true, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
func (s *FileSet) Get(device protocol.DeviceID, file string) (protocol.FileInfo, bool) {
f, ok, err := s.db.getFileDirty([]byte(s.folder), device[:], []byte(osutil.NormalizedFilename(file)))
func (s *Snapshot) Get(device protocol.DeviceID, file string) (protocol.FileInfo, bool) {
f, ok, err := s.t.getFile([]byte(s.folder), device[:], []byte(osutil.NormalizedFilename(file)))
if backend.IsClosed(err) {
return protocol.FileInfo{}, false
} else if err != nil {
@@ -261,8 +287,8 @@ func (s *FileSet) Get(device protocol.DeviceID, file string) (protocol.FileInfo,
return f, ok
}
func (s *FileSet) GetGlobal(file string) (protocol.FileInfo, bool) {
fi, ok, err := s.db.getGlobalDirty([]byte(s.folder), []byte(osutil.NormalizedFilename(file)), false)
func (s *Snapshot) GetGlobal(file string) (protocol.FileInfo, bool) {
_, fi, ok, err := s.t.getGlobal(nil, []byte(s.folder), []byte(osutil.NormalizedFilename(file)), false)
if backend.IsClosed(err) {
return protocol.FileInfo{}, false
} else if err != nil {
@@ -276,8 +302,8 @@ func (s *FileSet) GetGlobal(file string) (protocol.FileInfo, bool) {
return f, true
}
func (s *FileSet) GetGlobalTruncated(file string) (FileInfoTruncated, bool) {
fi, ok, err := s.db.getGlobalDirty([]byte(s.folder), []byte(osutil.NormalizedFilename(file)), true)
func (s *Snapshot) GetGlobalTruncated(file string) (FileInfoTruncated, bool) {
_, fi, ok, err := s.t.getGlobal(nil, []byte(s.folder), []byte(osutil.NormalizedFilename(file)), true)
if backend.IsClosed(err) {
return FileInfoTruncated{}, false
} else if err != nil {
@@ -291,8 +317,8 @@ func (s *FileSet) GetGlobalTruncated(file string) (FileInfoTruncated, bool) {
return f, true
}
func (s *FileSet) Availability(file string) []protocol.DeviceID {
av, err := s.db.availability([]byte(s.folder), []byte(osutil.NormalizedFilename(file)))
func (s *Snapshot) Availability(file string) []protocol.DeviceID {
av, err := s.t.availability([]byte(s.folder), []byte(osutil.NormalizedFilename(file)))
if backend.IsClosed(err) {
return nil
} else if err != nil {
@@ -301,26 +327,110 @@ func (s *FileSet) Availability(file string) []protocol.DeviceID {
return av
}
func (s *FileSet) Sequence(device protocol.DeviceID) int64 {
return s.meta.Sequence(device)
func (s *Snapshot) Sequence(device protocol.DeviceID) int64 {
return s.meta.Counts(device, 0).Sequence
}
func (s *FileSet) LocalSize() Counts {
// RemoteSequence returns the change version for the given folder, as
// sent by remote peers. This is guaranteed to increment if the contents of
// the remote or global folder has changed.
func (s *Snapshot) RemoteSequence() int64 {
var ver int64
for _, device := range s.meta.devices() {
ver += s.Sequence(device)
}
return ver
}
func (s *Snapshot) LocalSize() Counts {
local := s.meta.Counts(protocol.LocalDeviceID, 0)
recvOnlyChanged := s.meta.Counts(protocol.LocalDeviceID, protocol.FlagLocalReceiveOnly)
return local.Add(recvOnlyChanged)
return local.Add(s.ReceiveOnlyChangedSize())
}
func (s *FileSet) ReceiveOnlyChangedSize() Counts {
func (s *Snapshot) ReceiveOnlyChangedSize() Counts {
return s.meta.Counts(protocol.LocalDeviceID, protocol.FlagLocalReceiveOnly)
}
func (s *FileSet) GlobalSize() Counts {
func (s *Snapshot) GlobalSize() Counts {
global := s.meta.Counts(protocol.GlobalDeviceID, 0)
recvOnlyChanged := s.meta.Counts(protocol.GlobalDeviceID, protocol.FlagLocalReceiveOnly)
return global.Add(recvOnlyChanged)
}
func (s *Snapshot) NeedSize() Counts {
var result Counts
s.WithNeedTruncated(protocol.LocalDeviceID, func(f FileIntf) bool {
switch {
case f.IsDeleted():
result.Deleted++
case f.IsDirectory():
result.Directories++
case f.IsSymlink():
result.Symlinks++
default:
result.Files++
result.Bytes += f.FileSize()
}
return true
})
return result
}
// LocalChangedFiles returns a paginated list of currently needed files in
// progress, queued, and to be queued on next puller iteration, as well as the
// total number of files currently needed.
func (s *Snapshot) LocalChangedFiles(page, perpage int) []FileInfoTruncated {
if s.ReceiveOnlyChangedSize().TotalItems() == 0 {
return nil
}
files := make([]FileInfoTruncated, 0, perpage)
skip := (page - 1) * perpage
get := perpage
s.WithHaveTruncated(protocol.LocalDeviceID, func(f FileIntf) bool {
if !f.IsReceiveOnlyChanged() {
return true
}
if skip > 0 {
skip--
return true
}
ft := f.(FileInfoTruncated)
files = append(files, ft)
get--
return get > 0
})
return files
}
// RemoteNeedFolderFiles returns paginated list of currently needed files in
// progress, queued, and to be queued on next puller iteration, as well as the
// total number of files currently needed.
func (s *Snapshot) RemoteNeedFolderFiles(device protocol.DeviceID, page, perpage int) []FileInfoTruncated {
files := make([]FileInfoTruncated, 0, perpage)
skip := (page - 1) * perpage
get := perpage
s.WithNeedTruncated(device, func(f FileIntf) bool {
if skip > 0 {
skip--
return true
}
files = append(files, f.(FileInfoTruncated))
get--
return get > 0
})
return files
}
func (s *FileSet) Sequence(device protocol.DeviceID) int64 {
return s.meta.Sequence(device)
}
func (s *FileSet) IndexID(device protocol.DeviceID) protocol.IndexID {
id, err := s.db.getIndexID(device[:], []byte(s.folder))
if backend.IsClosed(err) {
+93 -52
View File
@@ -46,7 +46,9 @@ func genBlocks(n int) []protocol.BlockInfo {
func globalList(s *db.FileSet) []protocol.FileInfo {
var fs []protocol.FileInfo
s.WithGlobal(func(fi db.FileIntf) bool {
snap := s.Snapshot()
defer snap.Release()
snap.WithGlobal(func(fi db.FileIntf) bool {
f := fi.(protocol.FileInfo)
fs = append(fs, f)
return true
@@ -55,7 +57,9 @@ func globalList(s *db.FileSet) []protocol.FileInfo {
}
func globalListPrefixed(s *db.FileSet, prefix string) []db.FileInfoTruncated {
var fs []db.FileInfoTruncated
s.WithPrefixedGlobalTruncated(prefix, func(fi db.FileIntf) bool {
snap := s.Snapshot()
defer snap.Release()
snap.WithPrefixedGlobalTruncated(prefix, func(fi db.FileIntf) bool {
f := fi.(db.FileInfoTruncated)
fs = append(fs, f)
return true
@@ -65,7 +69,9 @@ func globalListPrefixed(s *db.FileSet, prefix string) []db.FileInfoTruncated {
func haveList(s *db.FileSet, n protocol.DeviceID) []protocol.FileInfo {
var fs []protocol.FileInfo
s.WithHave(n, func(fi db.FileIntf) bool {
snap := s.Snapshot()
defer snap.Release()
snap.WithHave(n, func(fi db.FileIntf) bool {
f := fi.(protocol.FileInfo)
fs = append(fs, f)
return true
@@ -75,7 +81,9 @@ func haveList(s *db.FileSet, n protocol.DeviceID) []protocol.FileInfo {
func haveListPrefixed(s *db.FileSet, n protocol.DeviceID, prefix string) []db.FileInfoTruncated {
var fs []db.FileInfoTruncated
s.WithPrefixedHaveTruncated(n, prefix, func(fi db.FileIntf) bool {
snap := s.Snapshot()
defer snap.Release()
snap.WithPrefixedHaveTruncated(n, prefix, func(fi db.FileIntf) bool {
f := fi.(db.FileInfoTruncated)
fs = append(fs, f)
return true
@@ -85,7 +93,9 @@ func haveListPrefixed(s *db.FileSet, n protocol.DeviceID, prefix string) []db.Fi
func needList(s *db.FileSet, n protocol.DeviceID) []protocol.FileInfo {
var fs []protocol.FileInfo
s.WithNeed(n, func(fi db.FileIntf) bool {
snap := s.Snapshot()
defer snap.Release()
snap.WithNeed(n, func(fi db.FileIntf) bool {
f := fi.(protocol.FileInfo)
fs = append(fs, f)
return true
@@ -206,7 +216,7 @@ func TestGlobalSet(t *testing.T) {
}
globalBytes += f.FileSize()
}
gs := m.GlobalSize()
gs := globalSize(m)
if gs.Files != globalFiles {
t.Errorf("Incorrect GlobalSize files; %d != %d", gs.Files, globalFiles)
}
@@ -242,7 +252,7 @@ func TestGlobalSet(t *testing.T) {
}
haveBytes += f.FileSize()
}
ls := m.LocalSize()
ls := localSize(m)
if ls.Files != haveFiles {
t.Errorf("Incorrect LocalSize files; %d != %d", ls.Files, haveFiles)
}
@@ -277,7 +287,9 @@ func TestGlobalSet(t *testing.T) {
t.Errorf("Need incorrect;\n A: %v !=\n E: %v", n, expectedRemoteNeed)
}
f, ok := m.Get(protocol.LocalDeviceID, "b")
snap := m.Snapshot()
defer snap.Release()
f, ok := snap.Get(protocol.LocalDeviceID, "b")
if !ok {
t.Error("Unexpectedly not OK")
}
@@ -285,7 +297,7 @@ func TestGlobalSet(t *testing.T) {
t.Errorf("Get incorrect;\n A: %v !=\n E: %v", f, localTot[1])
}
f, ok = m.Get(remoteDevice0, "b")
f, ok = snap.Get(remoteDevice0, "b")
if !ok {
t.Error("Unexpectedly not OK")
}
@@ -293,7 +305,7 @@ func TestGlobalSet(t *testing.T) {
t.Errorf("Get incorrect;\n A: %v !=\n E: %v", f, remote1[0])
}
f, ok = m.GetGlobal("b")
f, ok = snap.GetGlobal("b")
if !ok {
t.Error("Unexpectedly not OK")
}
@@ -301,7 +313,7 @@ func TestGlobalSet(t *testing.T) {
t.Errorf("GetGlobal incorrect;\n A: %v !=\n E: %v", f, remote1[0])
}
f, ok = m.Get(protocol.LocalDeviceID, "zz")
f, ok = snap.Get(protocol.LocalDeviceID, "zz")
if ok {
t.Error("Unexpectedly OK")
}
@@ -309,7 +321,7 @@ func TestGlobalSet(t *testing.T) {
t.Errorf("Get incorrect;\n A: %v !=\n E: %v", f, protocol.FileInfo{})
}
f, ok = m.GetGlobal("zz")
f, ok = snap.GetGlobal("zz")
if ok {
t.Error("Unexpectedly OK")
}
@@ -318,15 +330,15 @@ func TestGlobalSet(t *testing.T) {
}
av := []protocol.DeviceID{protocol.LocalDeviceID, remoteDevice0}
a := m.Availability("a")
a := snap.Availability("a")
if !(len(a) == 2 && (a[0] == av[0] && a[1] == av[1] || a[0] == av[1] && a[1] == av[0])) {
t.Errorf("Availability incorrect;\n A: %v !=\n E: %v", a, av)
}
a = m.Availability("b")
a = snap.Availability("b")
if len(a) != 1 || a[0] != remoteDevice0 {
t.Errorf("Availability incorrect;\n A: %v !=\n E: %v", a, remoteDevice0)
}
a = m.Availability("d")
a = snap.Availability("d")
if len(a) != 1 || a[0] != protocol.LocalDeviceID {
t.Errorf("Availability incorrect;\n A: %v !=\n E: %v", a, protocol.LocalDeviceID)
}
@@ -446,19 +458,22 @@ func TestInvalidAvailability(t *testing.T) {
replace(s, remoteDevice0, remote0Have)
replace(s, remoteDevice1, remote1Have)
if av := s.Availability("both"); len(av) != 2 {
snap := s.Snapshot()
defer snap.Release()
if av := snap.Availability("both"); len(av) != 2 {
t.Error("Incorrect availability for 'both':", av)
}
if av := s.Availability("r0only"); len(av) != 1 || av[0] != remoteDevice0 {
if av := snap.Availability("r0only"); len(av) != 1 || av[0] != remoteDevice0 {
t.Error("Incorrect availability for 'r0only':", av)
}
if av := s.Availability("r1only"); len(av) != 1 || av[0] != remoteDevice1 {
if av := snap.Availability("r1only"); len(av) != 1 || av[0] != remoteDevice1 {
t.Error("Incorrect availability for 'r1only':", av)
}
if av := s.Availability("none"); len(av) != 0 {
if av := snap.Availability("none"); len(av) != 0 {
t.Error("Incorrect availability for 'none':", av)
}
}
@@ -826,20 +841,20 @@ func TestIssue4701(t *testing.T) {
s.Update(protocol.LocalDeviceID, localHave)
if c := s.LocalSize(); c.Files != 1 {
if c := localSize(s); c.Files != 1 {
t.Errorf("Expected 1 local file, got %v", c.Files)
}
if c := s.GlobalSize(); c.Files != 1 {
if c := globalSize(s); c.Files != 1 {
t.Errorf("Expected 1 global file, got %v", c.Files)
}
localHave[1].LocalFlags = 0
s.Update(protocol.LocalDeviceID, localHave)
if c := s.LocalSize(); c.Files != 2 {
if c := localSize(s); c.Files != 2 {
t.Errorf("Expected 2 local files, got %v", c.Files)
}
if c := s.GlobalSize(); c.Files != 2 {
if c := globalSize(s); c.Files != 2 {
t.Errorf("Expected 2 global files, got %v", c.Files)
}
@@ -847,10 +862,10 @@ func TestIssue4701(t *testing.T) {
localHave[1].LocalFlags = protocol.FlagLocalIgnored
s.Update(protocol.LocalDeviceID, localHave)
if c := s.LocalSize(); c.Files != 0 {
if c := localSize(s); c.Files != 0 {
t.Errorf("Expected 0 local files, got %v", c.Files)
}
if c := s.GlobalSize(); c.Files != 0 {
if c := globalSize(s); c.Files != 0 {
t.Errorf("Expected 0 global files, got %v", c.Files)
}
}
@@ -873,7 +888,9 @@ func TestWithHaveSequence(t *testing.T) {
replace(s, protocol.LocalDeviceID, localHave)
i := 2
s.WithHaveSequence(int64(i), func(fi db.FileIntf) bool {
snap := s.Snapshot()
defer snap.Release()
snap.WithHaveSequence(int64(i), func(fi db.FileIntf) bool {
if f := fi.(protocol.FileInfo); !f.IsEquivalent(localHave[i-1], 0) {
t.Fatalf("Got %v\nExpected %v", f, localHave[i-1])
}
@@ -922,13 +939,15 @@ loop:
break loop
default:
}
s.WithHaveSequence(prevSeq+1, func(fi db.FileIntf) bool {
snap := s.Snapshot()
snap.WithHaveSequence(prevSeq+1, func(fi db.FileIntf) bool {
if fi.SequenceNo() < prevSeq+1 {
t.Fatal("Skipped ", prevSeq+1, fi.SequenceNo())
}
prevSeq = fi.SequenceNo()
return true
})
snap.Release()
}
}
@@ -981,12 +1000,12 @@ func TestMoveGlobalBack(t *testing.T) {
t.Error("Expected no need for remote 0, got", need)
}
ls := s.LocalSize()
ls := localSize(s)
if haveBytes := localHave[0].Size; ls.Bytes != haveBytes {
t.Errorf("Incorrect LocalSize bytes; %d != %d", ls.Bytes, haveBytes)
}
gs := s.GlobalSize()
gs := globalSize(s)
if globalBytes := remote0Have[0].Size; gs.Bytes != globalBytes {
t.Errorf("Incorrect GlobalSize bytes; %d != %d", gs.Bytes, globalBytes)
}
@@ -1007,12 +1026,12 @@ func TestMoveGlobalBack(t *testing.T) {
t.Error("Expected no local need, got", need)
}
ls = s.LocalSize()
ls = localSize(s)
if haveBytes := localHave[0].Size; ls.Bytes != haveBytes {
t.Errorf("Incorrect LocalSize bytes; %d != %d", ls.Bytes, haveBytes)
}
gs = s.GlobalSize()
gs = globalSize(s)
if globalBytes := localHave[0].Size; gs.Bytes != globalBytes {
t.Errorf("Incorrect GlobalSize bytes; %d != %d", gs.Bytes, globalBytes)
}
@@ -1106,22 +1125,22 @@ func TestReceiveOnlyAccounting(t *testing.T) {
replace(s, protocol.LocalDeviceID, files)
replace(s, remote, files)
if n := s.LocalSize().Files; n != 3 {
if n := localSize(s).Files; n != 3 {
t.Fatal("expected 3 local files initially, not", n)
}
if n := s.LocalSize().Bytes; n != 30 {
if n := localSize(s).Bytes; n != 30 {
t.Fatal("expected 30 local bytes initially, not", n)
}
if n := s.GlobalSize().Files; n != 3 {
if n := globalSize(s).Files; n != 3 {
t.Fatal("expected 3 global files initially, not", n)
}
if n := s.GlobalSize().Bytes; n != 30 {
if n := globalSize(s).Bytes; n != 30 {
t.Fatal("expected 30 global bytes initially, not", n)
}
if n := s.ReceiveOnlyChangedSize().Files; n != 0 {
if n := receiveOnlyChangedSize(s).Files; n != 0 {
t.Fatal("expected 0 receive only changed files initially, not", n)
}
if n := s.ReceiveOnlyChangedSize().Bytes; n != 0 {
if n := receiveOnlyChangedSize(s).Bytes; n != 0 {
t.Fatal("expected 0 receive only changed bytes initially, not", n)
}
@@ -1136,22 +1155,22 @@ func TestReceiveOnlyAccounting(t *testing.T) {
// Check that we see the files
if n := s.LocalSize().Files; n != 3 {
if n := localSize(s).Files; n != 3 {
t.Fatal("expected 3 local files after local change, not", n)
}
if n := s.LocalSize().Bytes; n != 120 {
if n := localSize(s).Bytes; n != 120 {
t.Fatal("expected 120 local bytes after local change, not", n)
}
if n := s.GlobalSize().Files; n != 3 {
if n := globalSize(s).Files; n != 3 {
t.Fatal("expected 3 global files after local change, not", n)
}
if n := s.GlobalSize().Bytes; n != 30 {
if n := globalSize(s).Bytes; n != 30 {
t.Fatal("expected 30 global files after local change, not", n)
}
if n := s.ReceiveOnlyChangedSize().Files; n != 1 {
if n := receiveOnlyChangedSize(s).Files; n != 1 {
t.Fatal("expected 1 receive only changed file after local change, not", n)
}
if n := s.ReceiveOnlyChangedSize().Bytes; n != 100 {
if n := receiveOnlyChangedSize(s).Bytes; n != 100 {
t.Fatal("expected 100 receive only changed btyes after local change, not", n)
}
@@ -1167,22 +1186,22 @@ func TestReceiveOnlyAccounting(t *testing.T) {
// Check that we see the files, same data as initially
if n := s.LocalSize().Files; n != 3 {
if n := localSize(s).Files; n != 3 {
t.Fatal("expected 3 local files after revert, not", n)
}
if n := s.LocalSize().Bytes; n != 30 {
if n := localSize(s).Bytes; n != 30 {
t.Fatal("expected 30 local bytes after revert, not", n)
}
if n := s.GlobalSize().Files; n != 3 {
if n := globalSize(s).Files; n != 3 {
t.Fatal("expected 3 global files after revert, not", n)
}
if n := s.GlobalSize().Bytes; n != 30 {
if n := globalSize(s).Bytes; n != 30 {
t.Fatal("expected 30 global bytes after revert, not", n)
}
if n := s.ReceiveOnlyChangedSize().Files; n != 0 {
if n := receiveOnlyChangedSize(s).Files; n != 0 {
t.Fatal("expected 0 receive only changed files after revert, not", n)
}
if n := s.ReceiveOnlyChangedSize().Bytes; n != 0 {
if n := receiveOnlyChangedSize(s).Bytes; n != 0 {
t.Fatal("expected 0 receive only changed bytes after revert, not", n)
}
}
@@ -1229,7 +1248,7 @@ func TestRemoteInvalidNotAccounted(t *testing.T) {
}
s.Update(remoteDevice0, files)
global := s.GlobalSize()
global := globalSize(s)
if global.Files != 1 {
t.Error("Expected one file in global size, not", global.Files)
}
@@ -1386,12 +1405,14 @@ func TestSequenceIndex(t *testing.T) {
// a subset of those files if we manage to run before a complete
// update has happened since our last iteration.
latest = latest[:0]
s.WithHaveSequence(seq+1, func(f db.FileIntf) bool {
snap := s.Snapshot()
snap.WithHaveSequence(seq+1, func(f db.FileIntf) bool {
seen[f.FileName()] = f
latest = append(latest, f)
seq = f.SequenceNo()
return true
})
snap.Release()
// Calculate the spread in sequence number.
var max, min int64
@@ -1449,7 +1470,9 @@ func TestIgnoreAfterReceiveOnly(t *testing.T) {
s.Update(protocol.LocalDeviceID, fs)
if f, ok := s.Get(protocol.LocalDeviceID, file); !ok {
snap := s.Snapshot()
defer snap.Release()
if f, ok := snap.Get(protocol.LocalDeviceID, file); !ok {
t.Error("File missing in db")
} else if f.IsReceiveOnlyChanged() {
t.Error("File is still receive-only changed")
@@ -1462,3 +1485,21 @@ func replace(fs *db.FileSet, device protocol.DeviceID, files []protocol.FileInfo
fs.Drop(device)
fs.Update(device, files)
}
func localSize(fs *db.FileSet) db.Counts {
snap := fs.Snapshot()
defer snap.Release()
return snap.LocalSize()
}
func globalSize(fs *db.FileSet) db.Counts {
snap := fs.Snapshot()
defer snap.Release()
return snap.GlobalSize()
}
func receiveOnlyChangedSize(fs *db.FileSet) db.Counts {
snap := fs.Snapshot()
defer snap.Release()
return snap.ReceiveOnlyChangedSize()
}
+346
View File
@@ -7,6 +7,8 @@
package db
import (
"bytes"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/protocol"
)
@@ -94,6 +96,291 @@ func (t readOnlyTransaction) getGlobal(keyBuf, folder, file []byte, truncate boo
return keyBuf, fi, true, nil
}
func (t *readOnlyTransaction) withHave(folder, device, prefix []byte, truncate bool, fn Iterator) error {
if len(prefix) > 0 {
unslashedPrefix := prefix
if bytes.HasSuffix(prefix, []byte{'/'}) {
unslashedPrefix = unslashedPrefix[:len(unslashedPrefix)-1]
} else {
prefix = append(prefix, '/')
}
key, err := t.keyer.GenerateDeviceFileKey(nil, folder, device, unslashedPrefix)
if err != nil {
return err
}
if f, ok, err := t.getFileTrunc(key, true); err != nil {
return err
} else if ok && !fn(f) {
return nil
}
}
key, err := t.keyer.GenerateDeviceFileKey(nil, folder, device, prefix)
if err != nil {
return err
}
dbi, err := t.NewPrefixIterator(key)
if err != nil {
return err
}
defer dbi.Release()
for dbi.Next() {
name := t.keyer.NameFromDeviceFileKey(dbi.Key())
if len(prefix) > 0 && !bytes.HasPrefix(name, prefix) {
return nil
}
f, err := unmarshalTrunc(dbi.Value(), truncate)
if err != nil {
l.Debugln("unmarshal error:", err)
continue
}
if !fn(f) {
return nil
}
}
return dbi.Error()
}
func (t *readOnlyTransaction) withHaveSequence(folder []byte, startSeq int64, fn Iterator) error {
first, err := t.keyer.GenerateSequenceKey(nil, folder, startSeq)
if err != nil {
return err
}
last, err := t.keyer.GenerateSequenceKey(nil, folder, maxInt64)
if err != nil {
return err
}
dbi, err := t.NewRangeIterator(first, last)
if err != nil {
return err
}
defer dbi.Release()
for dbi.Next() {
f, ok, err := t.getFileByKey(dbi.Value())
if err != nil {
return err
}
if !ok {
l.Debugln("missing file for sequence number", t.keyer.SequenceFromSequenceKey(dbi.Key()))
continue
}
if shouldDebug() {
if seq := t.keyer.SequenceFromSequenceKey(dbi.Key()); f.Sequence != seq {
l.Warnf("Sequence index corruption (folder %v, file %v): sequence %d != expected %d", string(folder), f.Name, f.Sequence, seq)
panic("sequence index corruption")
}
}
if !fn(f) {
return nil
}
}
return dbi.Error()
}
func (t *readOnlyTransaction) withGlobal(folder, prefix []byte, truncate bool, fn Iterator) error {
if len(prefix) > 0 {
unslashedPrefix := prefix
if bytes.HasSuffix(prefix, []byte{'/'}) {
unslashedPrefix = unslashedPrefix[:len(unslashedPrefix)-1]
} else {
prefix = append(prefix, '/')
}
if _, f, ok, err := t.getGlobal(nil, folder, unslashedPrefix, truncate); err != nil {
return err
} else if ok && !fn(f) {
return nil
}
}
key, err := t.keyer.GenerateGlobalVersionKey(nil, folder, prefix)
if err != nil {
return err
}
dbi, err := t.NewPrefixIterator(key)
if err != nil {
return err
}
defer dbi.Release()
var dk []byte
for dbi.Next() {
name := t.keyer.NameFromGlobalVersionKey(dbi.Key())
if len(prefix) > 0 && !bytes.HasPrefix(name, prefix) {
return nil
}
vl, ok := unmarshalVersionList(dbi.Value())
if !ok {
continue
}
dk, err = t.keyer.GenerateDeviceFileKey(dk, folder, vl.Versions[0].Device, name)
if err != nil {
return err
}
f, ok, err := t.getFileTrunc(dk, truncate)
if err != nil {
return err
}
if !ok {
continue
}
if !fn(f) {
return nil
}
}
if err != nil {
return err
}
return dbi.Error()
}
func (t *readOnlyTransaction) availability(folder, file []byte) ([]protocol.DeviceID, error) {
k, err := t.keyer.GenerateGlobalVersionKey(nil, folder, file)
if err != nil {
return nil, err
}
bs, err := t.Get(k)
if backend.IsNotFound(err) {
return nil, nil
}
if err != nil {
return nil, err
}
vl, ok := unmarshalVersionList(bs)
if !ok {
return nil, nil
}
var devices []protocol.DeviceID
for _, v := range vl.Versions {
if !v.Version.Equal(vl.Versions[0].Version) {
break
}
if v.Invalid {
continue
}
n := protocol.DeviceIDFromBytes(v.Device)
devices = append(devices, n)
}
return devices, nil
}
func (t *readOnlyTransaction) withNeed(folder, device []byte, truncate bool, fn Iterator) error {
if bytes.Equal(device, protocol.LocalDeviceID[:]) {
return t.withNeedLocal(folder, truncate, fn)
}
key, err := t.keyer.GenerateGlobalVersionKey(nil, folder, nil)
if err != nil {
return err
}
dbi, err := t.NewPrefixIterator(key.WithoutName())
if err != nil {
return err
}
defer dbi.Release()
var dk []byte
devID := protocol.DeviceIDFromBytes(device)
for dbi.Next() {
vl, ok := unmarshalVersionList(dbi.Value())
if !ok {
continue
}
haveFV, have := vl.Get(device)
// XXX: This marks Concurrent (i.e. conflicting) changes as
// needs. Maybe we should do that, but it needs special
// handling in the puller.
if have && haveFV.Version.GreaterEqual(vl.Versions[0].Version) {
continue
}
name := t.keyer.NameFromGlobalVersionKey(dbi.Key())
needVersion := vl.Versions[0].Version
needDevice := protocol.DeviceIDFromBytes(vl.Versions[0].Device)
for i := range vl.Versions {
if !vl.Versions[i].Version.Equal(needVersion) {
// We haven't found a valid copy of the file with the needed version.
break
}
if vl.Versions[i].Invalid {
// The file is marked invalid, don't use it.
continue
}
dk, err = t.keyer.GenerateDeviceFileKey(dk, folder, vl.Versions[i].Device, name)
if err != nil {
return err
}
gf, ok, err := t.getFileTrunc(dk, truncate)
if err != nil {
return err
}
if !ok {
continue
}
if gf.IsDeleted() && !have {
// We don't need deleted files that we don't have
break
}
l.Debugf("need folder=%q device=%v name=%q have=%v invalid=%v haveV=%v globalV=%v globalDev=%v", folder, devID, name, have, haveFV.Invalid, haveFV.Version, needVersion, needDevice)
if !fn(gf) {
return nil
}
// This file is handled, no need to look further in the version list
break
}
}
return dbi.Error()
}
func (t *readOnlyTransaction) withNeedLocal(folder []byte, truncate bool, fn Iterator) error {
key, err := t.keyer.GenerateNeedFileKey(nil, folder, nil)
if err != nil {
return err
}
dbi, err := t.NewPrefixIterator(key.WithoutName())
if err != nil {
return err
}
defer dbi.Release()
var keyBuf []byte
var f FileIntf
var ok bool
for dbi.Next() {
keyBuf, f, ok, err = t.getGlobal(keyBuf, folder, t.keyer.NameFromGlobalVersionKey(dbi.Key()), truncate)
if err != nil {
return err
}
if !ok {
continue
}
if !fn(f) {
return nil
}
}
return dbi.Error()
}
// A readWriteTransaction is a readOnlyTransaction plus a batch for writes.
// The batch will be committed on close() or by checkFlush() if it exceeds the
// batch size.
@@ -353,6 +640,65 @@ func (t readWriteTransaction) deleteKeyPrefix(prefix []byte) error {
return dbi.Error()
}
func (t *readWriteTransaction) withAllFolderTruncated(folder []byte, fn func(device []byte, f FileInfoTruncated) bool) error {
key, err := t.keyer.GenerateDeviceFileKey(nil, folder, nil, nil)
if err != nil {
return err
}
dbi, err := t.NewPrefixIterator(key.WithoutNameAndDevice())
if err != nil {
return err
}
defer dbi.Release()
var gk, keyBuf []byte
for dbi.Next() {
device, ok := t.keyer.DeviceFromDeviceFileKey(dbi.Key())
if !ok {
// Not having the device in the index is bad. Clear it.
if err := t.Delete(dbi.Key()); err != nil {
return err
}
continue
}
var f FileInfoTruncated
// The iterator function may keep a reference to the unmarshalled
// struct, which in turn references the buffer it was unmarshalled
// from. dbi.Value() just returns an internal slice that it reuses, so
// we need to copy it.
err := f.Unmarshal(append([]byte{}, dbi.Value()...))
if err != nil {
return err
}
switch f.Name {
case "", ".", "..", "/": // A few obviously invalid filenames
l.Infof("Dropping invalid filename %q from database", f.Name)
name := []byte(f.Name)
gk, err = t.keyer.GenerateGlobalVersionKey(gk, folder, name)
if err != nil {
return err
}
keyBuf, err = t.removeFromGlobal(gk, keyBuf, folder, device, name, nil)
if err != nil {
return err
}
if err := t.Delete(dbi.Key()); err != nil {
return err
}
continue
}
if !fn(device, f) {
return nil
}
}
if err := dbi.Error(); err != nil {
return err
}
return t.commit()
}
type marshaller interface {
Marshal() ([]byte, error)
}