diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index f1fb1aec4..8cb0d6cfc 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -197,6 +197,11 @@ are mostly useful for developers. Use with care. "minio" for the github.com/minio/sha256-simd implementation, and blank (the default) for auto detection. + STDBCHECKEVERY Set to a time interval to override the default database + check interval of 30 days (720h). The interval understands + "h", "m" and "s" abbreviations for hours minutes and seconds. + Valid values are like "720h", "30s", etc. + GOMAXPROCS Set the maximum number of CPU cores to use. Defaults to all available CPU cores. diff --git a/cmd/syncthing/usage_report.go b/cmd/syncthing/usage_report.go index 9a83932de..2556d45fd 100644 --- a/cmd/syncthing/usage_report.go +++ b/cmd/syncthing/usage_report.go @@ -51,10 +51,10 @@ func reportData(cfg configIntf, m modelIntf, connectionsService connectionsIntf, var totBytes, maxBytes int64 for folderID := range cfg.Folders() { global := m.GlobalSize(folderID) - totFiles += global.Files + totFiles += int(global.Files) totBytes += global.Bytes - if global.Files > maxFiles { - maxFiles = global.Files + if int(global.Files) > maxFiles { + maxFiles = int(global.Files) } if global.Bytes > maxBytes { maxBytes = global.Bytes diff --git a/lib/db/leveldb.go b/lib/db/leveldb.go index 2ed151b2c..e30fd6a7b 100644 --- a/lib/db/leveldb.go +++ b/lib/db/leveldb.go @@ -25,6 +25,7 @@ const ( KeyTypeFolderIdx KeyTypeDeviceIdx KeyTypeIndexID + KeyTypeFolderMeta ) func (l VersionList) String() string { diff --git a/lib/db/leveldb_dbinstance.go b/lib/db/leveldb_dbinstance.go index 3940157aa..0c6a9ce0f 100644 --- a/lib/db/leveldb_dbinstance.go +++ b/lib/db/leveldb_dbinstance.go @@ -93,12 +93,11 @@ func (db *Instance) Location() string { return db.location } -func (db *Instance) updateFiles(folder, device []byte, fs []protocol.FileInfo, localSize, globalSize *sizeTracker) { +func (db *Instance) updateFiles(folder, device []byte, fs []protocol.FileInfo, meta *metadataTracker) { t := db.newReadWriteTransaction() defer t.close() var fk []byte - isLocalDevice := bytes.Equal(device, protocol.LocalDeviceID[:]) for _, f := range fs { name := []byte(f.Name) fk = db.deviceKeyInto(fk[:cap(fk)], folder, device, name) @@ -116,15 +115,14 @@ func (db *Instance) updateFiles(folder, device []byte, fs []protocol.FileInfo, l continue } - if isLocalDevice { - if err == nil { - localSize.removeFile(ef) - } - localSize.addFile(f) + devID := protocol.DeviceIDFromBytes(device) + if err == nil { + meta.removeFile(devID, ef) } + meta.addFile(devID, f) t.insertFile(folder, device, f) - t.updateGlobal(folder, device, f, globalSize) + t.updateGlobal(folder, device, f, meta) // Write out and reuse the batch every few records, to avoid the batch // growing too large and thus allocating unnecessarily much memory. @@ -465,7 +463,7 @@ func (db *Instance) dropFolder(folder []byte) { dbi.Release() } -func (db *Instance) dropDeviceFolder(device, folder []byte, globalSize *sizeTracker) { +func (db *Instance) dropDeviceFolder(device, folder []byte, meta *metadataTracker) { t := db.newReadWriteTransaction() defer t.close() @@ -475,13 +473,13 @@ func (db *Instance) dropDeviceFolder(device, folder []byte, globalSize *sizeTrac for dbi.Next() { key := dbi.Key() name := db.deviceKeyName(key) - t.removeFromGlobal(folder, device, name, globalSize) + t.removeFromGlobal(folder, device, name, meta) t.Delete(key) t.checkFlush() } } -func (db *Instance) checkGlobals(folder []byte, globalSize *sizeTracker) { +func (db *Instance) checkGlobals(folder []byte, meta *metadataTracker) { t := db.newReadWriteTransaction() defer t.close() @@ -520,7 +518,7 @@ func (db *Instance) checkGlobals(folder []byte, globalSize *sizeTracker) { if i == 0 { if fi, ok := t.getFile(folder, version.Device, name); ok { - globalSize.addFile(fi) + meta.addFile(globalDeviceID, fi) } } } @@ -760,6 +758,13 @@ func (db *Instance) mtimesKey(folder []byte) []byte { return prefix } +func (db *Instance) folderMetaKey(folder []byte) []byte { + prefix := make([]byte, 5) // key type + 4 bytes folder idx number + prefix[0] = KeyTypeFolderMeta + binary.BigEndian.PutUint32(prefix[1:], db.folderIdx.ID(folder)) + return prefix +} + // DropDeltaIndexIDs removes all index IDs from the database. This will // cause a full index transmission on the next connection. func (db *Instance) DropDeltaIndexIDs() { @@ -770,6 +775,10 @@ func (db *Instance) dropMtimes(folder []byte) { db.dropPrefix(db.mtimesKey(folder)) } +func (db *Instance) dropFolderMeta(folder []byte) { + db.dropPrefix(db.folderMetaKey(folder)) +} + func (db *Instance) dropPrefix(prefix []byte) { t := db.newReadWriteTransaction() defer t.close() diff --git a/lib/db/leveldb_transactions.go b/lib/db/leveldb_transactions.go index f9921f78f..bcb2f514b 100644 --- a/lib/db/leveldb_transactions.go +++ b/lib/db/leveldb_transactions.go @@ -85,7 +85,7 @@ func (t readWriteTransaction) insertFile(folder, device []byte, file protocol.Fi // updateGlobal adds this device+version to the version list for the given // file. If the device is already present in the list, the version is updated. // If the file does not have an entry in the global list, it is created. -func (t readWriteTransaction) updateGlobal(folder, device []byte, file protocol.FileInfo, globalSize *sizeTracker) bool { +func (t readWriteTransaction) updateGlobal(folder, device []byte, file protocol.FileInfo, meta *metadataTracker) bool { l.Debugf("update global; folder=%q device=%v file=%q version=%v invalid=%v", folder, protocol.DeviceIDFromBytes(device), file.Name, file.Version, file.Invalid) name := []byte(file.Name) gk := t.db.globalKey(folder, name) @@ -106,7 +106,7 @@ func (t readWriteTransaction) updateGlobal(folder, device []byte, file protocol. if i == 0 { // Keep the current newest file around so we can subtract it from - // the globalSize if we replace it. + // the metadata if we replace it. oldFile, hasOldFile = t.getFile(folder, fl.Versions[0].Device, name) } @@ -169,16 +169,16 @@ insert: // We just inserted a new newest version. Fixup the global size // calculation. if !file.Version.Equal(oldFile.Version) { - globalSize.addFile(file) + meta.addFile(globalDeviceID, file) if hasOldFile { // We have the old file that was removed at the head of the list. - globalSize.removeFile(oldFile) + meta.removeFile(globalDeviceID, oldFile) } else if len(fl.Versions) > 1 { // The previous newest version is now at index 1, grab it from there. if oldFile, ok := t.getFile(folder, fl.Versions[1].Device, name); ok { // A failure to get the file here is surprising and our // global size data will be incorrect until a restart... - globalSize.removeFile(oldFile) + meta.removeFile(globalDeviceID, oldFile) } } } @@ -193,7 +193,7 @@ insert: // removeFromGlobal removes the device from the global version list for the // given file. If the version list is empty after this, the file entry is // removed entirely. -func (t readWriteTransaction) removeFromGlobal(folder, device, file []byte, globalSize *sizeTracker) { +func (t readWriteTransaction) removeFromGlobal(folder, device, file []byte, meta *metadataTracker) { l.Debugf("remove from global; folder=%q device=%v file=%q", folder, protocol.DeviceIDFromBytes(device), file) gk := t.db.globalKey(folder, file) @@ -214,13 +214,13 @@ func (t readWriteTransaction) removeFromGlobal(folder, device, file []byte, glob removed := false for i := range fl.Versions { if bytes.Equal(fl.Versions[i].Device, device) { - if i == 0 && globalSize != nil { + if i == 0 && meta != nil { f, ok := t.getFile(folder, device, file) if !ok { // didn't exist anyway, apparently continue } - globalSize.removeFile(f) + meta.removeFile(globalDeviceID, f) removed = true } fl.Versions = append(fl.Versions[:i], fl.Versions[i+1:]...) @@ -238,7 +238,7 @@ func (t readWriteTransaction) removeFromGlobal(folder, device, file []byte, glob if f, ok := t.getFile(folder, fl.Versions[0].Device, file); ok { // A failure to get the file here is surprising and our // global size data will be incorrect until a restart... - globalSize.addFile(f) + meta.addFile(globalDeviceID, f) } } } diff --git a/lib/db/meta.go b/lib/db/meta.go new file mode 100644 index 000000000..4a2504c44 --- /dev/null +++ b/lib/db/meta.go @@ -0,0 +1,220 @@ +// Copyright (C) 2017 The Syncthing Authors. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +package db + +import ( + "time" + + "github.com/syncthing/syncthing/lib/protocol" + "github.com/syncthing/syncthing/lib/sync" +) + +// like protocol.LocalDeviceID but with 0xf8 in all positions +var globalDeviceID = protocol.DeviceID{0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8} + +type metadataTracker struct { + mut sync.RWMutex + counts CountsSet + indexes map[protocol.DeviceID]int // device ID -> index in counts +} + +func newMetadataTracker() *metadataTracker { + return &metadataTracker{ + mut: sync.NewRWMutex(), + indexes: make(map[protocol.DeviceID]int), + } +} + +// Unmarshal loads a metadataTracker from the corresponding protobuf +// representation +func (m *metadataTracker) Unmarshal(bs []byte) error { + if err := m.counts.Unmarshal(bs); err != nil { + return err + } + + // Initialize the index map + for i, c := range m.counts.Counts { + m.indexes[protocol.DeviceIDFromBytes(c.DeviceID)] = i + } + return nil +} + +// Unmarshal returns the protobuf representation of the metadataTracker +func (m *metadataTracker) Marshal() ([]byte, error) { + return m.counts.Marshal() +} + +// toDB saves the marshalled metadataTracker to the given db, under the key +// corresponding to the given folder +func (m *metadataTracker) toDB(db *Instance, folder []byte) error { + key := db.folderMetaKey(folder) + bs, err := m.Marshal() + if err != nil { + return err + } + return db.Put(key, bs, nil) +} + +// fromDB initializes the metadataTracker from the marshalled data found in +// the database under the key corresponding to the given folder +func (m *metadataTracker) fromDB(db *Instance, folder []byte) error { + key := db.folderMetaKey(folder) + bs, err := db.Get(key, nil) + if err != nil { + return err + } + return m.Unmarshal(bs) +} + +// countsPtr returns a pointer to the corresponding Counts struct, if +// necessary allocating one in the process +func (m *metadataTracker) countsPtr(dev protocol.DeviceID) *Counts { + // must be called with the mutex held + + idx, ok := m.indexes[dev] + if !ok { + idx = len(m.counts.Counts) + m.counts.Counts = append(m.counts.Counts, Counts{DeviceID: dev[:]}) + m.indexes[dev] = idx + } + return &m.counts.Counts[idx] +} + +// addFile adds a file to the counts, adjusting the sequence number as +// appropriate +func (m *metadataTracker) addFile(dev protocol.DeviceID, f FileIntf) { + if f.IsInvalid() { + return + } + + m.mut.Lock() + cp := m.countsPtr(dev) + + switch { + case f.IsDeleted(): + cp.Deleted++ + case f.IsDirectory() && !f.IsSymlink(): + cp.Directories++ + case f.IsSymlink(): + cp.Symlinks++ + default: + cp.Files++ + } + cp.Bytes += f.FileSize() + + if seq := f.SequenceNo(); seq > cp.Sequence { + cp.Sequence = seq + } + + m.mut.Unlock() +} + +// removeFile removes a file from the counts +func (m *metadataTracker) removeFile(dev protocol.DeviceID, f FileIntf) { + if f.IsInvalid() { + return + } + + m.mut.Lock() + cp := m.countsPtr(dev) + + switch { + case f.IsDeleted(): + cp.Deleted-- + case f.IsDirectory() && !f.IsSymlink(): + cp.Directories-- + case f.IsSymlink(): + cp.Symlinks-- + default: + cp.Files-- + } + cp.Bytes -= f.FileSize() + + if cp.Deleted < 0 || cp.Files < 0 || cp.Directories < 0 || cp.Symlinks < 0 { + panic("bug: removed more than added") + } + + m.mut.Unlock() +} + +// resetAll resets all metadata for the given device +func (m *metadataTracker) resetAll(dev protocol.DeviceID) { + m.mut.Lock() + *m.countsPtr(dev) = Counts{DeviceID: dev[:]} + m.mut.Unlock() +} + +// resetCounts resets the file, dir, etc. counters, while retaining the +// sequence number +func (m *metadataTracker) resetCounts(dev protocol.DeviceID) { + m.mut.Lock() + + c := m.countsPtr(dev) + c.Bytes = 0 + c.Deleted = 0 + c.Directories = 0 + c.Files = 0 + c.Symlinks = 0 + // c.Sequence deliberately untouched + + m.mut.Unlock() +} + +// Counts returns the counts for the given device ID +func (m *metadataTracker) Counts(dev protocol.DeviceID) Counts { + m.mut.RLock() + defer m.mut.RUnlock() + + idx, ok := m.indexes[dev] + if !ok { + return Counts{} + } + + return m.counts.Counts[idx] +} + +// nextSeq allocates a new sequence number for the given device +func (m *metadataTracker) nextSeq(dev protocol.DeviceID) int64 { + m.mut.Lock() + defer m.mut.Unlock() + + c := m.countsPtr(dev) + c.Sequence++ + return c.Sequence +} + +// 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([]protocol.DeviceID, 0, len(m.counts.Counts)) + + m.mut.RLock() + for _, dev := range m.counts.Counts { + if dev.Sequence > 0 { + id := protocol.DeviceIDFromBytes(dev.DeviceID) + if id == globalDeviceID || id == protocol.LocalDeviceID { + continue + } + devs = append(devs, id) + } + } + m.mut.RUnlock() + + return devs +} + +func (m *metadataTracker) Created() time.Time { + m.mut.RLock() + defer m.mut.RUnlock() + return time.Unix(0, m.counts.Created) +} + +func (m *metadataTracker) SetCreated() { + m.mut.Lock() + m.counts.Created = time.Now().UnixNano() + m.mut.Unlock() +} diff --git a/lib/db/set.go b/lib/db/set.go index 7b8f2b5e8..7fbde2caa 100644 --- a/lib/db/set.go +++ b/lib/db/set.go @@ -13,8 +13,8 @@ package db import ( - stdsync "sync" - "sync/atomic" + "os" + "time" "github.com/syncthing/syncthing/lib/fs" "github.com/syncthing/syncthing/lib/osutil" @@ -23,16 +23,13 @@ import ( ) type FileSet struct { - sequence int64 // Our local sequence number - folder string - fs fs.Filesystem - db *Instance - blockmap *BlockMap - localSize sizeTracker - globalSize sizeTracker + folder string + fs fs.Filesystem + db *Instance + blockmap *BlockMap + meta *metadataTracker - remoteSequence map[protocol.DeviceID]int64 // Highest seen sequence numbers for other devices - updateMutex sync.Mutex // protects remoteSequence and database updates + updateMutex sync.Mutex // protects database updates and the corresponding metadata changes } // FileIntf is the set of methods implemented by both protocol.FileInfo and @@ -45,6 +42,7 @@ type FileIntf interface { IsDirectory() bool IsSymlink() bool HasPermissionBits() bool + SequenceNo() int64 } // The Iterator is called with either a protocol.FileInfo or a @@ -52,126 +50,76 @@ type FileIntf interface { // continue iteration, false to stop. type Iterator func(f FileIntf) bool -type Counts struct { - Files int - Directories int - Symlinks int - Deleted int - Bytes int64 -} +var databaseRecheckInterval = 30 * 24 * time.Hour -type sizeTracker struct { - Counts - mut stdsync.Mutex -} - -func (s *sizeTracker) addFile(f FileIntf) { - if f.IsInvalid() { - return +func init() { + if dur, err := time.ParseDuration(os.Getenv("STRECHECKDBEVERY")); err == nil { + databaseRecheckInterval = dur } - - s.mut.Lock() - switch { - case f.IsDeleted(): - s.Deleted++ - case f.IsDirectory() && !f.IsSymlink(): - s.Directories++ - case f.IsSymlink(): - s.Symlinks++ - default: - s.Files++ - } - s.Bytes += f.FileSize() - s.mut.Unlock() -} - -func (s *sizeTracker) removeFile(f FileIntf) { - if f.IsInvalid() { - return - } - - s.mut.Lock() - switch { - case f.IsDeleted(): - s.Deleted-- - case f.IsDirectory() && !f.IsSymlink(): - s.Directories-- - case f.IsSymlink(): - s.Symlinks-- - default: - s.Files-- - } - s.Bytes -= f.FileSize() - if s.Deleted < 0 || s.Files < 0 || s.Directories < 0 || s.Symlinks < 0 { - panic("bug: removed more than added") - } - s.mut.Unlock() -} - -func (s *sizeTracker) reset() { - s.mut.Lock() - defer s.mut.Unlock() - s.Counts = Counts{} -} - -func (s *sizeTracker) Size() Counts { - s.mut.Lock() - defer s.mut.Unlock() - return s.Counts } func NewFileSet(folder string, fs fs.Filesystem, db *Instance) *FileSet { var s = FileSet{ - remoteSequence: make(map[protocol.DeviceID]int64), - folder: folder, - fs: fs, - db: db, - blockmap: NewBlockMap(db, db.folderIdx.ID([]byte(folder))), - updateMutex: sync.NewMutex(), + folder: folder, + fs: fs, + db: db, + blockmap: NewBlockMap(db, db.folderIdx.ID([]byte(folder))), + meta: newMetadataTracker(), + updateMutex: sync.NewMutex(), } - s.db.checkGlobals([]byte(folder), &s.globalSize) - - var deviceID protocol.DeviceID - s.db.withAllFolderTruncated([]byte(folder), func(device []byte, f FileInfoTruncated) bool { - copy(deviceID[:], device) - if deviceID == protocol.LocalDeviceID { - if f.Sequence > s.sequence { - s.sequence = f.Sequence - } - s.localSize.addFile(f) - } else if f.Sequence > s.remoteSequence[deviceID] { - s.remoteSequence[deviceID] = f.Sequence - } - return true - }) - l.Debugf("loaded sequence for %q: %#v", folder, s.sequence) + if err := s.meta.fromDB(db, []byte(folder)); err != nil { + l.Infof("No stored folder metadata for %q: recalculating", folder) + s.recalcCounts() + } else if age := time.Since(s.meta.Created()); age > databaseRecheckInterval { + l.Infof("Stored folder metadata for %q is %v old; recalculating", folder, age) + s.recalcCounts() + } return &s } +func (s *FileSet) recalcCounts() { + s.meta = newMetadataTracker() + + s.db.checkGlobals([]byte(s.folder), s.meta) + + var deviceID protocol.DeviceID + s.db.withAllFolderTruncated([]byte(s.folder), func(device []byte, f FileInfoTruncated) bool { + copy(deviceID[:], device) + s.meta.addFile(deviceID, f) + return true + }) + + s.meta.SetCreated() + s.meta.toDB(s.db, []byte(s.folder)) +} + func (s *FileSet) Drop(device protocol.DeviceID) { l.Debugf("%s Drop(%v)", s.folder, device) s.updateMutex.Lock() defer s.updateMutex.Unlock() - s.db.dropDeviceFolder(device[:], []byte(s.folder), &s.globalSize) + s.db.dropDeviceFolder(device[:], []byte(s.folder), s.meta) if device == protocol.LocalDeviceID { s.blockmap.Drop() - s.localSize.reset() - // We deliberately do not reset s.sequence here. Dropping all files - // for the local device ID only happens in testing - which expects - // the sequence to be retained, like an old Replace() of all files - // would do. However, if we ever did it "in production" we would - // anyway want to retain the sequence for delta indexes to be happy. + s.meta.resetCounts(device) + // We deliberately do not reset the sequence number here. Dropping + // all files for the local device ID only happens in testing - which + // expects the sequence to be retained, like an old Replace() of all + // files would do. However, if we ever did it "in production" we + // would anyway want to retain the sequence for delta indexes to be + // happy. } else { // Here, on the other hand, we want to make sure that any file // announced from the remote is newer than our current sequence // number. - s.remoteSequence[device] = 0 + s.meta.resetAll(device) } + + s.meta.toDB(s.db, []byte(s.folder)) } func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) { @@ -181,12 +129,6 @@ func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) { s.updateMutex.Lock() defer s.updateMutex.Unlock() - s.updateLocked(device, fs) -} - -func (s *FileSet) updateLocked(device protocol.DeviceID, fs []protocol.FileInfo) { - // names must be normalized and the lock held - if device == protocol.LocalDeviceID { discards := make([]protocol.FileInfo, 0, len(fs)) updates := make([]protocol.FileInfo, 0, len(fs)) @@ -200,7 +142,7 @@ func (s *FileSet) updateLocked(device protocol.DeviceID, fs []protocol.FileInfo) continue } - nf.Sequence = atomic.AddInt64(&s.sequence, 1) + nf.Sequence = s.meta.nextSeq(protocol.LocalDeviceID) fs = append(fs, nf) if ok { @@ -210,10 +152,10 @@ func (s *FileSet) updateLocked(device protocol.DeviceID, fs []protocol.FileInfo) } s.blockmap.Discard(discards) s.blockmap.Update(updates) - } else { - s.remoteSequence[device] = maxSequence(fs) } - s.db.updateFiles([]byte(s.folder), device[:], fs, &s.localSize, &s.globalSize) + + s.db.updateFiles([]byte(s.folder), device[:], fs, s.meta) + s.meta.toDB(s.db, []byte(s.folder)) } func (s *FileSet) WithNeed(device protocol.DeviceID, fn Iterator) { @@ -298,21 +240,15 @@ func (s *FileSet) Availability(file string) []protocol.DeviceID { } func (s *FileSet) Sequence(device protocol.DeviceID) int64 { - if device == protocol.LocalDeviceID { - return atomic.LoadInt64(&s.sequence) - } - - s.updateMutex.Lock() - defer s.updateMutex.Unlock() - return s.remoteSequence[device] + return s.meta.Counts(device).Sequence } func (s *FileSet) LocalSize() Counts { - return s.localSize.Size() + return s.meta.Counts(protocol.LocalDeviceID) } func (s *FileSet) GlobalSize() Counts { - return s.globalSize.Size() + return s.meta.Counts(globalDeviceID) } func (s *FileSet) IndexID(device protocol.DeviceID) protocol.IndexID { @@ -339,29 +275,7 @@ func (s *FileSet) MtimeFS() *fs.MtimeFS { } func (s *FileSet) ListDevices() []protocol.DeviceID { - s.updateMutex.Lock() - devices := make([]protocol.DeviceID, 0, len(s.remoteSequence)) - for id, seq := range s.remoteSequence { - if seq > 0 { - devices = append(devices, id) - } - } - s.updateMutex.Unlock() - return devices -} - -// maxSequence returns the highest of the Sequence numbers found in -// the given slice of FileInfos. This should really be the Sequence of -// the last item, but Syncthing v0.14.0 and other implementations may not -// implement update sorting.... -func maxSequence(fs []protocol.FileInfo) int64 { - var max int64 - for _, f := range fs { - if f.Sequence > max { - max = f.Sequence - } - } - return max + return s.meta.devices() } // DropFolder clears out all information related to the given folder from the @@ -369,6 +283,7 @@ func maxSequence(fs []protocol.FileInfo) int64 { func DropFolder(db *Instance, folder string) { db.dropFolder([]byte(folder)) db.dropMtimes([]byte(folder)) + db.dropFolderMeta([]byte(folder)) bm := &BlockMap{ db: db, folder: db.folderIdx.ID([]byte(folder)), diff --git a/lib/db/set_test.go b/lib/db/set_test.go index 9ba855190..13eb36d26 100644 --- a/lib/db/set_test.go +++ b/lib/db/set_test.go @@ -169,7 +169,7 @@ func TestGlobalSet(t *testing.T) { t.Errorf("Global incorrect;\n A: %v !=\n E: %v", g, expectedGlobal) } - globalFiles, globalDirectories, globalDeleted, globalBytes := 0, 0, 0, int64(0) + globalFiles, globalDirectories, globalDeleted, globalBytes := int32(0), int32(0), int32(0), int64(0) for _, f := range g { if f.IsInvalid() { continue @@ -205,7 +205,7 @@ func TestGlobalSet(t *testing.T) { t.Errorf("Have incorrect;\n A: %v !=\n E: %v", h, localTot) } - haveFiles, haveDirectories, haveDeleted, haveBytes := 0, 0, 0, int64(0) + haveFiles, haveDirectories, haveDeleted, haveBytes := int32(0), int32(0), int32(0), int64(0) for _, f := range h { if f.IsInvalid() { continue diff --git a/lib/db/structs.go b/lib/db/structs.go index 593daa108..c8d4e6ed5 100644 --- a/lib/db/structs.go +++ b/lib/db/structs.go @@ -64,6 +64,10 @@ func (f FileInfoTruncated) ModTime() time.Time { return time.Unix(f.ModifiedS, int64(f.ModifiedNs)) } +func (f FileInfoTruncated) SequenceNo() int64 { + return f.Sequence +} + func (f FileInfoTruncated) ConvertToInvalidFileInfo(invalidatedBy protocol.ShortID) protocol.FileInfo { return protocol.FileInfo{ Name: f.Name, diff --git a/lib/db/structs.pb.go b/lib/db/structs.pb.go index 1420d6f59..fffa16e97 100644 --- a/lib/db/structs.pb.go +++ b/lib/db/structs.pb.go @@ -11,6 +11,8 @@ FileVersion VersionList FileInfoTruncated + Counts + CountsSet */ package db @@ -75,10 +77,39 @@ func (m *FileInfoTruncated) Reset() { *m = FileInfoTruncated{ func (*FileInfoTruncated) ProtoMessage() {} func (*FileInfoTruncated) Descriptor() ([]byte, []int) { return fileDescriptorStructs, []int{2} } +// For each folder and device we keep one of these to track the current +// counts and sequence. We also keep one for the global state of the folder. +type Counts struct { + Files int32 `protobuf:"varint,1,opt,name=files,proto3" json:"files,omitempty"` + Directories int32 `protobuf:"varint,2,opt,name=directories,proto3" json:"directories,omitempty"` + Symlinks int32 `protobuf:"varint,3,opt,name=symlinks,proto3" json:"symlinks,omitempty"` + Deleted int32 `protobuf:"varint,4,opt,name=deleted,proto3" json:"deleted,omitempty"` + Bytes int64 `protobuf:"varint,5,opt,name=bytes,proto3" json:"bytes,omitempty"` + Sequence int64 `protobuf:"varint,6,opt,name=sequence,proto3" json:"sequence,omitempty"` + DeviceID []byte `protobuf:"bytes,17,opt,name=deviceID,proto3" json:"deviceID,omitempty"` +} + +func (m *Counts) Reset() { *m = Counts{} } +func (m *Counts) String() string { return proto.CompactTextString(m) } +func (*Counts) ProtoMessage() {} +func (*Counts) Descriptor() ([]byte, []int) { return fileDescriptorStructs, []int{3} } + +type CountsSet struct { + Counts []Counts `protobuf:"bytes,1,rep,name=counts" json:"counts"` + Created int64 `protobuf:"varint,2,opt,name=created,proto3" json:"created,omitempty"` +} + +func (m *CountsSet) Reset() { *m = CountsSet{} } +func (m *CountsSet) String() string { return proto.CompactTextString(m) } +func (*CountsSet) ProtoMessage() {} +func (*CountsSet) Descriptor() ([]byte, []int) { return fileDescriptorStructs, []int{4} } + func init() { proto.RegisterType((*FileVersion)(nil), "db.FileVersion") proto.RegisterType((*VersionList)(nil), "db.VersionList") proto.RegisterType((*FileInfoTruncated)(nil), "db.FileInfoTruncated") + proto.RegisterType((*Counts)(nil), "db.Counts") + proto.RegisterType((*CountsSet)(nil), "db.CountsSet") } func (m *FileVersion) Marshal() (dAtA []byte, err error) { size := m.ProtoSize() @@ -257,6 +288,97 @@ func (m *FileInfoTruncated) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *Counts) Marshal() (dAtA []byte, err error) { + size := m.ProtoSize() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Counts) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Files != 0 { + dAtA[i] = 0x8 + i++ + i = encodeVarintStructs(dAtA, i, uint64(m.Files)) + } + if m.Directories != 0 { + dAtA[i] = 0x10 + i++ + i = encodeVarintStructs(dAtA, i, uint64(m.Directories)) + } + if m.Symlinks != 0 { + dAtA[i] = 0x18 + i++ + i = encodeVarintStructs(dAtA, i, uint64(m.Symlinks)) + } + if m.Deleted != 0 { + dAtA[i] = 0x20 + i++ + i = encodeVarintStructs(dAtA, i, uint64(m.Deleted)) + } + if m.Bytes != 0 { + dAtA[i] = 0x28 + i++ + i = encodeVarintStructs(dAtA, i, uint64(m.Bytes)) + } + if m.Sequence != 0 { + dAtA[i] = 0x30 + i++ + i = encodeVarintStructs(dAtA, i, uint64(m.Sequence)) + } + if len(m.DeviceID) > 0 { + dAtA[i] = 0x8a + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintStructs(dAtA, i, uint64(len(m.DeviceID))) + i += copy(dAtA[i:], m.DeviceID) + } + return i, nil +} + +func (m *CountsSet) Marshal() (dAtA []byte, err error) { + size := m.ProtoSize() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CountsSet) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Counts) > 0 { + for _, msg := range m.Counts { + dAtA[i] = 0xa + i++ + i = encodeVarintStructs(dAtA, i, uint64(msg.ProtoSize())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + if m.Created != 0 { + dAtA[i] = 0x10 + i++ + i = encodeVarintStructs(dAtA, i, uint64(m.Created)) + } + return i, nil +} + func encodeFixed64Structs(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) dAtA[offset+1] = uint8(v >> 8) @@ -357,6 +479,49 @@ func (m *FileInfoTruncated) ProtoSize() (n int) { return n } +func (m *Counts) ProtoSize() (n int) { + var l int + _ = l + if m.Files != 0 { + n += 1 + sovStructs(uint64(m.Files)) + } + if m.Directories != 0 { + n += 1 + sovStructs(uint64(m.Directories)) + } + if m.Symlinks != 0 { + n += 1 + sovStructs(uint64(m.Symlinks)) + } + if m.Deleted != 0 { + n += 1 + sovStructs(uint64(m.Deleted)) + } + if m.Bytes != 0 { + n += 1 + sovStructs(uint64(m.Bytes)) + } + if m.Sequence != 0 { + n += 1 + sovStructs(uint64(m.Sequence)) + } + l = len(m.DeviceID) + if l > 0 { + n += 2 + l + sovStructs(uint64(l)) + } + return n +} + +func (m *CountsSet) ProtoSize() (n int) { + var l int + _ = l + if len(m.Counts) > 0 { + for _, e := range m.Counts { + l = e.ProtoSize() + n += 1 + l + sovStructs(uint64(l)) + } + } + if m.Created != 0 { + n += 1 + sovStructs(uint64(m.Created)) + } + return n +} + func sovStructs(x uint64) (n int) { for { n++ @@ -913,6 +1078,301 @@ func (m *FileInfoTruncated) Unmarshal(dAtA []byte) error { } return nil } +func (m *Counts) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStructs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Counts: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Counts: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Files", wireType) + } + m.Files = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStructs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Files |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Directories", wireType) + } + m.Directories = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStructs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Directories |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Symlinks", wireType) + } + m.Symlinks = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStructs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Symlinks |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Deleted", wireType) + } + m.Deleted = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStructs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Deleted |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Bytes", wireType) + } + m.Bytes = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStructs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Bytes |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStructs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 17: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DeviceID", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStructs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthStructs + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DeviceID = append(m.DeviceID[:0], dAtA[iNdEx:postIndex]...) + if m.DeviceID == nil { + m.DeviceID = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipStructs(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthStructs + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CountsSet) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStructs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CountsSet: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CountsSet: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Counts", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStructs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthStructs + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Counts = append(m.Counts, Counts{}) + if err := m.Counts[len(m.Counts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Created", wireType) + } + m.Created = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStructs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Created |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipStructs(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthStructs + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipStructs(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 @@ -1021,36 +1481,43 @@ var ( func init() { proto.RegisterFile("structs.proto", fileDescriptorStructs) } var fileDescriptorStructs = []byte{ - // 487 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x52, 0xc1, 0x6a, 0xdb, 0x40, - 0x10, 0xf5, 0xc6, 0x4a, 0x6c, 0xaf, 0xe2, 0xb4, 0x59, 0x4a, 0x58, 0x0c, 0x95, 0x85, 0xa1, 0x20, - 0x0a, 0x95, 0x5b, 0x87, 0x5e, 0xda, 0x9b, 0x29, 0x81, 0x40, 0x29, 0x45, 0x09, 0x39, 0x15, 0x8c, - 0x25, 0x8d, 0xe5, 0xa5, 0xd2, 0xae, 0xa2, 0x5d, 0x19, 0xd4, 0x2f, 0xe9, 0x31, 0x9f, 0xe3, 0x63, - 0xcf, 0x3d, 0x84, 0xd6, 0xfd, 0x8e, 0x42, 0xd1, 0x4a, 0x56, 0xd4, 0x5b, 0x7b, 0x9b, 0x37, 0x7a, - 0x6f, 0xdf, 0x9b, 0x19, 0xe1, 0xa1, 0x54, 0x59, 0x1e, 0x28, 0xe9, 0xa6, 0x99, 0x50, 0x82, 0x1c, - 0x84, 0xfe, 0xe8, 0x45, 0xc4, 0xd4, 0x3a, 0xf7, 0xdd, 0x40, 0x24, 0xd3, 0x48, 0x44, 0x62, 0xaa, - 0x3f, 0xf9, 0xf9, 0x4a, 0x23, 0x0d, 0x74, 0x55, 0x49, 0x46, 0xaf, 0x5b, 0x74, 0x59, 0xf0, 0x40, - 0xad, 0x19, 0x8f, 0x5a, 0x55, 0xcc, 0xfc, 0xea, 0x85, 0x40, 0xc4, 0x53, 0x1f, 0xd2, 0x4a, 0x36, - 0xb9, 0xc5, 0xe6, 0x05, 0x8b, 0xe1, 0x06, 0x32, 0xc9, 0x04, 0x27, 0x2f, 0x71, 0x6f, 0x53, 0x95, - 0x14, 0xd9, 0xc8, 0x31, 0x67, 0x8f, 0xdd, 0xbd, 0xc8, 0xbd, 0x81, 0x40, 0x89, 0x6c, 0x6e, 0x6c, - 0xef, 0xc7, 0x1d, 0x6f, 0x4f, 0x23, 0x67, 0xf8, 0x28, 0x84, 0x0d, 0x0b, 0x80, 0x1e, 0xd8, 0xc8, - 0x39, 0xf6, 0x6a, 0x44, 0x28, 0xee, 0x31, 0xbe, 0x59, 0xc6, 0x2c, 0xa4, 0x5d, 0x1b, 0x39, 0x7d, - 0x6f, 0x0f, 0x27, 0x17, 0xd8, 0xac, 0xed, 0xde, 0x33, 0xa9, 0xc8, 0x2b, 0xdc, 0xaf, 0xdf, 0x92, - 0x14, 0xd9, 0x5d, 0xc7, 0x9c, 0x3d, 0x72, 0x43, 0xdf, 0x6d, 0xa5, 0xaa, 0x2d, 0x1b, 0xda, 0x1b, - 0xe3, 0xeb, 0xdd, 0xb8, 0x33, 0xf9, 0xdd, 0xc5, 0xa7, 0x25, 0xeb, 0x92, 0xaf, 0xc4, 0x75, 0x96, - 0xf3, 0x60, 0xa9, 0x20, 0x24, 0x04, 0x1b, 0x7c, 0x99, 0x80, 0x8e, 0x3f, 0xf0, 0x74, 0x4d, 0x9e, - 0x63, 0x43, 0x15, 0x69, 0x95, 0xf0, 0x64, 0x76, 0xf6, 0x30, 0x52, 0x23, 0x2f, 0x52, 0xf0, 0x34, - 0xa7, 0xd4, 0x4b, 0xf6, 0x05, 0x74, 0xe8, 0xae, 0xa7, 0x6b, 0x62, 0x63, 0x33, 0x85, 0x2c, 0x61, - 0xb2, 0x4a, 0x69, 0xd8, 0xc8, 0x19, 0x7a, 0xed, 0x16, 0x79, 0x8a, 0x71, 0x22, 0x42, 0xb6, 0x62, - 0x10, 0x2e, 0x24, 0x3d, 0xd4, 0xda, 0xc1, 0xbe, 0x73, 0x55, 0x2e, 0x23, 0x84, 0x18, 0x14, 0x84, - 0xf4, 0xa8, 0x5a, 0x46, 0x0d, 0xdb, 0x6b, 0xea, 0xfd, 0xb5, 0x26, 0xf2, 0x0c, 0x9f, 0x70, 0xb1, - 0x68, 0xfb, 0xf6, 0x35, 0x61, 0xc8, 0xc5, 0xc7, 0x96, 0x73, 0xeb, 0x62, 0x83, 0x7f, 0xbb, 0xd8, - 0x08, 0xf7, 0x25, 0xdc, 0xe6, 0xc0, 0x03, 0xa0, 0x58, 0x27, 0x6d, 0x30, 0x19, 0x63, 0xb3, 0x99, - 0x83, 0x4b, 0x6a, 0xda, 0xc8, 0x39, 0xf4, 0x9a, 0xd1, 0x3e, 0x48, 0xf2, 0xa9, 0x45, 0xf0, 0x0b, - 0x7a, 0x6c, 0x23, 0xc7, 0x98, 0xbf, 0x2d, 0x0d, 0xbe, 0xdf, 0x8f, 0xcf, 0xff, 0xe3, 0x1f, 0x74, - 0xaf, 0xd6, 0x22, 0x53, 0x97, 0xef, 0x1e, 0x5e, 0x9f, 0x17, 0xe5, 0xcc, 0xb2, 0x48, 0x62, 0xc6, - 0x3f, 0x2f, 0xd4, 0x32, 0x8b, 0x40, 0xd1, 0x53, 0x7d, 0xc6, 0x61, 0xdd, 0xbd, 0xd6, 0xcd, 0xea, - 0xfe, 0xf3, 0x27, 0xdb, 0x9f, 0x56, 0x67, 0xbb, 0xb3, 0xd0, 0xb7, 0x9d, 0x85, 0x7e, 0xec, 0xac, - 0xce, 0xdd, 0x2f, 0x0b, 0xf9, 0x47, 0xda, 0xe0, 0xfc, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfe, - 0xcd, 0x11, 0xef, 0x52, 0x03, 0x00, 0x00, + // 598 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x53, 0xcd, 0x6a, 0xdb, 0x4c, + 0x14, 0xb5, 0x62, 0xc9, 0xb1, 0x47, 0x71, 0xbe, 0x2f, 0x43, 0x08, 0xc2, 0x50, 0x5b, 0x18, 0x0a, + 0xa2, 0x50, 0xa5, 0x4d, 0xe8, 0xa6, 0xdd, 0xb9, 0x21, 0x10, 0x28, 0x6d, 0x99, 0x84, 0xac, 0x0a, + 0xc1, 0x92, 0xae, 0x9d, 0xa1, 0xf2, 0x8c, 0xa3, 0x19, 0x07, 0xd4, 0x27, 0xe9, 0x32, 0x0f, 0xd3, + 0x45, 0x96, 0x5d, 0x77, 0x11, 0x5a, 0xf7, 0x39, 0x0a, 0x45, 0x77, 0x24, 0x45, 0xe9, 0xaa, 0xdd, + 0xdd, 0x73, 0xff, 0xef, 0x39, 0x33, 0xa4, 0xaf, 0x74, 0xb6, 0x8a, 0xb5, 0x0a, 0x97, 0x99, 0xd4, + 0x92, 0x6e, 0x24, 0xd1, 0xe0, 0xe9, 0x9c, 0xeb, 0xcb, 0x55, 0x14, 0xc6, 0x72, 0xb1, 0x3f, 0x97, + 0x73, 0xb9, 0x8f, 0xa1, 0x68, 0x35, 0x43, 0x84, 0x00, 0x2d, 0x53, 0x32, 0x78, 0xd1, 0x48, 0x57, + 0xb9, 0x88, 0xf5, 0x25, 0x17, 0xf3, 0x86, 0x95, 0xf2, 0xc8, 0x74, 0x88, 0x65, 0xba, 0x1f, 0xc1, + 0xd2, 0x94, 0x8d, 0xaf, 0x88, 0x7b, 0xcc, 0x53, 0x38, 0x87, 0x4c, 0x71, 0x29, 0xe8, 0x33, 0xb2, + 0x79, 0x6d, 0x4c, 0xcf, 0xf2, 0xad, 0xc0, 0x3d, 0xf8, 0x3f, 0xac, 0x8a, 0xc2, 0x73, 0x88, 0xb5, + 0xcc, 0x26, 0xf6, 0xed, 0xdd, 0xa8, 0xc5, 0xaa, 0x34, 0xba, 0x47, 0x3a, 0x09, 0x5c, 0xf3, 0x18, + 0xbc, 0x0d, 0xdf, 0x0a, 0xb6, 0x58, 0x89, 0xa8, 0x47, 0x36, 0xb9, 0xb8, 0x9e, 0xa6, 0x3c, 0xf1, + 0xda, 0xbe, 0x15, 0x74, 0x59, 0x05, 0xc7, 0xc7, 0xc4, 0x2d, 0xc7, 0xbd, 0xe1, 0x4a, 0xd3, 0xe7, + 0xa4, 0x5b, 0xf6, 0x52, 0x9e, 0xe5, 0xb7, 0x03, 0xf7, 0xe0, 0xbf, 0x30, 0x89, 0xc2, 0xc6, 0x56, + 0xe5, 0xc8, 0x3a, 0xed, 0xa5, 0xfd, 0xf9, 0x66, 0xd4, 0x1a, 0xff, 0x6a, 0x93, 0x9d, 0x22, 0xeb, + 0x44, 0xcc, 0xe4, 0x59, 0xb6, 0x12, 0xf1, 0x54, 0x43, 0x42, 0x29, 0xb1, 0xc5, 0x74, 0x01, 0xb8, + 0x7e, 0x8f, 0xa1, 0x4d, 0x9f, 0x10, 0x5b, 0xe7, 0x4b, 0xb3, 0xe1, 0xf6, 0xc1, 0xde, 0xfd, 0x49, + 0x75, 0x79, 0xbe, 0x04, 0x86, 0x39, 0x45, 0xbd, 0xe2, 0x9f, 0x00, 0x97, 0x6e, 0x33, 0xb4, 0xa9, + 0x4f, 0xdc, 0x25, 0x64, 0x0b, 0xae, 0xcc, 0x96, 0xb6, 0x6f, 0x05, 0x7d, 0xd6, 0x74, 0xd1, 0x47, + 0x84, 0x2c, 0x64, 0xc2, 0x67, 0x1c, 0x92, 0x0b, 0xe5, 0x39, 0x58, 0xdb, 0xab, 0x3c, 0xa7, 0x05, + 0x19, 0x09, 0xa4, 0xa0, 0x21, 0xf1, 0x3a, 0x86, 0x8c, 0x12, 0x36, 0x69, 0xda, 0x7c, 0x40, 0x13, + 0x7d, 0x4c, 0xb6, 0x85, 0xbc, 0x68, 0xce, 0xed, 0x62, 0x42, 0x5f, 0xc8, 0xf7, 0x8d, 0xc9, 0x0d, + 0xc5, 0x7a, 0x7f, 0xa7, 0xd8, 0x80, 0x74, 0x15, 0x5c, 0xad, 0x40, 0xc4, 0xe0, 0x11, 0xdc, 0xb4, + 0xc6, 0x74, 0x44, 0xdc, 0xfa, 0x0e, 0xa1, 0x3c, 0xd7, 0xb7, 0x02, 0x87, 0xd5, 0xa7, 0xbd, 0x55, + 0xf4, 0x43, 0x23, 0x21, 0xca, 0xbd, 0x2d, 0xdf, 0x0a, 0xec, 0xc9, 0xab, 0x62, 0xc0, 0xb7, 0xbb, + 0xd1, 0xe1, 0x3f, 0xbc, 0xc1, 0xf0, 0xf4, 0x52, 0x66, 0xfa, 0xe4, 0xe8, 0xbe, 0xfb, 0x24, 0x2f, + 0x6e, 0x56, 0xf9, 0x22, 0xe5, 0xe2, 0xe3, 0x85, 0x9e, 0x66, 0x73, 0xd0, 0xde, 0x0e, 0xca, 0xd8, + 0x2f, 0xbd, 0x67, 0xe8, 0x2c, 0xf5, 0xff, 0x62, 0x91, 0xce, 0x6b, 0xb9, 0x12, 0x5a, 0xd1, 0x5d, + 0xe2, 0xcc, 0x78, 0x0a, 0x0a, 0x55, 0x77, 0x98, 0x01, 0x85, 0x6c, 0x09, 0xcf, 0x90, 0x03, 0x0e, + 0x0a, 0xd5, 0x77, 0x58, 0xd3, 0x85, 0x54, 0x98, 0xce, 0x0a, 0x05, 0x77, 0x58, 0x8d, 0x9b, 0x9a, + 0xd9, 0x18, 0xaa, 0x35, 0xdb, 0x25, 0x4e, 0x94, 0x6b, 0xa8, 0x74, 0x36, 0xe0, 0x01, 0xad, 0x9d, + 0x3f, 0x68, 0x1d, 0x90, 0xae, 0xf9, 0x16, 0x27, 0x47, 0x78, 0xd1, 0x16, 0xab, 0xf1, 0xf8, 0x1d, + 0xe9, 0x99, 0x2b, 0x4e, 0x41, 0xd3, 0x80, 0x74, 0x62, 0x04, 0xe5, 0x57, 0x20, 0xc5, 0x57, 0x30, + 0xe1, 0x52, 0xc6, 0x32, 0x5e, 0xac, 0x17, 0x67, 0x50, 0x3c, 0x79, 0x3c, 0xac, 0xcd, 0x2a, 0x38, + 0xd9, 0xbd, 0xfd, 0x31, 0x6c, 0xdd, 0xae, 0x87, 0xd6, 0xd7, 0xf5, 0xd0, 0xfa, 0xbe, 0x1e, 0xb6, + 0x6e, 0x7e, 0x0e, 0xad, 0xa8, 0x83, 0xc4, 0x1f, 0xfe, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x6e, 0x86, + 0x97, 0x21, 0x6a, 0x04, 0x00, 0x00, } diff --git a/lib/db/structs.proto b/lib/db/structs.proto index 7d112c88f..5af65cad2 100644 --- a/lib/db/structs.proto +++ b/lib/db/structs.proto @@ -37,3 +37,20 @@ message FileInfoTruncated { int64 sequence = 10; string symlink_target = 17; } + +// For each folder and device we keep one of these to track the current +// counts and sequence. We also keep one for the global state of the folder. +message Counts { + int32 files = 1; + int32 directories = 2; + int32 symlinks = 3; + int32 deleted = 4; + int64 bytes = 5; + int64 sequence = 6; // zero for the global state + bytes deviceID = 17; // device ID for remote devices, or special values for local/global +} + +message CountsSet { + repeated Counts counts = 1 [(gogoproto.nullable) = false]; + int64 created = 2; // unix nanos +} diff --git a/lib/model/model_test.go b/lib/model/model_test.go index da97ede75..50d13180c 100644 --- a/lib/model/model_test.go +++ b/lib/model/model_test.go @@ -179,6 +179,7 @@ func genFiles(n int) []protocol.FileInfo { ModifiedS: t, Sequence: int64(i + 1), Blocks: []protocol.BlockInfo{{Offset: 0, Size: 100, Hash: []byte("some hash bytes")}}, + Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 1}}}, } } @@ -1345,7 +1346,7 @@ func TestROScanRecovery(t *testing.T) { ldb := db.OpenMemory() set := db.NewFileSet("default", defaultFs, ldb) set.Update(protocol.LocalDeviceID, []protocol.FileInfo{ - {Name: "dummyfile"}, + {Name: "dummyfile", Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 1}}}}, }) fcfg := config.FolderConfiguration{ @@ -1433,7 +1434,7 @@ func TestRWScanRecovery(t *testing.T) { ldb := db.OpenMemory() set := db.NewFileSet("default", defaultFs, ldb) set.Update(protocol.LocalDeviceID, []protocol.FileInfo{ - {Name: "dummyfile"}, + {Name: "dummyfile", Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 1}}}}, }) fcfg := config.FolderConfiguration{ @@ -2530,7 +2531,7 @@ func TestIssue3496(t *testing.T) { // The one we added synthetically above t.Errorf("Incorrect need size; %d, %d != 1, 1234", need.Files, need.Bytes) } - if need.Deleted != len(localFiles)-1 { + if int(need.Deleted) != len(localFiles)-1 { // The rest t.Errorf("Incorrect need deletes; %d != %d", need.Deleted, len(localFiles)-1) } diff --git a/lib/protocol/bep.pb.go b/lib/protocol/bep.pb.go index d19ed9077..cb018572e 100644 --- a/lib/protocol/bep.pb.go +++ b/lib/protocol/bep.pb.go @@ -306,7 +306,7 @@ type FileInfo struct { NoPermissions bool `protobuf:"varint,8,opt,name=no_permissions,json=noPermissions,proto3" json:"no_permissions,omitempty"` Version Vector `protobuf:"bytes,9,opt,name=version" json:"version"` Sequence int64 `protobuf:"varint,10,opt,name=sequence,proto3" json:"sequence,omitempty"` - Blocks []BlockInfo `protobuf:"bytes,16,rep,name=Blocks,json=blocks" json:"Blocks"` + Blocks []BlockInfo `protobuf:"bytes,16,rep,name=Blocks" json:"Blocks"` SymlinkTarget string `protobuf:"bytes,17,opt,name=symlink_target,json=symlinkTarget,proto3" json:"symlink_target,omitempty"` } @@ -4217,61 +4217,61 @@ var fileDescriptorBep = []byte{ 0xf3, 0xb0, 0x17, 0x27, 0x71, 0xb8, 0xbe, 0x5c, 0x51, 0x85, 0xa3, 0x3c, 0x4e, 0x42, 0xd3, 0xc3, 0xe3, 0x36, 0x80, 0xd1, 0x52, 0xae, 0xb2, 0x5a, 0x7c, 0x2f, 0xae, 0xc5, 0xc1, 0x05, 0xf5, 0x82, 0x4e, 0x7b, 0xb3, 0xa2, 0xb9, 0x44, 0x3f, 0x82, 0x42, 0xd3, 0xa1, 0xe3, 0x17, 0xb1, 0xd2, 0xef, - 0x6c, 0xee, 0xc7, 0xec, 0xa9, 0x7c, 0x16, 0x46, 0x0c, 0x18, 0x86, 0xee, 0x2f, 0x67, 0x8e, 0xed, - 0xbe, 0x30, 0x02, 0xd3, 0x9b, 0x92, 0x40, 0xde, 0xe3, 0x0d, 0x3f, 0xb2, 0x0e, 0x99, 0xf1, 0xe7, - 0xe2, 0x1f, 0xbf, 0x39, 0xcc, 0xd4, 0x5d, 0x28, 0x27, 0xfb, 0x84, 0x25, 0x45, 0x27, 0x13, 0x9f, - 0x04, 0x2c, 0xff, 0x39, 0x1c, 0xcd, 0x92, 0xac, 0x66, 0x59, 0x40, 0x3c, 0xab, 0x08, 0xc4, 0x0b, - 0xd3, 0xbf, 0x60, 0x99, 0xae, 0x62, 0x36, 0x0e, 0x75, 0xfc, 0x92, 0x98, 0x2f, 0x0c, 0xe6, 0xe0, - 0x79, 0x2e, 0x85, 0x86, 0x27, 0xa6, 0x7f, 0x11, 0x9d, 0xf7, 0x4b, 0x28, 0x70, 0x5e, 0xd1, 0x17, - 0x50, 0x1a, 0xd3, 0x85, 0x1b, 0x6c, 0x7a, 0xfd, 0x5e, 0xba, 0x55, 0x30, 0x4f, 0x14, 0x59, 0x02, - 0xac, 0x9f, 0x42, 0x31, 0x72, 0xa1, 0x87, 0x49, 0x1f, 0x13, 0x9b, 0xf7, 0xae, 0x51, 0xb8, 0xdd, - 0xfc, 0x2f, 0x4d, 0x67, 0xc1, 0x2f, 0x2f, 0x62, 0x3e, 0xa9, 0xff, 0x45, 0x80, 0x22, 0x0e, 0xd3, - 0xe6, 0x07, 0xa9, 0x67, 0x23, 0xbf, 0xf5, 0x6c, 0x6c, 0x04, 0x96, 0xdd, 0x12, 0x58, 0xac, 0x91, - 0x5c, 0x4a, 0x23, 0x1b, 0xe6, 0xc4, 0xb7, 0x32, 0x97, 0x7f, 0x0b, 0x73, 0x85, 0x14, 0x73, 0x0f, - 0x61, 0x77, 0xe2, 0xd1, 0x19, 0x7b, 0x18, 0xa8, 0x67, 0x7a, 0xcb, 0xa8, 0x9e, 0x77, 0x42, 0xeb, - 0x30, 0x36, 0xd6, 0x0d, 0x28, 0x61, 0xe2, 0xcf, 0xa9, 0xeb, 0x93, 0x5b, 0xaf, 0x8d, 0x40, 0xb4, - 0xcc, 0xc0, 0x64, 0x97, 0xae, 0x62, 0x36, 0x46, 0x8f, 0x40, 0x1c, 0x53, 0x8b, 0x5f, 0x79, 0x37, - 0x5d, 0x43, 0x9a, 0xe7, 0x51, 0xaf, 0x45, 0x2d, 0x82, 0x19, 0xa0, 0x3e, 0x07, 0xa9, 0x4d, 0x5f, - 0xba, 0x0e, 0x35, 0xad, 0xbe, 0x47, 0xa7, 0x61, 0x83, 0xbe, 0xb5, 0xd1, 0xb4, 0xa1, 0xb8, 0x60, - 0xad, 0x28, 0x6e, 0x35, 0x0f, 0xb6, 0x5b, 0xc3, 0xf5, 0x8d, 0x78, 0xdf, 0x8a, 0xf5, 0x14, 0x2d, - 0xad, 0xff, 0x4d, 0x00, 0xe5, 0x76, 0x34, 0xea, 0x40, 0x85, 0x23, 0x8d, 0xd4, 0x3f, 0xc9, 0xd1, - 0xbb, 0x1c, 0xc4, 0xba, 0x12, 0x2c, 0x92, 0xf1, 0x5b, 0x1f, 0xb4, 0x94, 0xfe, 0x73, 0xef, 0xa6, - 0xff, 0x47, 0xb0, 0xc3, 0x74, 0x96, 0x3c, 0xdf, 0xa2, 0x9a, 0x3b, 0xca, 0x37, 0xb3, 0x52, 0x06, - 0x57, 0x47, 0x5c, 0x49, 0xcc, 0x5e, 0x2f, 0x80, 0xd8, 0xb7, 0xdd, 0x69, 0xfd, 0x10, 0xf2, 0x2d, - 0x87, 0xb2, 0x84, 0x15, 0x3c, 0x62, 0xfa, 0xd4, 0x8d, 0x79, 0xe4, 0xb3, 0xe3, 0xbf, 0x66, 0xa1, - 0x92, 0xfa, 0xb5, 0x42, 0x8f, 0x61, 0xb7, 0xd5, 0x3d, 0x1f, 0x0c, 0x35, 0x6c, 0xb4, 0x7a, 0xfa, - 0x69, 0xe7, 0x4c, 0xca, 0x28, 0x07, 0xab, 0xb5, 0x2a, 0xcf, 0x36, 0xa0, 0xed, 0xbf, 0xa6, 0x43, - 0xc8, 0x77, 0xf4, 0xb6, 0xf6, 0x5b, 0x49, 0x50, 0xee, 0xae, 0xd6, 0xaa, 0x94, 0x02, 0xf2, 0x27, - 0xe8, 0x13, 0xa8, 0x32, 0x80, 0x71, 0xde, 0x6f, 0x37, 0x86, 0x9a, 0x94, 0x55, 0x94, 0xd5, 0x5a, - 0xdd, 0xbf, 0x8e, 0x8b, 0x38, 0xff, 0x10, 0x8a, 0x58, 0xfb, 0xcd, 0xb9, 0x36, 0x18, 0x4a, 0x39, - 0x65, 0x7f, 0xb5, 0x56, 0x51, 0x0a, 0x18, 0xab, 0xe6, 0x21, 0x94, 0xb0, 0x36, 0xe8, 0xf7, 0xf4, - 0x81, 0x26, 0x89, 0xca, 0x0f, 0x56, 0x6b, 0xf5, 0xce, 0x16, 0x2a, 0xaa, 0xd2, 0x9f, 0xc0, 0x5e, - 0xbb, 0xf7, 0x95, 0xde, 0xed, 0x35, 0xda, 0x46, 0x1f, 0xf7, 0xce, 0xb0, 0x36, 0x18, 0x48, 0x79, - 0xe5, 0x70, 0xb5, 0x56, 0xdf, 0x4f, 0xe1, 0x6f, 0x14, 0xdd, 0x07, 0x20, 0xf6, 0x3b, 0xfa, 0x99, - 0x54, 0x50, 0xee, 0xac, 0xd6, 0xea, 0x7b, 0x29, 0x68, 0x48, 0x6a, 0x18, 0x71, 0xab, 0xdb, 0x1b, - 0x68, 0x52, 0xf1, 0x46, 0xc4, 0x8c, 0xec, 0xe3, 0xdf, 0x01, 0xba, 0xf9, 0xf3, 0x89, 0x1e, 0x80, - 0xa8, 0xf7, 0x74, 0x4d, 0xca, 0xf0, 0xf8, 0x6f, 0x22, 0x74, 0xea, 0x12, 0x54, 0x87, 0x5c, 0xf7, - 0xeb, 0x2f, 0x25, 0x41, 0xf9, 0xe1, 0x6a, 0xad, 0xde, 0xbb, 0x09, 0xea, 0x7e, 0xfd, 0xe5, 0x31, - 0x85, 0x4a, 0x7a, 0xe3, 0x3a, 0x94, 0x9e, 0x6a, 0xc3, 0x46, 0xbb, 0x31, 0x6c, 0x48, 0x19, 0x7e, - 0xa5, 0xd8, 0xfd, 0x94, 0x04, 0x26, 0x13, 0xe1, 0x01, 0xe4, 0x75, 0xed, 0x99, 0x86, 0x25, 0x41, - 0xd9, 0x5b, 0xad, 0xd5, 0x9d, 0x18, 0xa0, 0x93, 0x4b, 0xe2, 0xa1, 0x1a, 0x14, 0x1a, 0xdd, 0xaf, - 0x1a, 0xcf, 0x07, 0x52, 0x56, 0x41, 0xab, 0xb5, 0xba, 0x1b, 0xbb, 0x1b, 0xce, 0x4b, 0x73, 0xe9, - 0x1f, 0xff, 0x57, 0x80, 0x6a, 0xfa, 0xc1, 0x45, 0x35, 0x10, 0x4f, 0x3b, 0x5d, 0x2d, 0x3e, 0x2e, - 0xed, 0x0b, 0xc7, 0xe8, 0x08, 0xca, 0xed, 0x0e, 0xd6, 0x5a, 0xc3, 0x1e, 0x7e, 0x1e, 0xc7, 0x92, - 0x06, 0xb5, 0x6d, 0x8f, 0x15, 0xf8, 0x12, 0xfd, 0x0c, 0xaa, 0x83, 0xe7, 0x4f, 0xbb, 0x1d, 0xfd, - 0xd7, 0x06, 0xdb, 0x31, 0xab, 0x3c, 0x5a, 0xad, 0xd5, 0xfb, 0x5b, 0x60, 0x32, 0xf7, 0xc8, 0xd8, - 0x0c, 0x88, 0x35, 0xe0, 0x8f, 0x48, 0xe8, 0x2c, 0x09, 0xa8, 0x05, 0x7b, 0xf1, 0xd2, 0xcd, 0x61, - 0x39, 0xe5, 0x93, 0xd5, 0x5a, 0xfd, 0xe8, 0x7b, 0xd7, 0x27, 0xa7, 0x97, 0x04, 0xf4, 0x00, 0x8a, - 0xd1, 0x26, 0x71, 0x25, 0xa5, 0x97, 0x46, 0x0b, 0x8e, 0xff, 0x2c, 0x40, 0x39, 0x69, 0x57, 0x21, - 0xe1, 0x7a, 0xcf, 0xd0, 0x30, 0xee, 0xe1, 0x98, 0x81, 0xc4, 0xa9, 0x53, 0x36, 0x44, 0xf7, 0xa1, - 0x78, 0xa6, 0xe9, 0x1a, 0xee, 0xb4, 0x62, 0x61, 0x24, 0x90, 0x33, 0xe2, 0x12, 0xcf, 0x1e, 0xa3, - 0x8f, 0xa1, 0xaa, 0xf7, 0x8c, 0xc1, 0x79, 0xeb, 0x49, 0x1c, 0x3a, 0x3b, 0x3f, 0xb5, 0xd5, 0x60, - 0x31, 0xbe, 0x60, 0x7c, 0x1e, 0x87, 0x1a, 0x7a, 0xd6, 0xe8, 0x76, 0xda, 0x1c, 0x9a, 0x53, 0xe4, - 0xd5, 0x5a, 0xbd, 0x9b, 0x40, 0x3b, 0xfc, 0xcf, 0x23, 0xc4, 0x1e, 0x5b, 0x50, 0xfb, 0xfe, 0xc6, - 0x84, 0x54, 0x28, 0x34, 0xfa, 0x7d, 0x4d, 0x6f, 0xc7, 0xb7, 0xdf, 0xf8, 0x1a, 0xf3, 0x39, 0x71, - 0xad, 0x10, 0x71, 0xda, 0xc3, 0x67, 0xda, 0x30, 0xbe, 0xfc, 0x06, 0x71, 0x4a, 0xc3, 0x17, 0xbc, - 0x79, 0xf0, 0xfa, 0xbb, 0x5a, 0xe6, 0xdb, 0xef, 0x6a, 0x99, 0xd7, 0x57, 0x35, 0xe1, 0xdb, 0xab, - 0x9a, 0xf0, 0x8f, 0xab, 0x5a, 0xe6, 0x5f, 0x57, 0x35, 0xe1, 0x9b, 0x7f, 0xd6, 0x84, 0x51, 0x81, - 0x35, 0xb2, 0x2f, 0xfe, 0x17, 0x00, 0x00, 0xff, 0xff, 0xe3, 0x49, 0x45, 0xc4, 0x8f, 0x0e, 0x00, + 0x6c, 0xee, 0xc7, 0xec, 0xa9, 0x7c, 0x46, 0xc0, 0x30, 0x74, 0x7f, 0x39, 0x73, 0x6c, 0xf7, 0x85, + 0x11, 0x98, 0xde, 0x94, 0x04, 0xf2, 0x1e, 0x6f, 0xf8, 0x91, 0x75, 0xc8, 0x8c, 0x3f, 0x17, 0xff, + 0xf8, 0xcd, 0x61, 0xa6, 0xee, 0x42, 0x39, 0xd9, 0x27, 0x2c, 0x29, 0x3a, 0x99, 0xf8, 0x24, 0x60, + 0xf9, 0xcf, 0xe1, 0x68, 0x96, 0x64, 0x35, 0xcb, 0x02, 0xe2, 0x59, 0x45, 0x20, 0x5e, 0x98, 0xfe, + 0x05, 0xcb, 0x74, 0x15, 0xb3, 0x71, 0xa8, 0xe3, 0x97, 0xc4, 0x7c, 0x61, 0x30, 0x07, 0xcf, 0x73, + 0x29, 0x34, 0x3c, 0x31, 0xfd, 0x8b, 0xe8, 0xbc, 0x5f, 0x42, 0x81, 0xf3, 0x8a, 0xbe, 0x80, 0xd2, + 0x98, 0x2e, 0xdc, 0x60, 0xd3, 0xeb, 0xf7, 0xd2, 0xad, 0x82, 0x79, 0xa2, 0xc8, 0x12, 0x60, 0xfd, + 0x14, 0x8a, 0x91, 0x0b, 0x3d, 0x4c, 0xfa, 0x98, 0xd8, 0xbc, 0x77, 0x8d, 0xc2, 0xed, 0xe6, 0x7f, + 0x69, 0x3a, 0x0b, 0x7e, 0x79, 0x11, 0xf3, 0x49, 0xfd, 0x2f, 0x02, 0x14, 0x71, 0x98, 0x36, 0x3f, + 0x48, 0x3d, 0x1b, 0xf9, 0xad, 0x67, 0x63, 0x23, 0xb0, 0xec, 0x96, 0xc0, 0x62, 0x8d, 0xe4, 0x52, + 0x1a, 0xd9, 0x30, 0x27, 0xbe, 0x95, 0xb9, 0xfc, 0x5b, 0x98, 0x2b, 0xa4, 0x98, 0x7b, 0x08, 0xbb, + 0x13, 0x8f, 0xce, 0xd8, 0xc3, 0x40, 0x3d, 0xd3, 0x5b, 0x46, 0xf5, 0xbc, 0x13, 0x5a, 0x87, 0xb1, + 0xb1, 0x6e, 0x40, 0x09, 0x13, 0x7f, 0x4e, 0x5d, 0x9f, 0xdc, 0x7a, 0x6d, 0x04, 0xa2, 0x65, 0x06, + 0x26, 0xbb, 0x74, 0x15, 0xb3, 0x31, 0x7a, 0x04, 0xe2, 0x98, 0x5a, 0xfc, 0xca, 0xbb, 0xe9, 0x1a, + 0xd2, 0x3c, 0x8f, 0x7a, 0x2d, 0x6a, 0x11, 0xcc, 0x00, 0xf5, 0x39, 0x48, 0x6d, 0xfa, 0xd2, 0x75, + 0xa8, 0x69, 0xf5, 0x3d, 0x3a, 0x0d, 0x1b, 0xf4, 0xad, 0x8d, 0xa6, 0x0d, 0xc5, 0x05, 0x6b, 0x45, + 0x71, 0xab, 0x79, 0xb0, 0xdd, 0x1a, 0xae, 0x6f, 0xc4, 0xfb, 0x56, 0xac, 0xa7, 0x68, 0x69, 0xfd, + 0x6f, 0x02, 0x28, 0xb7, 0xa3, 0x51, 0x07, 0x2a, 0x1c, 0x69, 0xa4, 0xfe, 0x49, 0x8e, 0xde, 0xe5, + 0x20, 0xd6, 0x95, 0x60, 0x91, 0x8c, 0xdf, 0xfa, 0xa0, 0xa5, 0xf4, 0x9f, 0x7b, 0x37, 0xfd, 0x3f, + 0x82, 0x9d, 0x51, 0x28, 0x98, 0xe4, 0xf9, 0x16, 0xd5, 0xdc, 0x51, 0xbe, 0x99, 0x95, 0x32, 0xb8, + 0x3a, 0xe2, 0x4a, 0x62, 0xf6, 0x7a, 0x01, 0xc4, 0xbe, 0xed, 0x4e, 0xeb, 0x87, 0x90, 0x6f, 0x39, + 0x94, 0x25, 0xac, 0xe0, 0x11, 0xd3, 0xa7, 0x6e, 0xcc, 0x23, 0x9f, 0x1d, 0xff, 0x35, 0x0b, 0x95, + 0xd4, 0xaf, 0x15, 0x7a, 0x0c, 0xbb, 0xad, 0xee, 0xf9, 0x60, 0xa8, 0x61, 0xa3, 0xd5, 0xd3, 0x4f, + 0x3b, 0x67, 0x52, 0x46, 0x39, 0x58, 0xad, 0x55, 0x79, 0xb6, 0x01, 0x6d, 0xff, 0x35, 0x1d, 0x42, + 0xbe, 0xa3, 0xb7, 0xb5, 0xdf, 0x4a, 0x82, 0x72, 0x77, 0xb5, 0x56, 0xa5, 0x14, 0x90, 0x3f, 0x41, + 0x9f, 0x40, 0x95, 0x01, 0x8c, 0xf3, 0x7e, 0xbb, 0x31, 0xd4, 0xa4, 0xac, 0xa2, 0xac, 0xd6, 0xea, + 0xfe, 0x75, 0x5c, 0xc4, 0xf9, 0x87, 0x50, 0xc4, 0xda, 0x6f, 0xce, 0xb5, 0xc1, 0x50, 0xca, 0x29, + 0xfb, 0xab, 0xb5, 0x8a, 0x52, 0xc0, 0x58, 0x35, 0x0f, 0xa1, 0x84, 0xb5, 0x41, 0xbf, 0xa7, 0x0f, + 0x34, 0x49, 0x54, 0x7e, 0xb0, 0x5a, 0xab, 0x77, 0xb6, 0x50, 0x51, 0x95, 0xfe, 0x04, 0xf6, 0xda, + 0xbd, 0xaf, 0xf4, 0x6e, 0xaf, 0xd1, 0x36, 0xfa, 0xb8, 0x77, 0x86, 0xb5, 0xc1, 0x40, 0xca, 0x2b, + 0x87, 0xab, 0xb5, 0xfa, 0x7e, 0x0a, 0x7f, 0xa3, 0xe8, 0x3e, 0x00, 0xb1, 0xdf, 0xd1, 0xcf, 0xa4, + 0x82, 0x72, 0x67, 0xb5, 0x56, 0xdf, 0x4b, 0x41, 0x43, 0x52, 0xc3, 0x88, 0x5b, 0xdd, 0xde, 0x40, + 0x93, 0x8a, 0x37, 0x22, 0x66, 0x64, 0x1f, 0xff, 0x0e, 0xd0, 0xcd, 0x9f, 0x4f, 0xf4, 0x00, 0x44, + 0xbd, 0xa7, 0x6b, 0x52, 0x86, 0xc7, 0x7f, 0x13, 0xa1, 0x53, 0x97, 0xa0, 0x3a, 0xe4, 0xba, 0x5f, + 0x7f, 0x29, 0x09, 0xca, 0x0f, 0x57, 0x6b, 0xf5, 0xde, 0x4d, 0x50, 0xf7, 0xeb, 0x2f, 0x8f, 0x29, + 0x54, 0xd2, 0x1b, 0xd7, 0xa1, 0xf4, 0x54, 0x1b, 0x36, 0xda, 0x8d, 0x61, 0x43, 0xca, 0xf0, 0x2b, + 0xc5, 0xee, 0xa7, 0x24, 0x30, 0x99, 0x08, 0x0f, 0x20, 0xaf, 0x6b, 0xcf, 0x34, 0x2c, 0x09, 0xca, + 0xde, 0x6a, 0xad, 0xee, 0xc4, 0x00, 0x9d, 0x5c, 0x12, 0x0f, 0xd5, 0xa0, 0xd0, 0xe8, 0x7e, 0xd5, + 0x78, 0x3e, 0x90, 0xb2, 0x0a, 0x5a, 0xad, 0xd5, 0xdd, 0xd8, 0xdd, 0x70, 0x5e, 0x9a, 0x4b, 0xff, + 0xf8, 0xbf, 0x02, 0x54, 0xd3, 0x0f, 0x2e, 0xaa, 0x81, 0x78, 0xda, 0xe9, 0x6a, 0xf1, 0x71, 0x69, + 0x5f, 0x38, 0x46, 0x47, 0x50, 0x6e, 0x77, 0xb0, 0xd6, 0x1a, 0xf6, 0xf0, 0xf3, 0x38, 0x96, 0x34, + 0xa8, 0x6d, 0x7b, 0xac, 0xc0, 0x97, 0xe8, 0x67, 0x50, 0x1d, 0x3c, 0x7f, 0xda, 0xed, 0xe8, 0xbf, + 0x36, 0xd8, 0x8e, 0x59, 0xe5, 0xd1, 0x6a, 0xad, 0xde, 0xdf, 0x02, 0x93, 0xb9, 0x47, 0xc6, 0x66, + 0x40, 0xac, 0x01, 0x7f, 0x44, 0x42, 0x67, 0x49, 0x40, 0x2d, 0xd8, 0x8b, 0x97, 0x6e, 0x0e, 0xcb, + 0x29, 0x9f, 0xac, 0xd6, 0xea, 0x47, 0xdf, 0xbb, 0x3e, 0x39, 0xbd, 0x24, 0xa0, 0x07, 0x50, 0x8c, + 0x36, 0x89, 0x2b, 0x29, 0xbd, 0x34, 0x5a, 0x70, 0xfc, 0x67, 0x01, 0xca, 0x49, 0xbb, 0x0a, 0x09, + 0xd7, 0x7b, 0x86, 0x86, 0x71, 0x0f, 0xc7, 0x0c, 0x24, 0x4e, 0x9d, 0xb2, 0x21, 0xba, 0x0f, 0xc5, + 0x33, 0x4d, 0xd7, 0x70, 0xa7, 0x15, 0x0b, 0x23, 0x81, 0x9c, 0x11, 0x97, 0x78, 0xf6, 0x18, 0x7d, + 0x0c, 0x55, 0xbd, 0x67, 0x0c, 0xce, 0x5b, 0x4f, 0xe2, 0xd0, 0xd9, 0xf9, 0xa9, 0xad, 0x06, 0x8b, + 0xf1, 0x05, 0xe3, 0xf3, 0x38, 0xd4, 0xd0, 0xb3, 0x46, 0xb7, 0xd3, 0xe6, 0xd0, 0x9c, 0x22, 0xaf, + 0xd6, 0xea, 0xdd, 0x04, 0xda, 0xe1, 0x7f, 0x1e, 0x21, 0xf6, 0xd8, 0x82, 0xda, 0xf7, 0x37, 0x26, + 0xa4, 0x42, 0xa1, 0xd1, 0xef, 0x6b, 0x7a, 0x3b, 0xbe, 0xfd, 0xc6, 0xd7, 0x98, 0xcf, 0x89, 0x6b, + 0x85, 0x88, 0xd3, 0x1e, 0x3e, 0xd3, 0x86, 0xf1, 0xe5, 0x37, 0x88, 0x53, 0x1a, 0xbe, 0xe0, 0xcd, + 0x83, 0xd7, 0xdf, 0xd5, 0x32, 0xdf, 0x7e, 0x57, 0xcb, 0xbc, 0xbe, 0xaa, 0x09, 0xdf, 0x5e, 0xd5, + 0x84, 0x7f, 0x5c, 0xd5, 0x32, 0xff, 0xba, 0xaa, 0x09, 0xdf, 0xfc, 0xb3, 0x26, 0x8c, 0x0a, 0xac, + 0x91, 0x7d, 0xf1, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xad, 0x8a, 0xef, 0x7f, 0x8f, 0x0e, 0x00, 0x00, } diff --git a/lib/protocol/bep_extensions.go b/lib/protocol/bep_extensions.go index 45a0b7706..6c339f124 100644 --- a/lib/protocol/bep_extensions.go +++ b/lib/protocol/bep_extensions.go @@ -88,6 +88,10 @@ func (f FileInfo) ModTime() time.Time { return time.Unix(f.ModifiedS, int64(f.ModifiedNs)) } +func (f FileInfo) SequenceNo() int64 { + return f.Sequence +} + // WinsConflict returns true if "f" is the one to choose when it is in // conflict with "other". func (f FileInfo) WinsConflict(other FileInfo) bool {