cmd/syncthing, lib/db, lib/model, lib/protocol: Implement delta indexes (fixes #438)

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3427
This commit is contained in:
Jakob Borg
2016-07-23 12:46:31 +00:00
committed by Audrius Butkevicius
parent 8ab6b60778
commit 47fa4b0a2c
11 changed files with 428 additions and 222 deletions

View File

@@ -14,6 +14,7 @@ package db
import (
stdsync "sync"
"sync/atomic"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
@@ -21,13 +22,15 @@ import (
)
type FileSet struct {
localVersion map[protocol.DeviceID]int64
mutex sync.Mutex
localVersion int64 // Our local version counter
folder string
db *Instance
blockmap *BlockMap
localSize sizeTracker
globalSize sizeTracker
remoteLocalVersion map[protocol.DeviceID]int64 // Highest seen local versions for other devices
rlvMutex sync.Mutex // protects remoteLocalVersion
}
// FileIntf is the set of methods implemented by both protocol.FileInfo and
@@ -95,11 +98,11 @@ func (s *sizeTracker) Size() (files, deleted int, bytes int64) {
func NewFileSet(folder string, db *Instance) *FileSet {
var s = FileSet{
localVersion: make(map[protocol.DeviceID]int64),
folder: folder,
db: db,
blockmap: NewBlockMap(db, db.folderIdx.ID([]byte(folder))),
mutex: sync.NewMutex(),
remoteLocalVersion: make(map[protocol.DeviceID]int64),
folder: folder,
db: db,
blockmap: NewBlockMap(db, db.folderIdx.ID([]byte(folder))),
rlvMutex: sync.NewMutex(),
}
s.db.checkGlobals([]byte(folder), &s.globalSize)
@@ -107,16 +110,17 @@ func NewFileSet(folder string, db *Instance) *FileSet {
var deviceID protocol.DeviceID
s.db.withAllFolderTruncated([]byte(folder), func(device []byte, f FileInfoTruncated) bool {
copy(deviceID[:], device)
if f.LocalVersion > s.localVersion[deviceID] {
s.localVersion[deviceID] = f.LocalVersion
}
if deviceID == protocol.LocalDeviceID {
if f.LocalVersion > s.localVersion {
s.localVersion = f.LocalVersion
}
s.localSize.addFile(f)
} else if f.LocalVersion > s.remoteLocalVersion[deviceID] {
s.remoteLocalVersion[deviceID] = f.LocalVersion
}
return true
})
l.Debugf("loaded localVersion for %q: %#v", folder, s.localVersion)
clock(s.localVersion[protocol.LocalDeviceID])
return &s
}
@@ -124,13 +128,23 @@ func NewFileSet(folder string, db *Instance) *FileSet {
func (s *FileSet) Replace(device protocol.DeviceID, fs []protocol.FileInfo) {
l.Debugf("%s Replace(%v, [%d])", s.folder, device, len(fs))
normalizeFilenames(fs)
s.mutex.Lock()
defer s.mutex.Unlock()
s.localVersion[device] = s.db.replace([]byte(s.folder), device[:], fs, &s.localSize, &s.globalSize)
if len(fs) == 0 {
// Reset the local version if all files were removed.
s.localVersion[device] = 0
if device == protocol.LocalDeviceID {
if len(fs) == 0 {
s.localVersion = 0
} else {
// Always overwrite LocalVersion on updated files to ensure
// correct ordering. The caller is supposed to leave it set to
// zero anyhow.
for i := range fs {
fs[i].LocalVersion = atomic.AddInt64(&s.localVersion, 1)
}
}
} else {
s.rlvMutex.Lock()
s.remoteLocalVersion[device] = maxLocalVersion(fs)
s.rlvMutex.Unlock()
}
s.db.replace([]byte(s.folder), device[:], fs, &s.localSize, &s.globalSize)
if device == protocol.LocalDeviceID {
s.blockmap.Drop()
s.blockmap.Add(fs)
@@ -140,12 +154,11 @@ func (s *FileSet) Replace(device protocol.DeviceID, fs []protocol.FileInfo) {
func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
l.Debugf("%s Update(%v, [%d])", s.folder, device, len(fs))
normalizeFilenames(fs)
s.mutex.Lock()
defer s.mutex.Unlock()
if device == protocol.LocalDeviceID {
discards := make([]protocol.FileInfo, 0, len(fs))
updates := make([]protocol.FileInfo, 0, len(fs))
for _, newFile := range fs {
for i, newFile := range fs {
fs[i].LocalVersion = atomic.AddInt64(&s.localVersion, 1)
existingFile, ok := s.db.getFile([]byte(s.folder), device[:], []byte(newFile.Name))
if !ok || !existingFile.Version.Equal(newFile.Version) {
discards = append(discards, existingFile)
@@ -154,10 +167,12 @@ func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
}
s.blockmap.Discard(discards)
s.blockmap.Update(updates)
} else {
s.rlvMutex.Lock()
s.remoteLocalVersion[device] = maxLocalVersion(fs)
s.rlvMutex.Unlock()
}
if lv := s.db.updateFiles([]byte(s.folder), device[:], fs, &s.localSize, &s.globalSize); lv > s.localVersion[device] {
s.localVersion[device] = lv
}
s.db.updateFiles([]byte(s.folder), device[:], fs, &s.localSize, &s.globalSize)
}
func (s *FileSet) WithNeed(device protocol.DeviceID, fn Iterator) {
@@ -230,9 +245,13 @@ func (s *FileSet) Availability(file string) []protocol.DeviceID {
}
func (s *FileSet) LocalVersion(device protocol.DeviceID) int64 {
s.mutex.Lock()
defer s.mutex.Unlock()
return s.localVersion[device]
if device == protocol.LocalDeviceID {
return atomic.LoadInt64(&s.localVersion)
}
s.rlvMutex.Lock()
defer s.rlvMutex.Unlock()
return s.remoteLocalVersion[device]
}
func (s *FileSet) LocalSize() (files, deleted int, bytes int64) {
@@ -243,6 +262,37 @@ func (s *FileSet) GlobalSize() (files, deleted int, bytes int64) {
return s.globalSize.Size()
}
func (s *FileSet) IndexID(device protocol.DeviceID) protocol.IndexID {
id := s.db.getIndexID(device[:], []byte(s.folder))
if id == 0 && device == protocol.LocalDeviceID {
// No index ID set yet. We create one now.
id = protocol.NewIndexID()
s.db.setIndexID(device[:], []byte(s.folder), id)
}
return id
}
func (s *FileSet) SetIndexID(device protocol.DeviceID, id protocol.IndexID) {
if device == protocol.LocalDeviceID {
panic("do not explicitly set index ID for local device")
}
s.db.setIndexID(device[:], []byte(s.folder), id)
}
// maxLocalVersion returns the highest of the LocalVersion numbers found in
// the given slice of FileInfos. This should really be the LocalVersion of
// the last item, but Syncthing v0.14.0 and other implementations may not
// implement update sorting....
func maxLocalVersion(fs []protocol.FileInfo) int64 {
var max int64
for _, f := range fs {
if f.LocalVersion > max {
max = f.LocalVersion
}
}
return max
}
// DropFolder clears out all information related to the given folder from the
// database.
func DropFolder(db *Instance, folder string) {