Add Local Version field to files, send index in segments.

This commit is contained in:
Jakob Borg 2014-07-15 13:04:37 +02:00
parent fccdd85cc1
commit 8b349945de
16 changed files with 291 additions and 288 deletions

View File

@ -180,7 +180,7 @@ func restGetModelVersion(m *model.Model, w http.ResponseWriter, r *http.Request)
var repo = qs.Get("repo") var repo = qs.Get("repo")
var res = make(map[string]interface{}) var res = make(map[string]interface{})
res["version"] = m.Version(repo) res["version"] = m.LocalVersion(repo)
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res) json.NewEncoder(w).Encode(res)
@ -210,7 +210,7 @@ func restGetModel(m *model.Model, w http.ResponseWriter, r *http.Request) {
res["inSyncFiles"], res["inSyncBytes"] = globalFiles-needFiles, globalBytes-needBytes res["inSyncFiles"], res["inSyncBytes"] = globalFiles-needFiles, globalBytes-needBytes
res["state"] = m.State(repo) res["state"] = m.State(repo)
res["version"] = m.Version(repo) res["version"] = m.LocalVersion(repo)
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res) json.NewEncoder(w).Encode(res)

View File

@ -290,11 +290,6 @@ func main() {
rateBucket = ratelimit.NewBucketWithRate(float64(1000*cfg.Options.MaxSendKbps), int64(5*1000*cfg.Options.MaxSendKbps)) rateBucket = ratelimit.NewBucketWithRate(float64(1000*cfg.Options.MaxSendKbps), int64(5*1000*cfg.Options.MaxSendKbps))
} }
havePersistentIndex := false
if fi, err := os.Stat(filepath.Join(confDir, "index")); err == nil && fi.IsDir() {
havePersistentIndex = true
}
db, err := leveldb.OpenFile(filepath.Join(confDir, "index"), nil) db, err := leveldb.OpenFile(filepath.Join(confDir, "index"), nil)
if err != nil { if err != nil {
l.Fatalln("leveldb.OpenFile():", err) l.Fatalln("leveldb.OpenFile():", err)
@ -364,11 +359,6 @@ nextRepo:
// Walk the repository and update the local model before establishing any // Walk the repository and update the local model before establishing any
// connections to other nodes. // connections to other nodes.
if !havePersistentIndex {
// There's no new style index, load old ones
l.Infoln("Loading legacy index files")
m.LoadIndexes(confDir)
}
m.CleanRepos() m.CleanRepos()
l.Infoln("Performing initial repository scan") l.Infoln("Performing initial repository scan")
m.ScanRepos() m.ScanRepos()
@ -645,9 +635,10 @@ next:
if rateBucket != nil { if rateBucket != nil {
wr = &limitedWriter{conn, rateBucket} wr = &limitedWriter{conn, rateBucket}
} }
protoConn := protocol.NewConnection(remoteID, conn, wr, m) name := fmt.Sprintf("%s-%s", conn.LocalAddr(), conn.RemoteAddr())
protoConn := protocol.NewConnection(remoteID, conn, wr, m, name)
l.Infof("Established secure connection to %s at %v", remoteID, conn.RemoteAddr()) l.Infof("Established secure connection to %s at %s", remoteID, name)
if debugNet { if debugNet {
l.Debugf("cipher suite %04X", conn.ConnectionState().CipherSuite) l.Debugf("cipher suite %04X", conn.ConnectionState().CipherSuite)
} }

View File

@ -3,6 +3,7 @@ package files
import ( import (
"bytes" "bytes"
"sort" "sort"
"sync"
"github.com/calmh/syncthing/lamport" "github.com/calmh/syncthing/lamport"
"github.com/calmh/syncthing/protocol" "github.com/calmh/syncthing/protocol"
@ -12,6 +13,22 @@ import (
"github.com/syndtr/goleveldb/leveldb/util" "github.com/syndtr/goleveldb/leveldb/util"
) )
var (
clockTick uint64
clockMut sync.Mutex
)
func clock(v uint64) uint64 {
clockMut.Lock()
defer clockMut.Unlock()
if v > clockTick {
clockTick = v + 1
} else {
clockTick++
}
return clockTick
}
const ( const (
keyTypeNode = iota keyTypeNode = iota
keyTypeGlobal keyTypeGlobal
@ -91,11 +108,11 @@ func globalKeyName(key []byte) []byte {
return key[1+64:] return key[1+64:]
} }
type deletionHandler func(db dbReader, batch dbWriter, repo, node, name []byte, dbi iterator.Iterator) bool type deletionHandler func(db dbReader, batch dbWriter, repo, node, name []byte, dbi iterator.Iterator) uint64
type fileIterator func(f protocol.FileInfo) bool type fileIterator func(f protocol.FileInfo) bool
func ldbGenericReplace(db *leveldb.DB, repo, node []byte, fs []protocol.FileInfo, deleteFn deletionHandler) bool { func ldbGenericReplace(db *leveldb.DB, repo, node []byte, fs []protocol.FileInfo, deleteFn deletionHandler) uint64 {
sort.Sort(fileList(fs)) // sort list on name, same as on disk sort.Sort(fileList(fs)) // sort list on name, same as on disk
start := nodeKey(repo, node, nil) // before all repo/node files start := nodeKey(repo, node, nil) // before all repo/node files
@ -112,7 +129,7 @@ func ldbGenericReplace(db *leveldb.DB, repo, node []byte, fs []protocol.FileInfo
moreDb := dbi.Next() moreDb := dbi.Next()
fsi := 0 fsi := 0
changed := false var maxLocalVer uint64
for { for {
var newName, oldName []byte var newName, oldName []byte
@ -144,9 +161,10 @@ func ldbGenericReplace(db *leveldb.DB, repo, node []byte, fs []protocol.FileInfo
switch { switch {
case moreFs && (!moreDb || cmp == -1): case moreFs && (!moreDb || cmp == -1):
changed = true
// Disk is missing this file. Insert it. // Disk is missing this file. Insert it.
ldbInsert(batch, repo, node, newName, fs[fsi]) if lv := ldbInsert(batch, repo, node, newName, fs[fsi]); lv > maxLocalVer {
maxLocalVer = lv
}
ldbUpdateGlobal(snap, batch, repo, node, newName, fs[fsi].Version) ldbUpdateGlobal(snap, batch, repo, node, newName, fs[fsi].Version)
fsi++ fsi++
@ -155,9 +173,10 @@ func ldbGenericReplace(db *leveldb.DB, repo, node []byte, fs []protocol.FileInfo
var ef protocol.FileInfo var ef protocol.FileInfo
ef.UnmarshalXDR(dbi.Value()) ef.UnmarshalXDR(dbi.Value())
if fs[fsi].Version > ef.Version { if fs[fsi].Version > ef.Version {
ldbInsert(batch, repo, node, newName, fs[fsi]) if lv := ldbInsert(batch, repo, node, newName, fs[fsi]); lv > maxLocalVer {
maxLocalVer = lv
}
ldbUpdateGlobal(snap, batch, repo, node, newName, fs[fsi].Version) ldbUpdateGlobal(snap, batch, repo, node, newName, fs[fsi].Version)
changed = true
} }
// Iterate both sides. // Iterate both sides.
fsi++ fsi++
@ -165,8 +184,8 @@ func ldbGenericReplace(db *leveldb.DB, repo, node []byte, fs []protocol.FileInfo
case moreDb && (!moreFs || cmp == 1): case moreDb && (!moreFs || cmp == 1):
if deleteFn != nil { if deleteFn != nil {
if deleteFn(snap, batch, repo, node, oldName, dbi) { if lv := deleteFn(snap, batch, repo, node, oldName, dbi); lv > maxLocalVer {
changed = true maxLocalVer = lv
} }
} }
moreDb = dbi.Next() moreDb = dbi.Next()
@ -178,23 +197,24 @@ func ldbGenericReplace(db *leveldb.DB, repo, node []byte, fs []protocol.FileInfo
panic(err) panic(err)
} }
return changed return maxLocalVer
} }
func ldbReplace(db *leveldb.DB, repo, node []byte, fs []protocol.FileInfo) bool { func ldbReplace(db *leveldb.DB, repo, node []byte, fs []protocol.FileInfo) uint64 {
return ldbGenericReplace(db, repo, node, fs, func(db dbReader, batch dbWriter, repo, node, name []byte, dbi iterator.Iterator) bool { // TODO: Return the remaining maxLocalVer?
return ldbGenericReplace(db, repo, node, fs, func(db dbReader, batch dbWriter, repo, node, name []byte, dbi iterator.Iterator) uint64 {
// Disk has files that we are missing. Remove it. // Disk has files that we are missing. Remove it.
if debug { if debug {
l.Debugf("delete; repo=%q node=%x name=%q", repo, node, name) l.Debugf("delete; repo=%q node=%x name=%q", repo, node, name)
} }
batch.Delete(dbi.Key()) batch.Delete(dbi.Key())
ldbRemoveFromGlobal(db, batch, repo, node, name) ldbRemoveFromGlobal(db, batch, repo, node, name)
return true return 0
}) })
} }
func ldbReplaceWithDelete(db *leveldb.DB, repo, node []byte, fs []protocol.FileInfo) bool { func ldbReplaceWithDelete(db *leveldb.DB, repo, node []byte, fs []protocol.FileInfo) uint64 {
return ldbGenericReplace(db, repo, node, fs, func(db dbReader, batch dbWriter, repo, node, name []byte, dbi iterator.Iterator) bool { return ldbGenericReplace(db, repo, node, fs, func(db dbReader, batch dbWriter, repo, node, name []byte, dbi iterator.Iterator) uint64 {
var f protocol.FileInfo var f protocol.FileInfo
err := f.UnmarshalXDR(dbi.Value()) err := f.UnmarshalXDR(dbi.Value())
if err != nil { if err != nil {
@ -204,18 +224,20 @@ func ldbReplaceWithDelete(db *leveldb.DB, repo, node []byte, fs []protocol.FileI
if debug { if debug {
l.Debugf("mark deleted; repo=%q node=%x name=%q", repo, node, name) l.Debugf("mark deleted; repo=%q node=%x name=%q", repo, node, name)
} }
ts := clock(f.LocalVersion)
f.Blocks = nil f.Blocks = nil
f.Version = lamport.Default.Tick(f.Version) f.Version = lamport.Default.Tick(f.Version)
f.Flags |= protocol.FlagDeleted f.Flags |= protocol.FlagDeleted
f.LocalVersion = ts
batch.Put(dbi.Key(), f.MarshalXDR()) batch.Put(dbi.Key(), f.MarshalXDR())
ldbUpdateGlobal(db, batch, repo, node, nodeKeyName(dbi.Key()), f.Version) ldbUpdateGlobal(db, batch, repo, node, nodeKeyName(dbi.Key()), f.Version)
return true return ts
} }
return false return 0
}) })
} }
func ldbUpdate(db *leveldb.DB, repo, node []byte, fs []protocol.FileInfo) bool { func ldbUpdate(db *leveldb.DB, repo, node []byte, fs []protocol.FileInfo) uint64 {
batch := new(leveldb.Batch) batch := new(leveldb.Batch)
snap, err := db.GetSnapshot() snap, err := db.GetSnapshot()
if err != nil { if err != nil {
@ -223,12 +245,15 @@ func ldbUpdate(db *leveldb.DB, repo, node []byte, fs []protocol.FileInfo) bool {
} }
defer snap.Release() defer snap.Release()
var maxLocalVer uint64
for _, f := range fs { for _, f := range fs {
name := []byte(f.Name) name := []byte(f.Name)
fk := nodeKey(repo, node, name) fk := nodeKey(repo, node, name)
bs, err := snap.Get(fk, nil) bs, err := snap.Get(fk, nil)
if err == leveldb.ErrNotFound { if err == leveldb.ErrNotFound {
ldbInsert(batch, repo, node, name, f) if lv := ldbInsert(batch, repo, node, name, f); lv > maxLocalVer {
maxLocalVer = lv
}
ldbUpdateGlobal(snap, batch, repo, node, name, f.Version) ldbUpdateGlobal(snap, batch, repo, node, name, f.Version)
continue continue
} }
@ -239,7 +264,9 @@ func ldbUpdate(db *leveldb.DB, repo, node []byte, fs []protocol.FileInfo) bool {
panic(err) panic(err)
} }
if ef.Version != f.Version { if ef.Version != f.Version {
ldbInsert(batch, repo, node, name, f) if lv := ldbInsert(batch, repo, node, name, f); lv > maxLocalVer {
maxLocalVer = lv
}
ldbUpdateGlobal(snap, batch, repo, node, name, f.Version) ldbUpdateGlobal(snap, batch, repo, node, name, f.Version)
} }
} }
@ -249,16 +276,22 @@ func ldbUpdate(db *leveldb.DB, repo, node []byte, fs []protocol.FileInfo) bool {
panic(err) panic(err)
} }
return true return maxLocalVer
} }
func ldbInsert(batch dbWriter, repo, node, name []byte, file protocol.FileInfo) { func ldbInsert(batch dbWriter, repo, node, name []byte, file protocol.FileInfo) uint64 {
if debug { if debug {
l.Debugf("insert; repo=%q node=%x %v", repo, node, file) l.Debugf("insert; repo=%q node=%x %v", repo, node, file)
} }
if file.LocalVersion == 0 {
file.LocalVersion = clock(0)
}
nk := nodeKey(repo, node, name) nk := nodeKey(repo, node, name)
batch.Put(nk, file.MarshalXDR()) batch.Put(nk, file.MarshalXDR())
return file.LocalVersion
} }
// ldbUpdateGlobal adds this node+version to the version list for the given // ldbUpdateGlobal adds this node+version to the version list for the given

View File

@ -21,18 +21,29 @@ type fileRecord struct {
type bitset uint64 type bitset uint64
type Set struct { type Set struct {
changes map[protocol.NodeID]uint64 localVersion map[protocol.NodeID]uint64
mutex sync.Mutex mutex sync.Mutex
repo string repo string
db *leveldb.DB db *leveldb.DB
} }
func NewSet(repo string, db *leveldb.DB) *Set { func NewSet(repo string, db *leveldb.DB) *Set {
var s = Set{ var s = Set{
changes: make(map[protocol.NodeID]uint64), localVersion: make(map[protocol.NodeID]uint64),
repo: repo, repo: repo,
db: db, db: db,
} }
var lv uint64
ldbWithHave(db, []byte(repo), protocol.LocalNodeID[:], func(f protocol.FileInfo) bool {
if f.LocalVersion > lv {
lv = f.LocalVersion
}
return true
})
s.localVersion[protocol.LocalNodeID] = lv
clock(lv)
return &s return &s
} }
@ -42,8 +53,8 @@ func (s *Set) Replace(node protocol.NodeID, fs []protocol.FileInfo) {
} }
s.mutex.Lock() s.mutex.Lock()
defer s.mutex.Unlock() defer s.mutex.Unlock()
if ldbReplace(s.db, []byte(s.repo), node[:], fs) { if lv := ldbReplace(s.db, []byte(s.repo), node[:], fs); lv > s.localVersion[node] {
s.changes[node]++ s.localVersion[node] = lv
} }
} }
@ -53,8 +64,8 @@ func (s *Set) ReplaceWithDelete(node protocol.NodeID, fs []protocol.FileInfo) {
} }
s.mutex.Lock() s.mutex.Lock()
defer s.mutex.Unlock() defer s.mutex.Unlock()
if ldbReplaceWithDelete(s.db, []byte(s.repo), node[:], fs) { if lv := ldbReplaceWithDelete(s.db, []byte(s.repo), node[:], fs); lv > s.localVersion[node] {
s.changes[node]++ s.localVersion[node] = lv
} }
} }
@ -64,8 +75,8 @@ func (s *Set) Update(node protocol.NodeID, fs []protocol.FileInfo) {
} }
s.mutex.Lock() s.mutex.Lock()
defer s.mutex.Unlock() defer s.mutex.Unlock()
if ldbUpdate(s.db, []byte(s.repo), node[:], fs) { if lv := ldbUpdate(s.db, []byte(s.repo), node[:], fs); lv > s.localVersion[node] {
s.changes[node]++ s.localVersion[node] = lv
} }
} }
@ -102,8 +113,8 @@ func (s *Set) Availability(file string) []protocol.NodeID {
return ldbAvailability(s.db, []byte(s.repo), []byte(file)) return ldbAvailability(s.db, []byte(s.repo), []byte(file))
} }
func (s *Set) Changes(node protocol.NodeID) uint64 { func (s *Set) LocalVersion(node protocol.NodeID) uint64 {
s.mutex.Lock() s.mutex.Lock()
defer s.mutex.Unlock() defer s.mutex.Unlock()
return s.changes[node] return s.localVersion[node]
} }

View File

@ -554,7 +554,7 @@ func TestNeed(t *testing.T) {
} }
} }
func TestChanges(t *testing.T) { func TestLocalVersion(t *testing.T) {
db, err := leveldb.Open(storage.NewMemStorage(), nil) db, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -578,17 +578,17 @@ func TestChanges(t *testing.T) {
} }
m.ReplaceWithDelete(protocol.LocalNodeID, local1) m.ReplaceWithDelete(protocol.LocalNodeID, local1)
c0 := m.Changes(protocol.LocalNodeID) c0 := m.LocalVersion(protocol.LocalNodeID)
m.ReplaceWithDelete(protocol.LocalNodeID, local2) m.ReplaceWithDelete(protocol.LocalNodeID, local2)
c1 := m.Changes(protocol.LocalNodeID) c1 := m.LocalVersion(protocol.LocalNodeID)
if !(c1 > c0) { if !(c1 > c0) {
t.Fatal("Change number should have incremented") t.Fatal("Local version number should have incremented")
} }
m.ReplaceWithDelete(protocol.LocalNodeID, local2) m.ReplaceWithDelete(protocol.LocalNodeID, local2)
c2 := m.Changes(protocol.LocalNodeID) c2 := m.LocalVersion(protocol.LocalNodeID)
if c2 != c1 { if c2 != c1 {
t.Fatal("Change number should be unchanged") t.Fatal("Local version number should be unchanged")
} }
} }

View File

@ -1,8 +1,8 @@
package files package files
import ( import (
"sort"
"github.com/calmh/syncthing/protocol" "github.com/calmh/syncthing/protocol"
"sort"
) )
type SortBy func(p protocol.FileInfo) int type SortBy func(p protocol.FileInfo) int

View File

@ -112,6 +112,9 @@ alterFiles() {
done done
pkill -CONT syncthing pkill -CONT syncthing
echo "Restarting instance 2"
curl -HX-API-Key:abc123 -X POST "http://localhost:8082/rest/restart"
} }
rm -rf h?/*.idx.gz h?/index rm -rf h?/*.idx.gz h?/index

View File

@ -17,8 +17,8 @@ var (
// Generate returns a check digit for the string s, which should be composed // Generate returns a check digit for the string s, which should be composed
// of characters from the Alphabet a. // of characters from the Alphabet a.
func (a Alphabet) Generate(s string) (rune, error) { func (a Alphabet) Generate(s string) (rune, error) {
if err:=a.check();err!=nil{ if err := a.check(); err != nil {
return 0,err return 0, err
} }
factor := 1 factor := 1

View File

@ -5,8 +5,6 @@
package model package model
import ( import (
"compress/gzip"
"crypto/sha1"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -21,7 +19,6 @@ import (
"github.com/calmh/syncthing/events" "github.com/calmh/syncthing/events"
"github.com/calmh/syncthing/files" "github.com/calmh/syncthing/files"
"github.com/calmh/syncthing/lamport" "github.com/calmh/syncthing/lamport"
"github.com/calmh/syncthing/osutil"
"github.com/calmh/syncthing/protocol" "github.com/calmh/syncthing/protocol"
"github.com/calmh/syncthing/scanner" "github.com/calmh/syncthing/scanner"
"github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb"
@ -42,6 +39,9 @@ const (
// transfer to bring the systems into synchronization. // transfer to bring the systems into synchronization.
const zeroEntrySize = 128 const zeroEntrySize = 128
// How many files to send in each Index/IndexUpdate message.
const indexBatchSize = 1000
type Model struct { type Model struct {
indexDir string indexDir string
cfg *config.Configuration cfg *config.Configuration
@ -65,6 +65,9 @@ type Model struct {
nodeVer map[protocol.NodeID]string nodeVer map[protocol.NodeID]string
pmut sync.RWMutex // protects protoConn and rawConn pmut sync.RWMutex // protects protoConn and rawConn
sentLocalVer map[protocol.NodeID]map[string]uint64
slMut sync.Mutex
sup suppressor sup suppressor
addedRepo bool addedRepo bool
@ -95,6 +98,7 @@ func NewModel(indexDir string, cfg *config.Configuration, clientName, clientVers
protoConn: make(map[protocol.NodeID]protocol.Connection), protoConn: make(map[protocol.NodeID]protocol.Connection),
rawConn: make(map[protocol.NodeID]io.Closer), rawConn: make(map[protocol.NodeID]io.Closer),
nodeVer: make(map[protocol.NodeID]string), nodeVer: make(map[protocol.NodeID]string),
sentLocalVer: make(map[protocol.NodeID]map[string]uint64),
sup: suppressor{threshold: int64(cfg.Options.MaxChangeKbps)}, sup: suppressor{threshold: int64(cfg.Options.MaxChangeKbps)},
} }
@ -108,7 +112,6 @@ func NewModel(indexDir string, cfg *config.Configuration, clientName, clientVers
deadlockDetect(&m.rmut, time.Duration(timeout)*time.Second) deadlockDetect(&m.rmut, time.Duration(timeout)*time.Second)
deadlockDetect(&m.smut, time.Duration(timeout)*time.Second) deadlockDetect(&m.smut, time.Duration(timeout)*time.Second)
deadlockDetect(&m.pmut, time.Duration(timeout)*time.Second) deadlockDetect(&m.pmut, time.Duration(timeout)*time.Second)
go m.broadcastIndexLoop()
return m return m
} }
@ -392,13 +395,13 @@ func (m *Model) Close(node protocol.NodeID, err error) {
"error": err.Error(), "error": err.Error(),
}) })
m.pmut.Lock()
m.rmut.RLock() m.rmut.RLock()
for _, repo := range m.nodeRepos[node] { for _, repo := range m.nodeRepos[node] {
m.repoFiles[repo].Replace(node, nil) m.repoFiles[repo].Replace(node, nil)
} }
m.rmut.RUnlock() m.rmut.RUnlock()
m.pmut.Lock()
conn, ok := m.rawConn[node] conn, ok := m.rawConn[node]
if ok { if ok {
conn.Close() conn.Close()
@ -502,6 +505,7 @@ func (m *Model) ConnectedTo(nodeID protocol.NodeID) bool {
// repository changes. // repository changes.
func (m *Model) AddConnection(rawConn io.Closer, protoConn protocol.Connection) { func (m *Model) AddConnection(rawConn io.Closer, protoConn protocol.Connection) {
nodeID := protoConn.ID() nodeID := protoConn.ID()
m.pmut.Lock() m.pmut.Lock()
if _, ok := m.protoConn[nodeID]; ok { if _, ok := m.protoConn[nodeID]; ok {
panic("add existing node") panic("add existing node")
@ -511,48 +515,99 @@ func (m *Model) AddConnection(rawConn io.Closer, protoConn protocol.Connection)
panic("add existing node") panic("add existing node")
} }
m.rawConn[nodeID] = rawConn m.rawConn[nodeID] = rawConn
m.pmut.Unlock()
cm := m.clusterConfig(nodeID) cm := m.clusterConfig(nodeID)
protoConn.ClusterConfig(cm) protoConn.ClusterConfig(cm)
var idxToSend = make(map[string][]protocol.FileInfo)
m.rmut.RLock() m.rmut.RLock()
for _, repo := range m.nodeRepos[nodeID] { for _, repo := range m.nodeRepos[nodeID] {
idxToSend[repo] = m.protocolIndex(repo) fs := m.repoFiles[repo]
go sendIndexes(protoConn, repo, fs)
} }
m.rmut.RUnlock() m.rmut.RUnlock()
m.pmut.Unlock()
go func() {
for repo, idx := range idxToSend {
if debug {
l.Debugf("IDX(out/initial): %s: %q: %d files", nodeID, repo, len(idx))
}
const batchSize = 1000
for i := 0; i < len(idx); i += batchSize {
if len(idx[i:]) < batchSize {
protoConn.Index(repo, idx[i:])
} else {
protoConn.Index(repo, idx[i:i+batchSize])
}
}
}
}()
} }
// protocolIndex returns the current local index in protocol data types. func sendIndexes(conn protocol.Connection, repo string, fs *files.Set) {
func (m *Model) protocolIndex(repo string) []protocol.FileInfo { nodeID := conn.ID()
var fs []protocol.FileInfo name := conn.Name()
m.repoFiles[repo].WithHave(protocol.LocalNodeID, func(f protocol.FileInfo) bool {
fs = append(fs, f)
return true
})
return fs if debug {
l.Debugf("sendIndexes for %s-%s@/%q starting", nodeID, name, repo)
}
initial := true
minLocalVer := uint64(0)
var err error
defer func() {
if debug {
l.Debugf("sendIndexes for %s-%s@/%q exiting: %v", nodeID, name, repo, err)
}
}()
for err == nil {
if !initial && fs.LocalVersion(protocol.LocalNodeID) <= minLocalVer {
time.Sleep(1 * time.Second)
continue
}
batch := make([]protocol.FileInfo, 0, indexBatchSize)
maxLocalVer := uint64(0)
fs.WithHave(protocol.LocalNodeID, func(f protocol.FileInfo) bool {
if f.LocalVersion <= minLocalVer {
return true
}
if f.LocalVersion > maxLocalVer {
maxLocalVer = f.LocalVersion
}
if len(batch) == indexBatchSize {
if initial {
if err = conn.Index(repo, batch); err != nil {
return false
}
if debug {
l.Debugf("sendIndexes for %s-%s/%q: %d files (initial index)", nodeID, name, repo, len(batch))
}
initial = false
} else {
if err = conn.IndexUpdate(repo, batch); err != nil {
return false
}
if debug {
l.Debugf("sendIndexes for %s-%s/%q: %d files (batched update)", nodeID, name, repo, len(batch))
}
}
batch = make([]protocol.FileInfo, 0, indexBatchSize)
}
batch = append(batch, f)
return true
})
if initial {
err = conn.Index(repo, batch)
if debug && err == nil {
l.Debugf("sendIndexes for %s-%s/%q: %d files (small initial index)", nodeID, name, repo, len(batch))
}
initial = false
} else if len(batch) > 0 {
err = conn.IndexUpdate(repo, batch)
if debug && err == nil {
l.Debugf("sendIndexes for %s-%s/%q: %d files (last batch)", nodeID, name, repo, len(batch))
}
}
minLocalVer = maxLocalVer
}
} }
func (m *Model) updateLocal(repo string, f protocol.FileInfo) { func (m *Model) updateLocal(repo string, f protocol.FileInfo) {
f.LocalVersion = 0
m.rmut.RLock() m.rmut.RLock()
m.repoFiles[repo].Update(protocol.LocalNodeID, []protocol.FileInfo{f}) m.repoFiles[repo].Update(protocol.LocalNodeID, []protocol.FileInfo{f})
m.rmut.RUnlock() m.rmut.RUnlock()
@ -575,49 +630,6 @@ func (m *Model) requestGlobal(nodeID protocol.NodeID, repo, name string, offset
return nc.Request(repo, name, offset, size) return nc.Request(repo, name, offset, size)
} }
func (m *Model) broadcastIndexLoop() {
// TODO: Rewrite to send index in segments
var lastChange = map[string]uint64{}
for {
time.Sleep(5 * time.Second)
m.pmut.RLock()
m.rmut.RLock()
var indexWg sync.WaitGroup
for repo, fs := range m.repoFiles {
repo := repo
c := fs.Changes(protocol.LocalNodeID)
if c == lastChange[repo] {
continue
}
lastChange[repo] = c
idx := m.protocolIndex(repo)
for _, nodeID := range m.repoNodes[repo] {
nodeID := nodeID
if conn, ok := m.protoConn[nodeID]; ok {
indexWg.Add(1)
if debug {
l.Debugf("IDX(out/loop): %s: %d files", nodeID, len(idx))
}
go func() {
conn.Index(repo, idx)
indexWg.Done()
}()
}
}
}
m.rmut.RUnlock()
m.pmut.RUnlock()
indexWg.Wait()
}
}
func (m *Model) AddRepo(cfg config.RepositoryConfiguration) { func (m *Model) AddRepo(cfg config.RepositoryConfiguration) {
if m.started { if m.started {
panic("cannot add repo to started model") panic("cannot add repo to started model")
@ -709,88 +721,6 @@ func (m *Model) ScanRepo(repo string) error {
return nil return nil
} }
func (m *Model) LoadIndexes(dir string) {
m.rmut.RLock()
for repo := range m.repoCfgs {
fs := m.loadIndex(repo, dir)
var sfs = make([]protocol.FileInfo, len(fs))
for i := 0; i < len(fs); i++ {
lamport.Default.Tick(fs[i].Version)
fs[i].Flags &= ^uint32(protocol.FlagInvalid) // we might have saved an index with files that were suppressed; the should not be on startup
}
m.repoFiles[repo].Replace(protocol.LocalNodeID, sfs)
}
m.rmut.RUnlock()
}
func (m *Model) saveIndex(repo string, dir string, fs []protocol.FileInfo) error {
id := fmt.Sprintf("%x", sha1.Sum([]byte(m.repoCfgs[repo].Directory)))
name := id + ".idx.gz"
name = filepath.Join(dir, name)
tmp := fmt.Sprintf("%s.tmp.%d", name, time.Now().UnixNano())
idxf, err := os.OpenFile(tmp, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer os.Remove(tmp)
gzw := gzip.NewWriter(idxf)
n, err := protocol.IndexMessage{
Repository: repo,
Files: fs,
}.EncodeXDR(gzw)
if err != nil {
gzw.Close()
idxf.Close()
return err
}
err = gzw.Close()
if err != nil {
return err
}
err = idxf.Close()
if err != nil {
return err
}
if debug {
l.Debugln("wrote index,", n, "bytes uncompressed")
}
return osutil.Rename(tmp, name)
}
func (m *Model) loadIndex(repo string, dir string) []protocol.FileInfo {
id := fmt.Sprintf("%x", sha1.Sum([]byte(m.repoCfgs[repo].Directory)))
name := id + ".idx.gz"
name = filepath.Join(dir, name)
idxf, err := os.Open(name)
if err != nil {
return nil
}
defer idxf.Close()
gzr, err := gzip.NewReader(idxf)
if err != nil {
return nil
}
defer gzr.Close()
var im protocol.IndexMessage
err = im.DecodeXDR(gzr)
if err != nil || im.Repository != repo {
return nil
}
return im.Files
}
// clusterConfig returns a ClusterConfigMessage that is correct for the given peer node // clusterConfig returns a ClusterConfigMessage that is correct for the given peer node
func (m *Model) clusterConfig(node protocol.NodeID) protocol.ClusterConfigMessage { func (m *Model) clusterConfig(node protocol.NodeID) protocol.ClusterConfigMessage {
cm := protocol.ClusterConfigMessage{ cm := protocol.ClusterConfigMessage{
@ -868,12 +798,12 @@ func (m *Model) Override(repo string) {
// Version returns the change version for the given repository. This is // Version returns the change version for the given repository. This is
// guaranteed to increment if the contents of the local or global repository // guaranteed to increment if the contents of the local or global repository
// has changed. // has changed.
func (m *Model) Version(repo string) uint64 { func (m *Model) LocalVersion(repo string) uint64 {
var ver uint64 var ver uint64
m.rmut.Lock() m.rmut.Lock()
for _, n := range m.repoNodes[repo] { for _, n := range m.repoNodes[repo] {
ver += m.repoFiles[repo].Changes(n) ver += m.repoFiles[repo].LocalVersion(n)
} }
m.rmut.Unlock() m.rmut.Unlock()

View File

@ -193,7 +193,7 @@ func (p *puller) run() {
default: default:
} }
if v := p.model.Version(p.repoCfg.ID); v > prevVer { if v := p.model.LocalVersion(p.repoCfg.ID); v > prevVer {
// Queue more blocks to fetch, if any // Queue more blocks to fetch, if any
p.queueNeededBlocks() p.queueNeededBlocks()
prevVer = v prevVer = v
@ -335,15 +335,21 @@ func (p *puller) handleRequestResult(res requestResult) {
return return
} }
_, of.err = of.file.WriteAt(res.data, res.offset) if res.err != nil {
of.err = res.err
if debug {
l.Debugf("pull: not writing %q / %q offset %d: %v", p.repoCfg.ID, f.Name, res.offset, res.err)
}
} else {
_, of.err = of.file.WriteAt(res.data, res.offset)
if debug {
l.Debugf("pull: wrote %q / %q offset %d len %d outstanding %d done %v", p.repoCfg.ID, f.Name, res.offset, len(res.data), of.outstanding, of.done)
}
}
of.outstanding-- of.outstanding--
p.openFiles[f.Name] = of p.openFiles[f.Name] = of
if debug {
l.Debugf("pull: wrote %q / %q offset %d outstanding %d done %v", p.repoCfg.ID, f.Name, res.offset, of.outstanding, of.done)
}
if of.done && of.outstanding == 0 { if of.done && of.outstanding == 0 {
p.closeFile(f) p.closeFile(f)
} }
@ -526,7 +532,7 @@ func (p *puller) handleRequestBlock(b bqBlock) bool {
} }
node := p.oustandingPerNode.leastBusyNode(of.availability) node := p.oustandingPerNode.leastBusyNode(of.availability)
if len(node) == 0 { if node == (protocol.NodeID{}) {
of.err = errNoNode of.err = errNoNode
if of.file != nil { if of.file != nil {
of.file.Close() of.file.Close()
@ -662,7 +668,7 @@ func (p *puller) closeFile(f protocol.FileInfo) {
for i := range hb { for i := range hb {
if bytes.Compare(hb[i].Hash, f.Blocks[i].Hash) != 0 { if bytes.Compare(hb[i].Hash, f.Blocks[i].Hash) != 0 {
l.Debugf("pull: %q / %q: block %d hash mismatch", p.repoCfg.ID, f.Name, i) l.Debugf("pull: %q / %q: block %d hash mismatch\n\thave: %x\n\twant: %x", p.repoCfg.ID, f.Name, i, hb[i].Hash, f.Blocks[i].Hash)
return return
} }
} }

View File

@ -182,7 +182,7 @@ Cluster Config messages MUST NOT be sent after the initial exchange.
| Flags | | Flags |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | | |
+ Max Version (64 bits) + + Max Local Version (64 bits) +
| | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
@ -255,13 +255,13 @@ The Node Flags field contains the following single bit flags:
Exactly one of the T, R or S bits MUST be set. Exactly one of the T, R or S bits MUST be set.
The Node Max Version field contains the highest file version number of The Node Max Local Version field contains the highest local file version
the files already known to be in the index sent by this node. If nothing number of the files already known to be in the index sent by this node.
is known about the index of a given node, this field MUST be set to If nothing is known about the index of a given node, this field MUST be
zero. When receiving a Cluster Config message with a non-zero Max set to zero. When receiving a Cluster Config message with a non-zero Max
Version for the local node ID, a node MAY elect to send an Index Update Version for the local node ID, a node MAY elect to send an Index Update
message containing only files with higher version numbers in place of message containing only files with higher local version numbers in place
the initial Index message. of the initial Index message.
The Options field contain option values to be used in an implementation The Options field contain option values to be used in an implementation
specific manner. The options list is conceptually a map of Key => Value specific manner. The options list is conceptually a map of Key => Value
@ -292,7 +292,7 @@ peers acting in a specific manner as a result of sent options.
struct Node { struct Node {
string ID<>; string ID<>;
unsigned int Flags; unsigned int Flags;
unsigned hyper MaxVersion; unsigned hyper MaxLocalVersion;
} }
struct Option { struct Option {
@ -359,6 +359,10 @@ Index message MUST be sent. There is no response to the Index message.
+ Version (64 bits) + + Version (64 bits) +
| | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Local Version (64 bits) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of Blocks | | Number of Blocks |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ / / /
@ -400,6 +404,10 @@ detected and received change. The combination of Repository, Name and
Version uniquely identifies the contents of a file at a given point in Version uniquely identifies the contents of a file at a given point in
time. time.
The Local Version field is the value of a node local monotonic clock at
the time of last local database update to a file. The clock ticks on
every local database update.
The Flags field is made up of the following single bit flags: The Flags field is made up of the following single bit flags:
0 1 2 3 0 1 2 3
@ -458,6 +466,7 @@ block which may represent a smaller amount of data.
unsigned int Flags; unsigned int Flags;
hyper Modified; hyper Modified;
unsigned hyper Version; unsigned hyper Version;
unsigned hyper LocalVer;
BlockInfo Blocks<>; BlockInfo Blocks<>;
} }

View File

@ -12,11 +12,12 @@ type IndexMessage struct {
} }
type FileInfo struct { type FileInfo struct {
Name string // max:1024 Name string // max:1024
Flags uint32 Flags uint32
Modified int64 Modified int64
Version uint64 Version uint64
Blocks []BlockInfo // max:1000000 LocalVersion uint64
Blocks []BlockInfo // max:1000000
} }
func (f FileInfo) String() string { func (f FileInfo) String() string {
@ -69,9 +70,9 @@ type Repository struct {
} }
type Node struct { type Node struct {
ID []byte // max:32 ID []byte // max:32
Flags uint32 Flags uint32
MaxVersion uint64 MaxLocalVersion uint64
} }
type Option struct { type Option struct {

View File

@ -121,6 +121,10 @@ FileInfo Structure:
+ Version (64 bits) + + Version (64 bits) +
| | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Local Version (64 bits) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of Blocks | | Number of Blocks |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ / / /
@ -134,6 +138,7 @@ struct FileInfo {
unsigned int Flags; unsigned int Flags;
hyper Modified; hyper Modified;
unsigned hyper Version; unsigned hyper Version;
unsigned hyper LocalVersion;
BlockInfo Blocks<1000000>; BlockInfo Blocks<1000000>;
} }
@ -163,6 +168,7 @@ func (o FileInfo) encodeXDR(xw *xdr.Writer) (int, error) {
xw.WriteUint32(o.Flags) xw.WriteUint32(o.Flags)
xw.WriteUint64(uint64(o.Modified)) xw.WriteUint64(uint64(o.Modified))
xw.WriteUint64(o.Version) xw.WriteUint64(o.Version)
xw.WriteUint64(o.LocalVersion)
if len(o.Blocks) > 1000000 { if len(o.Blocks) > 1000000 {
return xw.Tot(), xdr.ErrElementSizeExceeded return xw.Tot(), xdr.ErrElementSizeExceeded
} }
@ -189,6 +195,7 @@ func (o *FileInfo) decodeXDR(xr *xdr.Reader) error {
o.Flags = xr.ReadUint32() o.Flags = xr.ReadUint32()
o.Modified = int64(xr.ReadUint64()) o.Modified = int64(xr.ReadUint64())
o.Version = xr.ReadUint64() o.Version = xr.ReadUint64()
o.LocalVersion = xr.ReadUint64()
_BlocksSize := int(xr.ReadUint32()) _BlocksSize := int(xr.ReadUint32())
if _BlocksSize > 1000000 { if _BlocksSize > 1000000 {
return xdr.ErrElementSizeExceeded return xdr.ErrElementSizeExceeded
@ -567,7 +574,7 @@ Node Structure:
| Flags | | Flags |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | | |
+ Max Version (64 bits) + + Max Local Version (64 bits) +
| | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
@ -575,7 +582,7 @@ Node Structure:
struct Node { struct Node {
opaque ID<32>; opaque ID<32>;
unsigned int Flags; unsigned int Flags;
unsigned hyper MaxVersion; unsigned hyper MaxLocalVersion;
} }
*/ */
@ -602,7 +609,7 @@ func (o Node) encodeXDR(xw *xdr.Writer) (int, error) {
} }
xw.WriteBytes(o.ID) xw.WriteBytes(o.ID)
xw.WriteUint32(o.Flags) xw.WriteUint32(o.Flags)
xw.WriteUint64(o.MaxVersion) xw.WriteUint64(o.MaxLocalVersion)
return xw.Tot(), xw.Error() return xw.Tot(), xw.Error()
} }
@ -620,7 +627,7 @@ func (o *Node) UnmarshalXDR(bs []byte) error {
func (o *Node) decodeXDR(xr *xdr.Reader) error { func (o *Node) decodeXDR(xr *xdr.Reader) error {
o.ID = xr.ReadBytesMax(32) o.ID = xr.ReadBytesMax(32)
o.Flags = xr.ReadUint32() o.Flags = xr.ReadUint32()
o.MaxVersion = xr.ReadUint64() o.MaxLocalVersion = xr.ReadUint64()
return xr.Error() return xr.Error()
} }

View File

@ -66,7 +66,9 @@ type Model interface {
type Connection interface { type Connection interface {
ID() NodeID ID() NodeID
Index(repo string, files []FileInfo) Name() string
Index(repo string, files []FileInfo) error
IndexUpdate(repo string, files []FileInfo) error
Request(repo string, name string, offset int64, size int) ([]byte, error) Request(repo string, name string, offset int64, size int) ([]byte, error)
ClusterConfig(config ClusterConfigMessage) ClusterConfig(config ClusterConfigMessage)
Statistics() Statistics Statistics() Statistics
@ -74,6 +76,7 @@ type Connection interface {
type rawConnection struct { type rawConnection struct {
id NodeID id NodeID
name string
receiver Model receiver Model
state int state int
@ -87,8 +90,7 @@ type rawConnection struct {
awaiting []chan asyncResult awaiting []chan asyncResult
awaitingMut sync.Mutex awaitingMut sync.Mutex
idxSent map[string]map[string]uint64 idxMut sync.Mutex // ensures serialization of Index calls
idxMut sync.Mutex // ensures serialization of Index calls
nextID chan int nextID chan int
outbox chan []encodable outbox chan []encodable
@ -106,15 +108,16 @@ const (
pingIdleTime = 60 * time.Second pingIdleTime = 60 * time.Second
) )
func NewConnection(nodeID NodeID, reader io.Reader, writer io.Writer, receiver Model) Connection { func NewConnection(nodeID NodeID, reader io.Reader, writer io.Writer, receiver Model, name string) Connection {
cr := &countingReader{Reader: reader} cr := &countingReader{Reader: reader}
cw := &countingWriter{Writer: writer} cw := &countingWriter{Writer: writer}
rb := bufio.NewReader(cr) rb := bufio.NewReader(cr)
wb := bufio.NewWriter(cw) wb := bufio.NewWriterSize(cw, 65536)
c := rawConnection{ c := rawConnection{
id: nodeID, id: nodeID,
name: name,
receiver: nativeModel{receiver}, receiver: nativeModel{receiver},
state: stateInitial, state: stateInitial,
cr: cr, cr: cr,
@ -123,7 +126,6 @@ func NewConnection(nodeID NodeID, reader io.Reader, writer io.Writer, receiver M
wb: wb, wb: wb,
xw: xdr.NewWriter(wb), xw: xdr.NewWriter(wb),
awaiting: make([]chan asyncResult, 0x1000), awaiting: make([]chan asyncResult, 0x1000),
idxSent: make(map[string]map[string]uint64),
outbox: make(chan []encodable), outbox: make(chan []encodable),
nextID: make(chan int), nextID: make(chan int),
closed: make(chan struct{}), closed: make(chan struct{}),
@ -142,36 +144,34 @@ func (c *rawConnection) ID() NodeID {
return c.id return c.id
} }
func (c *rawConnection) Name() string {
return c.name
}
// Index writes the list of file information to the connected peer node // Index writes the list of file information to the connected peer node
func (c *rawConnection) Index(repo string, idx []FileInfo) { func (c *rawConnection) Index(repo string, idx []FileInfo) error {
select {
case <-c.closed:
return ErrClosed
default:
}
c.idxMut.Lock() c.idxMut.Lock()
defer c.idxMut.Unlock() c.send(header{0, -1, messageTypeIndex}, IndexMessage{repo, idx})
c.idxMut.Unlock()
return nil
}
var msgType int // IndexUpdate writes the list of file information to the connected peer node as an update
if c.idxSent[repo] == nil { func (c *rawConnection) IndexUpdate(repo string, idx []FileInfo) error {
// This is the first time we send an index. select {
msgType = messageTypeIndex case <-c.closed:
return ErrClosed
c.idxSent[repo] = make(map[string]uint64) default:
for _, f := range idx {
c.idxSent[repo][f.Name] = f.Version
}
} else {
// We have sent one full index. Only send updates now.
msgType = messageTypeIndexUpdate
var diff []FileInfo
for _, f := range idx {
if vs, ok := c.idxSent[repo][f.Name]; !ok || f.Version != vs {
diff = append(diff, f)
c.idxSent[repo][f.Name] = f.Version
}
}
idx = diff
}
if msgType == messageTypeIndex || len(idx) > 0 {
c.send(header{0, -1, msgType}, IndexMessage{repo, idx})
} }
c.idxMut.Lock()
c.send(header{0, -1, messageTypeIndexUpdate}, IndexMessage{repo, idx})
c.idxMut.Unlock()
return nil
} }
// Request returns the bytes for the specified block after fetching them from the connected peer. // Request returns the bytes for the specified block after fetching them from the connected peer.
@ -445,15 +445,13 @@ func (c *rawConnection) send(h header, es ...encodable) bool {
} }
func (c *rawConnection) writerLoop() { func (c *rawConnection) writerLoop() {
var err error
for { for {
select { select {
case es := <-c.outbox: case es := <-c.outbox:
for _, e := range es { for _, e := range es {
e.encodeXDR(c.xw) e.encodeXDR(c.xw)
} }
if err := c.flush(); err != nil {
if err = c.flush(); err != nil {
c.close(err) c.close(err)
return return
} }
@ -471,11 +469,9 @@ func (c *rawConnection) flush() error {
if err := c.xw.Error(); err != nil { if err := c.xw.Error(); err != nil {
return err return err
} }
if err := c.wb.Flush(); err != nil { if err := c.wb.Flush(); err != nil {
return err return err
} }
return nil return nil
} }

View File

@ -18,7 +18,11 @@ func (c wireFormatConnection) ID() NodeID {
return c.next.ID() return c.next.ID()
} }
func (c wireFormatConnection) Index(repo string, fs []FileInfo) { func (c wireFormatConnection) Name() string {
return c.next.Name()
}
func (c wireFormatConnection) Index(repo string, fs []FileInfo) error {
var myFs = make([]FileInfo, len(fs)) var myFs = make([]FileInfo, len(fs))
copy(myFs, fs) copy(myFs, fs)
@ -26,7 +30,18 @@ func (c wireFormatConnection) Index(repo string, fs []FileInfo) {
myFs[i].Name = norm.NFC.String(filepath.ToSlash(myFs[i].Name)) myFs[i].Name = norm.NFC.String(filepath.ToSlash(myFs[i].Name))
} }
c.next.Index(repo, myFs) return c.next.Index(repo, myFs)
}
func (c wireFormatConnection) IndexUpdate(repo string, fs []FileInfo) error {
var myFs = make([]FileInfo, len(fs))
copy(myFs, fs)
for i := range fs {
myFs[i].Name = norm.NFC.String(filepath.ToSlash(myFs[i].Name))
}
return c.next.IndexUpdate(repo, myFs)
} }
func (c wireFormatConnection) Request(repo, name string, offset int64, size int) ([]byte, error) { func (c wireFormatConnection) Request(repo, name string, offset int64, size int) ([]byte, error) {

View File

@ -6,6 +6,7 @@ package scanner
import ( import (
"bytes" "bytes"
"code.google.com/p/go.text/unicode/norm"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -14,7 +15,6 @@ import (
"runtime" "runtime"
"strings" "strings"
"time" "time"
"code.google.com/p/go.text/unicode/norm"
"github.com/calmh/syncthing/lamport" "github.com/calmh/syncthing/lamport"
"github.com/calmh/syncthing/protocol" "github.com/calmh/syncthing/protocol"
@ -216,6 +216,7 @@ func (w *Walker) walkAndHashFiles(res *[]protocol.FileInfo, ign map[string][]str
l.Infof("Changes to %q are being temporarily suppressed because it changes too frequently.", p) l.Infof("Changes to %q are being temporarily suppressed because it changes too frequently.", p)
cf.Flags |= protocol.FlagInvalid cf.Flags |= protocol.FlagInvalid
cf.Version = lamport.Default.Tick(cf.Version) cf.Version = lamport.Default.Tick(cf.Version)
cf.LocalVersion = 0
if debug { if debug {
l.Debugln("suppressed:", cf) l.Debugln("suppressed:", cf)
} }