2014-07-12 15:45:33 -07:00
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
// All rights reserved. Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
2014-06-01 13:50:14 -07:00
2014-05-14 20:26:55 -07:00
package model
2013-12-15 03:43:31 -07:00
import (
2014-09-14 15:03:53 -07:00
"bufio"
2014-09-09 23:48:15 -07:00
"crypto/tls"
2014-01-06 13:31:36 -07:00
"errors"
2013-12-23 10:12:44 -07:00
"fmt"
2013-12-31 19:22:49 -07:00
"io"
2014-09-22 06:54:36 -07:00
"io/ioutil"
2014-01-05 15:54:57 -07:00
"net"
2013-12-15 03:43:31 -07:00
"os"
2014-03-28 06:36:57 -07:00
"path/filepath"
2014-06-26 02:24:38 -07:00
"strconv"
2014-08-11 11:20:01 -07:00
"strings"
2013-12-15 03:43:31 -07:00
"sync"
"time"
2014-06-21 00:43:12 -07:00
2014-08-01 07:35:37 -07:00
"github.com/syncthing/syncthing/config"
"github.com/syncthing/syncthing/events"
"github.com/syncthing/syncthing/files"
2014-09-04 13:29:53 -07:00
"github.com/syncthing/syncthing/ignore"
2014-08-01 07:35:37 -07:00
"github.com/syncthing/syncthing/lamport"
2014-09-14 15:03:53 -07:00
"github.com/syncthing/syncthing/osutil"
2014-08-01 07:35:37 -07:00
"github.com/syncthing/syncthing/protocol"
"github.com/syncthing/syncthing/scanner"
2014-08-21 15:45:40 -07:00
"github.com/syncthing/syncthing/stats"
2014-07-06 05:46:48 -07:00
"github.com/syndtr/goleveldb/leveldb"
2013-12-15 03:43:31 -07:00
)
2014-04-14 00:58:17 -07:00
type repoState int
const (
RepoIdle repoState = iota
RepoScanning
RepoSyncing
RepoCleaning
)
2014-07-17 04:38:36 -07:00
func ( s repoState ) String ( ) string {
switch s {
case RepoIdle :
return "idle"
case RepoScanning :
return "scanning"
case RepoCleaning :
return "cleaning"
case RepoSyncing :
return "syncing"
default :
return "unknown"
}
}
2014-07-15 04:04:37 -07:00
// How many files to send in each Index/IndexUpdate message.
2014-08-11 11:54:59 -07:00
const (
indexTargetSize = 250 * 1024 // Aim for making index messages no larger than 250 KiB (uncompressed)
indexPerFileSize = 250 // Each FileInfo is approximately this big, in bytes, excluding BlockInfos
IndexPerBlockSize = 40 // Each BlockInfo is approximately this big
indexBatchSize = 1000 // Either way, don't include more files than this
)
2014-07-15 04:04:37 -07:00
2013-12-15 03:43:31 -07:00
type Model struct {
2014-05-14 20:26:55 -07:00
indexDir string
cfg * config . Configuration
2014-07-06 05:46:48 -07:00
db * leveldb . DB
2014-05-14 20:26:55 -07:00
2014-08-14 15:15:26 -07:00
nodeName string
2014-05-14 20:26:55 -07:00
clientName string
clientVersion string
2014-08-21 15:45:40 -07:00
repoCfgs map [ string ] config . RepositoryConfiguration // repo -> cfg
repoFiles map [ string ] * files . Set // repo -> files
repoNodes map [ string ] [ ] protocol . NodeID // repo -> nodeIDs
nodeRepos map [ protocol . NodeID ] [ ] string // nodeID -> repos
nodeStatRefs map [ protocol . NodeID ] * stats . NodeStatisticsReference // nodeID -> statsRef
2014-09-04 13:29:53 -07:00
repoIgnores map [ string ] ignore . Patterns // repo -> list of ignore patterns
2014-08-21 15:45:40 -07:00
rmut sync . RWMutex // protects the above
2014-03-29 10:53:48 -07:00
2014-07-17 04:38:36 -07:00
repoState map [ string ] repoState // repo -> state
repoStateChanged map [ string ] time . Time // repo -> time when state changed
smut sync . RWMutex
2014-05-20 09:41:01 -07:00
2014-06-29 16:42:03 -07:00
protoConn map [ protocol . NodeID ] protocol . Connection
rawConn map [ protocol . NodeID ] io . Closer
nodeVer map [ protocol . NodeID ] string
2014-01-17 20:06:44 -07:00
pmut sync . RWMutex // protects protoConn and rawConn
2013-12-30 07:30:29 -07:00
2014-03-29 10:53:48 -07:00
addedRepo bool
started bool
2013-12-15 03:43:31 -07:00
}
2014-01-07 14:44:21 -07:00
var (
ErrNoSuchFile = errors . New ( "no such file" )
ErrInvalid = errors . New ( "file is invalid" )
)
2014-01-06 13:31:36 -07:00
2014-01-06 03:11:18 -07:00
// NewModel creates and starts a new model. The model starts in read-only mode,
// where it sends index information to connected peers and responds to requests
// for file data without altering the local repository in any way.
2014-08-14 15:15:26 -07:00
func NewModel ( indexDir string , cfg * config . Configuration , nodeName , clientName , clientVersion string , db * leveldb . DB ) * Model {
2013-12-15 03:43:31 -07:00
m := & Model {
2014-07-17 04:38:36 -07:00
indexDir : indexDir ,
cfg : cfg ,
db : db ,
2014-08-14 15:15:26 -07:00
nodeName : nodeName ,
2014-07-17 04:38:36 -07:00
clientName : clientName ,
clientVersion : clientVersion ,
repoCfgs : make ( map [ string ] config . RepositoryConfiguration ) ,
repoFiles : make ( map [ string ] * files . Set ) ,
repoNodes : make ( map [ string ] [ ] protocol . NodeID ) ,
nodeRepos : make ( map [ protocol . NodeID ] [ ] string ) ,
2014-08-21 15:45:40 -07:00
nodeStatRefs : make ( map [ protocol . NodeID ] * stats . NodeStatisticsReference ) ,
2014-09-04 13:29:53 -07:00
repoIgnores : make ( map [ string ] ignore . Patterns ) ,
2014-07-17 04:38:36 -07:00
repoState : make ( map [ string ] repoState ) ,
repoStateChanged : make ( map [ string ] time . Time ) ,
protoConn : make ( map [ protocol . NodeID ] protocol . Connection ) ,
rawConn : make ( map [ protocol . NodeID ] io . Closer ) ,
nodeVer : make ( map [ protocol . NodeID ] string ) ,
2013-12-15 03:43:31 -07:00
}
2014-06-26 02:24:38 -07:00
var timeout = 20 * 60 // seconds
if t := os . Getenv ( "STDEADLOCKTIMEOUT" ) ; len ( t ) > 0 {
it , err := strconv . Atoi ( t )
if err == nil {
timeout = it
}
}
deadlockDetect ( & m . rmut , time . Duration ( timeout ) * time . Second )
deadlockDetect ( & m . smut , time . Duration ( timeout ) * time . Second )
deadlockDetect ( & m . pmut , time . Duration ( timeout ) * time . Second )
2013-12-15 03:43:31 -07:00
return m
}
2014-01-06 03:11:18 -07:00
// StartRW starts read/write processing on the current model. When in
// read/write mode the model will attempt to keep in sync with the cluster by
// pulling needed files from peer nodes.
2014-04-08 04:45:18 -07:00
func ( m * Model ) StartRepoRW ( repo string , threads int ) {
2014-05-02 08:14:53 -07:00
m . rmut . RLock ( )
defer m . rmut . RUnlock ( )
2014-03-29 10:53:48 -07:00
2014-05-23 05:31:16 -07:00
if cfg , ok := m . repoCfgs [ repo ] ; ! ok {
2014-03-29 10:53:48 -07:00
panic ( "cannot start without repo" )
2014-04-08 04:45:18 -07:00
} else {
2014-05-23 05:31:16 -07:00
newPuller ( cfg , m , threads , m . cfg )
2014-03-29 10:53:48 -07:00
}
2014-03-28 06:36:57 -07:00
}
2014-01-06 03:11:18 -07:00
2014-03-28 06:36:57 -07:00
// StartRO starts read only processing on the current model. When in
// read only mode the model will announce files to the cluster but not
// pull in any external changes.
2014-04-08 04:45:18 -07:00
func ( m * Model ) StartRepoRO ( repo string ) {
m . StartRepoRW ( repo , 0 ) // zero threads => read only
2014-01-20 14:22:27 -07:00
}
2014-01-05 15:54:57 -07:00
type ConnectionInfo struct {
protocol . Statistics
2014-01-23 05:12:45 -07:00
Address string
ClientVersion string
2014-01-05 15:54:57 -07:00
}
2014-01-06 03:11:18 -07:00
// ConnectionStats returns a map with connection statistics for each connected node.
2014-01-05 15:54:57 -07:00
func ( m * Model ) ConnectionStats ( ) map [ string ] ConnectionInfo {
type remoteAddrer interface {
RemoteAddr ( ) net . Addr
}
2014-01-17 20:06:44 -07:00
m . pmut . RLock ( )
2014-03-29 10:53:48 -07:00
m . rmut . RLock ( )
2014-01-05 08:16:37 -07:00
2014-01-05 15:54:57 -07:00
var res = make ( map [ string ] ConnectionInfo )
2014-01-09 05:58:35 -07:00
for node , conn := range m . protoConn {
2014-01-05 15:54:57 -07:00
ci := ConnectionInfo {
2014-01-23 05:12:45 -07:00
Statistics : conn . Statistics ( ) ,
2014-04-13 06:28:26 -07:00
ClientVersion : m . nodeVer [ node ] ,
2014-01-05 15:54:57 -07:00
}
if nc , ok := m . rawConn [ node ] . ( remoteAddrer ) ; ok {
ci . Address = nc . RemoteAddr ( ) . String ( )
}
2014-02-13 04:41:37 -07:00
2014-06-29 16:42:03 -07:00
res [ node . String ( ) ] = ci
2013-12-30 07:30:29 -07:00
}
2014-01-17 20:06:44 -07:00
2014-03-29 10:53:48 -07:00
m . rmut . RUnlock ( )
2014-01-17 20:06:44 -07:00
m . pmut . RUnlock ( )
2014-03-28 06:36:57 -07:00
2014-05-24 12:34:11 -07:00
in , out := protocol . TotalInOut ( )
res [ "total" ] = ConnectionInfo {
Statistics : protocol . Statistics {
At : time . Now ( ) ,
2014-06-01 12:56:05 -07:00
InBytesTotal : in ,
OutBytesTotal : out ,
2014-05-24 12:34:11 -07:00
} ,
}
2014-01-05 08:16:37 -07:00
return res
2013-12-30 07:30:29 -07:00
}
2014-08-21 15:45:40 -07:00
// Returns statistics about each node
func ( m * Model ) NodeStatistics ( ) map [ string ] stats . NodeStatistics {
var res = make ( map [ string ] stats . NodeStatistics )
for _ , node := range m . cfg . Nodes {
2014-09-20 10:14:45 -07:00
res [ node . NodeID . String ( ) ] = m . nodeStatRef ( node . NodeID ) . GetStatistics ( )
2014-08-21 15:45:40 -07:00
}
return res
}
2014-07-29 02:06:52 -07:00
// Returns the completion status, in percent, for the given node and repo.
func ( m * Model ) Completion ( node protocol . NodeID , repo string ) float64 {
var tot int64
2014-08-05 11:16:25 -07:00
m . rmut . RLock ( )
rf , ok := m . repoFiles [ repo ]
m . rmut . RUnlock ( )
if ! ok {
return 0 // Repo doesn't exist, so we hardly have any of it
}
2014-08-12 04:53:31 -07:00
rf . WithGlobalTruncated ( func ( f protocol . FileIntf ) bool {
if ! f . IsDeleted ( ) {
tot += f . Size ( )
2014-07-29 02:06:52 -07:00
}
return true
} )
2014-08-05 11:16:25 -07:00
if tot == 0 {
return 100 // Repo is empty, so we have all of it
}
2014-07-29 02:06:52 -07:00
var need int64
2014-08-12 04:53:31 -07:00
rf . WithNeedTruncated ( node , func ( f protocol . FileIntf ) bool {
if ! f . IsDeleted ( ) {
need += f . Size ( )
2014-07-29 02:06:52 -07:00
}
return true
} )
2014-08-12 04:53:31 -07:00
res := 100 * ( 1 - float64 ( need ) / float64 ( tot ) )
if debug {
l . Debugf ( "Completion(%s, %q): %f (%d / %d)" , node , repo , res , need , tot )
}
return res
2014-07-29 02:06:52 -07:00
}
2014-07-12 14:06:48 -07:00
func sizeOf ( fs [ ] protocol . FileInfo ) ( files , deleted int , bytes int64 ) {
2014-03-28 06:36:57 -07:00
for _ , f := range fs {
2014-07-06 05:46:48 -07:00
fs , de , by := sizeOfFile ( f )
files += fs
deleted += de
bytes += by
}
return
}
2014-08-12 04:53:31 -07:00
func sizeOfFile ( f protocol . FileIntf ) ( files , deleted int , bytes int64 ) {
if ! f . IsDeleted ( ) {
2014-07-06 05:46:48 -07:00
files ++
} else {
deleted ++
2013-12-30 07:30:29 -07:00
}
2014-08-12 04:53:31 -07:00
bytes += f . Size ( )
2014-01-05 08:16:37 -07:00
return
}
2013-12-30 07:30:29 -07:00
2014-03-28 06:36:57 -07:00
// GlobalSize returns the number of files, deleted files and total bytes for all
// files in the global model.
2014-04-09 13:03:30 -07:00
func ( m * Model ) GlobalSize ( repo string ) ( files , deleted int , bytes int64 ) {
2014-03-29 10:53:48 -07:00
m . rmut . RLock ( )
2014-04-09 13:03:30 -07:00
defer m . rmut . RUnlock ( )
if rf , ok := m . repoFiles [ repo ] ; ok {
2014-08-12 04:53:31 -07:00
rf . WithGlobalTruncated ( func ( f protocol . FileIntf ) bool {
2014-07-06 05:46:48 -07:00
fs , de , by := sizeOfFile ( f )
files += fs
deleted += de
bytes += by
return true
} )
2014-03-29 10:53:48 -07:00
}
2014-07-06 05:46:48 -07:00
return
2014-03-28 06:36:57 -07:00
}
2014-01-06 03:11:18 -07:00
// LocalSize returns the number of files, deleted files and total bytes for all
// files in the local repository.
2014-04-09 13:03:30 -07:00
func ( m * Model ) LocalSize ( repo string ) ( files , deleted int , bytes int64 ) {
2014-03-29 10:53:48 -07:00
m . rmut . RLock ( )
2014-04-09 13:03:30 -07:00
defer m . rmut . RUnlock ( )
if rf , ok := m . repoFiles [ repo ] ; ok {
2014-08-12 04:53:31 -07:00
rf . WithHaveTruncated ( protocol . LocalNodeID , func ( f protocol . FileIntf ) bool {
2014-09-04 13:29:53 -07:00
if f . IsInvalid ( ) {
return true
}
2014-07-06 05:46:48 -07:00
fs , de , by := sizeOfFile ( f )
files += fs
deleted += de
bytes += by
return true
} )
2014-03-29 10:53:48 -07:00
}
2014-07-06 14:15:28 -07:00
return
2014-01-05 22:38:01 -07:00
}
2014-05-19 13:31:28 -07:00
// NeedSize returns the number and total size of currently needed files.
2014-04-09 13:03:30 -07:00
func ( m * Model ) NeedSize ( repo string ) ( files int , bytes int64 ) {
2014-07-15 08:54:00 -07:00
m . rmut . RLock ( )
defer m . rmut . RUnlock ( )
if rf , ok := m . repoFiles [ repo ] ; ok {
2014-08-12 04:53:31 -07:00
rf . WithNeedTruncated ( protocol . LocalNodeID , func ( f protocol . FileIntf ) bool {
2014-07-15 08:54:00 -07:00
fs , de , by := sizeOfFile ( f )
files += fs + de
bytes += by
return true
} )
}
2014-08-12 04:53:31 -07:00
if debug {
l . Debugf ( "NeedSize(%q): %d %d" , repo , files , bytes )
}
2014-07-15 08:54:00 -07:00
return
2013-12-23 10:12:44 -07:00
}
2014-09-13 01:57:36 -07:00
// NeedFiles returns the list of currently needed files, stopping at maxFiles
// files or maxBlocks blocks. Limits <= 0 are ignored.
func ( m * Model ) NeedFilesRepoLimited ( repo string , maxFiles , maxBlocks int ) [ ] protocol . FileInfo {
2014-04-01 14:18:32 -07:00
m . rmut . RLock ( )
2014-04-09 13:03:30 -07:00
defer m . rmut . RUnlock ( )
2014-09-13 01:57:36 -07:00
nblocks := 0
2014-04-09 13:03:30 -07:00
if rf , ok := m . repoFiles [ repo ] ; ok {
2014-09-13 01:57:36 -07:00
fs := make ( [ ] protocol . FileInfo , 0 , maxFiles )
2014-08-12 04:53:31 -07:00
rf . WithNeed ( protocol . LocalNodeID , func ( f protocol . FileIntf ) bool {
2014-09-13 01:57:36 -07:00
fi := f . ( protocol . FileInfo )
fs = append ( fs , fi )
nblocks += len ( fi . Blocks )
return ( maxFiles <= 0 || len ( fs ) < maxFiles ) && ( maxBlocks <= 0 || nblocks < maxBlocks )
2014-07-06 05:46:48 -07:00
} )
return fs
2014-04-09 13:03:30 -07:00
}
return nil
2014-04-01 14:18:32 -07:00
}
2013-12-30 07:30:29 -07:00
// Index is called when a new node is connected and we receive their full index.
2014-01-06 03:11:18 -07:00
// Implements the protocol.Model interface.
2014-06-29 16:42:03 -07:00
func ( m * Model ) Index ( nodeID protocol . NodeID , repo string , fs [ ] protocol . FileInfo ) {
2014-05-14 20:26:55 -07:00
if debug {
2014-05-19 13:31:28 -07:00
l . Debugf ( "IDX(in): %s %q: %d files" , nodeID , repo , len ( fs ) )
2014-03-29 10:53:48 -07:00
}
2014-06-06 12:48:29 -07:00
if ! m . repoSharedWith ( repo , nodeID ) {
2014-08-18 14:34:03 -07:00
events . Default . Log ( events . RepoRejected , map [ string ] string {
"repo" : repo ,
"node" : nodeID . String ( ) ,
} )
2014-06-06 12:48:29 -07:00
l . Warnf ( "Unexpected repository ID %q sent from node %q; ensure that the repository exists and that this node is selected under \"Share With\" in the repository configuration." , repo , nodeID )
return
}
2014-03-29 10:53:48 -07:00
m . rmut . RLock ( )
2014-09-04 13:29:53 -07:00
files , ok := m . repoFiles [ repo ]
ignores , _ := m . repoIgnores [ repo ]
2014-07-17 04:38:36 -07:00
m . rmut . RUnlock ( )
2014-09-04 13:29:53 -07:00
if ! ok {
2014-06-06 12:48:29 -07:00
l . Fatalf ( "Index for nonexistant repo %q" , repo )
2013-12-15 03:43:31 -07:00
}
2014-07-13 12:07:24 -07:00
2014-09-04 13:29:53 -07:00
for i := 0 ; i < len ( fs ) ; {
lamport . Default . Tick ( fs [ i ] . Version )
if ignores . Match ( fs [ i ] . Name ) {
fs [ i ] = fs [ len ( fs ) - 1 ]
fs = fs [ : len ( fs ) - 1 ]
} else {
i ++
}
}
files . Replace ( nodeID , fs )
2014-07-17 04:38:36 -07:00
events . Default . Log ( events . RemoteIndexUpdated , map [ string ] interface { } {
"node" : nodeID . String ( ) ,
"repo" : repo ,
"items" : len ( fs ) ,
2014-09-04 13:29:53 -07:00
"version" : files . LocalVersion ( nodeID ) ,
2014-07-13 12:07:24 -07:00
} )
2013-12-28 06:10:36 -07:00
}
2013-12-30 07:30:29 -07:00
// IndexUpdate is called for incremental updates to connected nodes' indexes.
2014-01-06 03:11:18 -07:00
// Implements the protocol.Model interface.
2014-06-29 16:42:03 -07:00
func ( m * Model ) IndexUpdate ( nodeID protocol . NodeID , repo string , fs [ ] protocol . FileInfo ) {
2014-05-14 20:26:55 -07:00
if debug {
2014-05-14 17:08:56 -07:00
l . Debugf ( "IDXUP(in): %s / %q: %d files" , nodeID , repo , len ( fs ) )
2014-03-29 10:53:48 -07:00
}
2014-06-06 12:48:29 -07:00
if ! m . repoSharedWith ( repo , nodeID ) {
2014-08-05 11:26:05 -07:00
l . Infof ( "Update for unexpected repository ID %q sent from node %q; ensure that the repository exists and that this node is selected under \"Share With\" in the repository configuration." , repo , nodeID )
2014-06-06 12:48:29 -07:00
return
}
2014-03-29 10:53:48 -07:00
m . rmut . RLock ( )
2014-09-04 13:29:53 -07:00
files , ok := m . repoFiles [ repo ]
ignores , _ := m . repoIgnores [ repo ]
2014-07-17 04:38:36 -07:00
m . rmut . RUnlock ( )
2014-09-04 13:29:53 -07:00
if ! ok {
2014-06-06 12:48:29 -07:00
l . Fatalf ( "IndexUpdate for nonexistant repo %q" , repo )
2013-12-28 06:10:36 -07:00
}
2014-07-13 12:07:24 -07:00
2014-09-04 13:29:53 -07:00
for i := 0 ; i < len ( fs ) ; {
lamport . Default . Tick ( fs [ i ] . Version )
if ignores . Match ( fs [ i ] . Name ) {
fs [ i ] = fs [ len ( fs ) - 1 ]
fs = fs [ : len ( fs ) - 1 ]
} else {
i ++
}
}
files . Update ( nodeID , fs )
2014-07-17 04:38:36 -07:00
events . Default . Log ( events . RemoteIndexUpdated , map [ string ] interface { } {
"node" : nodeID . String ( ) ,
"repo" : repo ,
"items" : len ( fs ) ,
2014-09-04 13:29:53 -07:00
"version" : files . LocalVersion ( nodeID ) ,
2014-07-13 12:07:24 -07:00
} )
2014-01-09 02:59:09 -07:00
}
2014-06-29 16:42:03 -07:00
func ( m * Model ) repoSharedWith ( repo string , nodeID protocol . NodeID ) bool {
2014-06-06 12:48:29 -07:00
m . rmut . RLock ( )
defer m . rmut . RUnlock ( )
for _ , nrepo := range m . nodeRepos [ nodeID ] {
if nrepo == repo {
return true
}
}
return false
}
2014-09-23 07:04:20 -07:00
func ( m * Model ) ClusterConfig ( nodeID protocol . NodeID , cm protocol . ClusterConfigMessage ) {
2014-04-13 06:28:26 -07:00
m . pmut . Lock ( )
2014-09-23 07:04:20 -07:00
if cm . ClientName == "syncthing" {
m . nodeVer [ nodeID ] = cm . ClientVersion
2014-04-13 06:28:26 -07:00
} else {
2014-09-23 07:04:20 -07:00
m . nodeVer [ nodeID ] = cm . ClientName + " " + cm . ClientVersion
2014-04-13 06:28:26 -07:00
}
2014-08-16 13:55:02 -07:00
m . pmut . Unlock ( )
2014-09-23 07:04:20 -07:00
l . Infof ( ` Node %s client is "%s %s" ` , nodeID , cm . ClientName , cm . ClientVersion )
2014-08-16 13:55:02 -07:00
2014-09-23 07:04:20 -07:00
if name := cm . GetOption ( "name" ) ; name != "" {
2014-09-21 15:11:13 -07:00
l . Infof ( "Node %s name is %q" , nodeID , name )
2014-08-14 15:15:26 -07:00
node := m . cfg . GetNodeConfiguration ( nodeID )
if node != nil && node . Name == "" {
node . Name = name
2014-09-06 08:27:20 -07:00
m . cfg . Save ( )
2014-08-14 15:15:26 -07:00
}
}
2014-09-23 07:04:20 -07:00
if m . cfg . GetNodeConfiguration ( nodeID ) . Introducer {
// This node is an introducer. Go through the announced lists of repos
// and nodes and add what we are missing.
var changed bool
for _ , repo := range cm . Repositories {
// If we don't have this repository yet, skip it. Ideally, we'd
// offer up something in the GUI to create the repo, but for the
// moment we only handle repos that we already have.
if _ , ok := m . repoNodes [ repo . ID ] ; ! ok {
continue
}
nextNode :
for _ , node := range repo . Nodes {
var id protocol . NodeID
copy ( id [ : ] , node . ID )
2014-09-27 00:37:37 -07:00
if m . cfg . GetNodeConfiguration ( id ) == nil {
2014-09-23 07:04:20 -07:00
// The node is currently unknown. Add it to the config.
l . Infof ( "Adding node %v to config (vouched for by introducer %v)" , id , nodeID )
newNodeCfg := config . NodeConfiguration {
NodeID : id ,
}
// The introducers' introducers are also our introducers.
if node . Flags & protocol . FlagIntroducer != 0 {
l . Infof ( "Node %v is now also an introducer" , id )
newNodeCfg . Introducer = true
}
m . cfg . Nodes = append ( m . cfg . Nodes , newNodeCfg )
changed = true
}
for _ , er := range m . nodeRepos [ id ] {
if er == repo . ID {
// We already share the repo with this node, so
// nothing to do.
continue nextNode
}
}
// We don't yet share this repo with this node. Add the node
// to sharing list of the repo.
l . Infof ( "Adding node %v to share %q (vouched for by introducer %v)" , id , repo . ID , nodeID )
m . nodeRepos [ id ] = append ( m . nodeRepos [ id ] , repo . ID )
m . repoNodes [ repo . ID ] = append ( m . repoNodes [ repo . ID ] , id )
repoCfg := m . cfg . GetRepoConfiguration ( repo . ID )
repoCfg . Nodes = append ( repoCfg . Nodes , config . RepositoryNodeConfiguration {
NodeID : id ,
} )
changed = true
}
}
if changed {
m . cfg . Save ( )
}
}
2014-04-13 06:28:26 -07:00
}
2014-01-20 14:22:27 -07:00
// Close removes the peer from the model and closes the underlying connection if possible.
2014-01-06 03:11:18 -07:00
// Implements the protocol.Model interface.
2014-06-29 16:42:03 -07:00
func ( m * Model ) Close ( node protocol . NodeID , err error ) {
2014-06-23 06:06:20 -07:00
l . Infof ( "Connection to %s closed: %v" , node , err )
2014-07-13 12:07:24 -07:00
events . Default . Log ( events . NodeDisconnected , map [ string ] string {
"id" : node . String ( ) ,
"error" : err . Error ( ) ,
} )
2014-02-09 15:13:06 -07:00
2014-07-15 04:04:37 -07:00
m . pmut . Lock ( )
2014-03-29 10:53:48 -07:00
m . rmut . RLock ( )
for _ , repo := range m . nodeRepos [ node ] {
2014-07-06 05:46:48 -07:00
m . repoFiles [ repo ] . Replace ( node , nil )
2014-03-29 10:53:48 -07:00
}
m . rmut . RUnlock ( )
2014-01-20 14:22:27 -07:00
2013-12-31 19:22:49 -07:00
conn , ok := m . rawConn [ node ]
2014-01-01 06:09:17 -07:00
if ok {
2014-09-09 23:48:15 -07:00
if conn , ok := conn . ( * tls . Conn ) ; ok {
// If the underlying connection is a *tls.Conn, Close() does more
// than it says on the tin. Specifically, it sends a TLS alert
// message, which might block forever if the connection is dead
// and we don't have a deadline site.
conn . SetWriteDeadline ( time . Now ( ) . Add ( 250 * time . Millisecond ) )
}
2014-01-01 06:09:17 -07:00
conn . Close ( )
2013-12-30 19:21:57 -07:00
}
2014-01-09 05:58:35 -07:00
delete ( m . protoConn , node )
2013-12-31 19:22:49 -07:00
delete ( m . rawConn , node )
2014-04-13 06:28:26 -07:00
delete ( m . nodeVer , node )
2014-01-17 20:06:44 -07:00
m . pmut . Unlock ( )
2013-12-15 03:43:31 -07:00
}
2014-01-06 03:11:18 -07:00
// Request returns the specified data segment by reading it from local disk.
// Implements the protocol.Model interface.
2014-06-29 16:42:03 -07:00
func ( m * Model ) Request ( nodeID protocol . NodeID , repo , name string , offset int64 , size int ) ( [ ] byte , error ) {
2014-03-28 06:36:57 -07:00
// Verify that the requested file exists in the local model.
2014-03-29 10:53:48 -07:00
m . rmut . RLock ( )
r , ok := m . repoFiles [ repo ]
m . rmut . RUnlock ( )
if ! ok {
2014-05-14 17:08:56 -07:00
l . Warnf ( "Request from %s for file %s in nonexistent repo %q" , nodeID , name , repo )
2014-03-29 10:53:48 -07:00
return nil , ErrNoSuchFile
}
2014-07-06 05:46:48 -07:00
lf := r . Get ( protocol . LocalNodeID , name )
2014-07-12 14:06:48 -07:00
if protocol . IsInvalid ( lf . Flags ) || protocol . IsDeleted ( lf . Flags ) {
2014-05-20 11:26:44 -07:00
if debug {
l . Debugf ( "REQ(in): %s: %q / %q o=%d s=%d; invalid: %v" , nodeID , repo , name , offset , size , lf )
}
2014-05-11 10:54:26 -07:00
return nil , ErrInvalid
2014-01-06 13:31:36 -07:00
}
2014-03-29 10:53:48 -07:00
2014-07-12 14:06:48 -07:00
if offset > lf . Size ( ) {
2014-05-14 20:26:55 -07:00
if debug {
2014-05-14 17:08:56 -07:00
l . Debugf ( "REQ(in; nonexistent): %s: %q o=%d s=%d" , nodeID , name , offset , size )
2014-05-11 10:54:26 -07:00
}
return nil , ErrNoSuchFile
2014-01-07 14:44:21 -07:00
}
2014-01-06 13:31:36 -07:00
2014-07-06 05:46:48 -07:00
if debug && nodeID != protocol . LocalNodeID {
2014-05-14 17:08:56 -07:00
l . Debugf ( "REQ(in): %s: %q / %q o=%d s=%d" , nodeID , repo , name , offset , size )
2013-12-15 03:43:31 -07:00
}
2014-03-29 10:53:48 -07:00
m . rmut . RLock ( )
2014-05-23 05:31:16 -07:00
fn := filepath . Join ( m . repoCfgs [ repo ] . Directory , name )
2014-03-29 10:53:48 -07:00
m . rmut . RUnlock ( )
2013-12-15 03:43:31 -07:00
fd , err := os . Open ( fn ) // XXX: Inefficient, should cache fd?
if err != nil {
return nil , err
}
defer fd . Close ( )
2014-06-18 14:57:22 -07:00
buf := make ( [ ] byte , size )
2014-01-09 08:35:49 -07:00
_ , err = fd . ReadAt ( buf , offset )
2013-12-15 03:43:31 -07:00
if err != nil {
return nil , err
}
return buf , nil
}
2014-01-06 03:11:18 -07:00
// ReplaceLocal replaces the local repository index with the given list of files.
2014-07-12 14:06:48 -07:00
func ( m * Model ) ReplaceLocal ( repo string , fs [ ] protocol . FileInfo ) {
2014-03-29 10:53:48 -07:00
m . rmut . RLock ( )
2014-07-06 05:46:48 -07:00
m . repoFiles [ repo ] . ReplaceWithDelete ( protocol . LocalNodeID , fs )
2014-03-29 10:53:48 -07:00
m . rmut . RUnlock ( )
2013-12-15 03:43:31 -07:00
}
2014-07-12 14:06:48 -07:00
func ( m * Model ) CurrentRepoFile ( repo string , file string ) protocol . FileInfo {
2014-04-01 14:18:32 -07:00
m . rmut . RLock ( )
2014-07-06 05:46:48 -07:00
f := m . repoFiles [ repo ] . Get ( protocol . LocalNodeID , file )
2014-04-01 14:18:32 -07:00
m . rmut . RUnlock ( )
return f
}
2014-07-12 14:06:48 -07:00
func ( m * Model ) CurrentGlobalFile ( repo string , file string ) protocol . FileInfo {
2014-04-01 14:18:32 -07:00
m . rmut . RLock ( )
f := m . repoFiles [ repo ] . GetGlobal ( file )
m . rmut . RUnlock ( )
return f
}
2014-03-29 10:53:48 -07:00
type cFiler struct {
m * Model
r string
2014-01-06 03:11:18 -07:00
}
2014-03-16 00:14:55 -07:00
// Implements scanner.CurrentFiler
2014-07-12 14:06:48 -07:00
func ( cf cFiler ) CurrentFile ( file string ) protocol . FileInfo {
2014-04-01 14:18:32 -07:00
return cf . m . CurrentRepoFile ( cf . r , file )
2014-03-16 00:14:55 -07:00
}
2014-01-06 03:11:18 -07:00
// ConnectedTo returns true if we are connected to the named node.
2014-06-29 16:42:03 -07:00
func ( m * Model ) ConnectedTo ( nodeID protocol . NodeID ) bool {
2014-01-17 20:06:44 -07:00
m . pmut . RLock ( )
2014-09-09 23:30:35 -07:00
_ , ok := m . protoConn [ nodeID ]
2014-09-20 10:14:45 -07:00
m . pmut . RUnlock ( )
2014-09-10 02:29:01 -07:00
if ok {
2014-09-20 10:14:45 -07:00
m . nodeWasSeen ( nodeID )
2014-09-10 02:29:01 -07:00
}
2014-01-06 03:11:18 -07:00
return ok
}
2014-09-14 15:03:53 -07:00
func ( m * Model ) GetIgnores ( repo string ) ( [ ] string , error ) {
var lines [ ] string
cfg , ok := m . repoCfgs [ repo ]
if ! ok {
return lines , fmt . Errorf ( "Repo %s does not exist" , repo )
}
m . rmut . Lock ( )
defer m . rmut . Unlock ( )
fd , err := os . Open ( filepath . Join ( cfg . Directory , ".stignore" ) )
if err != nil {
if os . IsNotExist ( err ) {
return lines , nil
}
l . Warnln ( "Loading .stignore:" , err )
return lines , err
}
defer fd . Close ( )
scanner := bufio . NewScanner ( fd )
for scanner . Scan ( ) {
lines = append ( lines , strings . TrimSpace ( scanner . Text ( ) ) )
}
return lines , nil
}
func ( m * Model ) SetIgnores ( repo string , content [ ] string ) error {
cfg , ok := m . repoCfgs [ repo ]
if ! ok {
return fmt . Errorf ( "Repo %s does not exist" , repo )
}
2014-09-22 07:57:06 -07:00
fd , err := ioutil . TempFile ( cfg . Directory , ".syncthing.stignore-" + repo )
2014-09-14 15:03:53 -07:00
if err != nil {
l . Warnln ( "Saving .stignore:" , err )
return err
}
2014-09-22 06:54:36 -07:00
defer os . Remove ( fd . Name ( ) )
2014-09-14 15:03:53 -07:00
for _ , line := range content {
2014-09-22 07:53:57 -07:00
_ , err = fmt . Fprintln ( fd , line )
if err != nil {
l . Warnln ( "Saving .stignore:" , err )
return err
}
2014-09-14 15:03:53 -07:00
}
err = fd . Close ( )
if err != nil {
l . Warnln ( "Saving .stignore:" , err )
return err
}
file := filepath . Join ( cfg . Directory , ".stignore" )
2014-09-22 06:54:36 -07:00
err = osutil . Rename ( fd . Name ( ) , file )
2014-09-14 15:03:53 -07:00
if err != nil {
l . Warnln ( "Saving .stignore:" , err )
return err
}
return m . ScanRepo ( repo )
}
2014-01-06 03:11:18 -07:00
// AddConnection adds a new peer connection to the model. An initial index will
// be sent to the connected peer, thereafter index updates whenever the local
// repository changes.
2014-03-28 06:36:57 -07:00
func ( m * Model ) AddConnection ( rawConn io . Closer , protoConn protocol . Connection ) {
2014-01-09 05:58:35 -07:00
nodeID := protoConn . ID ( )
2014-07-15 04:04:37 -07:00
2014-01-17 20:06:44 -07:00
m . pmut . Lock ( )
2014-03-23 00:45:05 -07:00
if _ , ok := m . protoConn [ nodeID ] ; ok {
panic ( "add existing node" )
}
2014-01-09 05:58:35 -07:00
m . protoConn [ nodeID ] = protoConn
2014-03-23 00:45:05 -07:00
if _ , ok := m . rawConn [ nodeID ] ; ok {
panic ( "add existing node" )
}
2014-01-09 05:58:35 -07:00
m . rawConn [ nodeID ] = rawConn
2014-01-06 03:11:18 -07:00
2014-04-13 06:28:26 -07:00
cm := m . clusterConfig ( nodeID )
protoConn . ClusterConfig ( cm )
2014-05-04 08:18:58 -07:00
m . rmut . RLock ( )
for _ , repo := range m . nodeRepos [ nodeID ] {
2014-07-15 04:04:37 -07:00
fs := m . repoFiles [ repo ]
2014-09-04 13:29:53 -07:00
go sendIndexes ( protoConn , repo , fs , m . repoIgnores [ repo ] )
2014-05-04 08:18:58 -07:00
}
m . rmut . RUnlock ( )
2014-07-15 04:04:37 -07:00
m . pmut . Unlock ( )
2014-09-20 10:14:45 -07:00
m . nodeWasSeen ( nodeID )
}
func ( m * Model ) nodeStatRef ( nodeID protocol . NodeID ) * stats . NodeStatisticsReference {
m . rmut . Lock ( )
defer m . rmut . Unlock ( )
if sr , ok := m . nodeStatRefs [ nodeID ] ; ok {
return sr
} else {
sr = stats . NewNodeStatisticsReference ( m . db , nodeID )
m . nodeStatRefs [ nodeID ] = sr
return sr
}
}
func ( m * Model ) nodeWasSeen ( nodeID protocol . NodeID ) {
m . nodeStatRef ( nodeID ) . WasSeen ( )
2014-07-15 04:04:37 -07:00
}
2014-09-04 13:29:53 -07:00
func sendIndexes ( conn protocol . Connection , repo string , fs * files . Set , ignores ignore . Patterns ) {
2014-07-15 04:04:37 -07:00
nodeID := conn . ID ( )
name := conn . Name ( )
2014-07-30 11:08:04 -07:00
var err error
2014-07-15 04:04:37 -07:00
if debug {
l . Debugf ( "sendIndexes for %s-%s@/%q starting" , nodeID , name , repo )
}
2014-05-04 08:18:58 -07:00
2014-07-15 04:04:37 -07:00
defer func ( ) {
if debug {
l . Debugf ( "sendIndexes for %s-%s@/%q exiting: %v" , nodeID , name , repo , err )
}
} ( )
2014-09-04 13:29:53 -07:00
minLocalVer , err := sendIndexTo ( true , 0 , conn , repo , fs , ignores )
2014-07-30 11:08:04 -07:00
2014-07-15 04:04:37 -07:00
for err == nil {
2014-07-30 11:08:04 -07:00
time . Sleep ( 5 * time . Second )
if fs . LocalVersion ( protocol . LocalNodeID ) <= minLocalVer {
continue
2014-07-15 04:04:37 -07:00
}
2014-09-04 13:29:53 -07:00
minLocalVer , err = sendIndexTo ( false , minLocalVer , conn , repo , fs , ignores )
2014-07-30 11:08:04 -07:00
}
}
2014-07-15 04:04:37 -07:00
2014-09-04 13:29:53 -07:00
func sendIndexTo ( initial bool , minLocalVer uint64 , conn protocol . Connection , repo string , fs * files . Set , ignores ignore . Patterns ) ( uint64 , error ) {
2014-07-30 11:08:04 -07:00
nodeID := conn . ID ( )
name := conn . Name ( )
batch := make ( [ ] protocol . FileInfo , 0 , indexBatchSize )
2014-08-11 11:54:59 -07:00
currentBatchSize := 0
2014-07-30 11:08:04 -07:00
maxLocalVer := uint64 ( 0 )
var err error
2014-07-15 04:04:37 -07:00
2014-08-12 04:53:31 -07:00
fs . WithHave ( protocol . LocalNodeID , func ( fi protocol . FileIntf ) bool {
f := fi . ( protocol . FileInfo )
2014-07-30 11:08:04 -07:00
if f . LocalVersion <= minLocalVer {
return true
}
2014-07-15 04:04:37 -07:00
2014-07-30 11:08:04 -07:00
if f . LocalVersion > maxLocalVer {
maxLocalVer = f . LocalVersion
}
2014-07-15 04:04:37 -07:00
2014-09-04 13:29:53 -07:00
if ignores . Match ( f . Name ) {
return true
}
2014-08-11 11:54:59 -07:00
if len ( batch ) == indexBatchSize || currentBatchSize > indexTargetSize {
2014-07-30 11:08:04 -07:00
if initial {
if err = conn . Index ( repo , batch ) ; err != nil {
return false
}
if debug {
2014-08-11 11:54:59 -07:00
l . Debugf ( "sendIndexes for %s-%s/%q: %d files (<%d bytes) (initial index)" , nodeID , name , repo , len ( batch ) , currentBatchSize )
2014-07-30 11:08:04 -07:00
}
initial = false
} else {
if err = conn . IndexUpdate ( repo , batch ) ; err != nil {
return false
}
if debug {
2014-08-11 11:54:59 -07:00
l . Debugf ( "sendIndexes for %s-%s/%q: %d files (<%d bytes) (batched update)" , nodeID , name , repo , len ( batch ) , currentBatchSize )
2014-07-30 11:08:04 -07:00
}
2014-07-03 03:30:10 -07:00
}
2014-01-06 03:11:18 -07:00
2014-07-30 11:08:04 -07:00
batch = make ( [ ] protocol . FileInfo , 0 , indexBatchSize )
2014-08-11 11:54:59 -07:00
currentBatchSize = 0
2014-07-15 04:04:37 -07:00
}
2014-07-30 11:08:04 -07:00
batch = append ( batch , f )
2014-08-11 11:54:59 -07:00
currentBatchSize += indexPerFileSize + len ( f . Blocks ) * IndexPerBlockSize
2014-07-30 11:08:04 -07:00
return true
} )
if initial && err == nil {
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 ) )
}
} else if len ( batch ) > 0 && err == nil {
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 ) )
}
2014-07-15 04:04:37 -07:00
}
2014-07-30 11:08:04 -07:00
return maxLocalVer , err
2014-01-06 03:11:18 -07:00
}
2014-07-12 14:06:48 -07:00
func ( m * Model ) updateLocal ( repo string , f protocol . FileInfo ) {
2014-07-15 04:04:37 -07:00
f . LocalVersion = 0
2014-03-29 10:53:48 -07:00
m . rmut . RLock ( )
2014-07-12 14:06:48 -07:00
m . repoFiles [ repo ] . Update ( protocol . LocalNodeID , [ ] protocol . FileInfo { f } )
2014-03-29 10:53:48 -07:00
m . rmut . RUnlock ( )
2014-07-17 04:38:36 -07:00
events . Default . Log ( events . LocalIndexUpdated , map [ string ] interface { } {
"repo" : repo ,
"name" : f . Name ,
"modified" : time . Unix ( f . Modified , 0 ) ,
"flags" : fmt . Sprintf ( "0%o" , f . Flags ) ,
"size" : f . Size ( ) ,
} )
2014-03-28 06:36:57 -07:00
}
2014-06-29 16:42:03 -07:00
func ( m * Model ) requestGlobal ( nodeID protocol . NodeID , repo , name string , offset int64 , size int , hash [ ] byte ) ( [ ] byte , error ) {
2014-01-17 20:06:44 -07:00
m . pmut . RLock ( )
2014-01-09 05:58:35 -07:00
nc , ok := m . protoConn [ nodeID ]
2014-01-17 20:06:44 -07:00
m . pmut . RUnlock ( )
2014-01-06 03:11:18 -07:00
if ! ok {
return nil , fmt . Errorf ( "requestGlobal: no such node: %s" , nodeID )
}
2014-05-14 20:26:55 -07:00
if debug {
2014-05-14 17:08:56 -07:00
l . Debugf ( "REQ(out): %s: %q / %q o=%d s=%d h=%x" , nodeID , repo , name , offset , size , hash )
2014-01-06 03:11:18 -07:00
}
2014-03-29 10:53:48 -07:00
return nc . Request ( repo , name , offset , size )
2014-01-06 03:11:18 -07:00
}
2014-05-23 05:31:16 -07:00
func ( m * Model ) AddRepo ( cfg config . RepositoryConfiguration ) {
2014-03-29 10:53:48 -07:00
if m . started {
panic ( "cannot add repo to started model" )
}
2014-05-23 05:31:16 -07:00
if len ( cfg . ID ) == 0 {
2014-03-29 10:53:48 -07:00
panic ( "cannot add empty repo id" )
}
m . rmut . Lock ( )
2014-05-23 05:31:16 -07:00
m . repoCfgs [ cfg . ID ] = cfg
2014-07-06 05:46:48 -07:00
m . repoFiles [ cfg . ID ] = files . NewSet ( cfg . ID , m . db )
2013-12-15 03:43:31 -07:00
2014-06-29 16:42:03 -07:00
m . repoNodes [ cfg . ID ] = make ( [ ] protocol . NodeID , len ( cfg . Nodes ) )
2014-05-23 05:31:16 -07:00
for i , node := range cfg . Nodes {
m . repoNodes [ cfg . ID ] [ i ] = node . NodeID
m . nodeRepos [ node . NodeID ] = append ( m . nodeRepos [ node . NodeID ] , cfg . ID )
2014-03-29 10:53:48 -07:00
}
2014-01-23 14:20:15 -07:00
2014-03-29 10:53:48 -07:00
m . addedRepo = true
m . rmut . Unlock ( )
}
2014-01-23 14:20:15 -07:00
2014-03-29 10:53:48 -07:00
func ( m * Model ) ScanRepos ( ) {
m . rmut . RLock ( )
2014-05-23 05:31:16 -07:00
var repos = make ( [ ] string , 0 , len ( m . repoCfgs ) )
for repo := range m . repoCfgs {
2014-04-14 00:58:17 -07:00
repos = append ( repos , repo )
2014-03-29 10:53:48 -07:00
}
m . rmut . RUnlock ( )
2014-04-14 00:58:17 -07:00
2014-05-13 16:42:12 -07:00
var wg sync . WaitGroup
wg . Add ( len ( repos ) )
2014-04-14 00:58:17 -07:00
for _ , repo := range repos {
2014-05-13 16:42:12 -07:00
repo := repo
go func ( ) {
2014-05-27 21:55:30 -07:00
err := m . ScanRepo ( repo )
if err != nil {
invalidateRepo ( m . cfg , repo , err )
}
2014-05-13 16:42:12 -07:00
wg . Done ( )
} ( )
2014-04-14 00:58:17 -07:00
}
2014-05-13 16:42:12 -07:00
wg . Wait ( )
2014-03-29 10:53:48 -07:00
}
2013-12-15 03:43:31 -07:00
2014-05-14 20:26:55 -07:00
func ( m * Model ) CleanRepos ( ) {
m . rmut . RLock ( )
2014-05-23 05:31:16 -07:00
var dirs = make ( [ ] string , 0 , len ( m . repoCfgs ) )
for _ , cfg := range m . repoCfgs {
dirs = append ( dirs , cfg . Directory )
2014-05-14 20:26:55 -07:00
}
m . rmut . RUnlock ( )
var wg sync . WaitGroup
wg . Add ( len ( dirs ) )
for _ , dir := range dirs {
w := & scanner . Walker {
Dir : dir ,
TempNamer : defTempNamer ,
}
go func ( ) {
w . CleanTempFiles ( )
wg . Done ( )
} ( )
}
wg . Wait ( )
}
2014-05-04 09:20:25 -07:00
func ( m * Model ) ScanRepo ( repo string ) error {
2014-08-11 11:20:01 -07:00
return m . ScanRepoSub ( repo , "" )
}
func ( m * Model ) ScanRepoSub ( repo , sub string ) error {
if p := filepath . Clean ( filepath . Join ( repo , sub ) ) ; ! strings . HasPrefix ( p , repo ) {
return errors . New ( "invalid subpath" )
}
2014-05-02 08:14:53 -07:00
m . rmut . RLock ( )
2014-08-11 11:20:01 -07:00
fs , ok := m . repoFiles [ repo ]
2014-07-15 05:27:46 -07:00
dir := m . repoCfgs [ repo ] . Directory
2014-09-04 13:29:53 -07:00
ignores , _ := ignore . Load ( filepath . Join ( dir , ".stignore" ) )
m . repoIgnores [ repo ] = ignores
2014-03-29 10:53:48 -07:00
w := & scanner . Walker {
2014-07-15 05:27:46 -07:00
Dir : dir ,
2014-08-11 11:20:01 -07:00
Sub : sub ,
2014-09-04 13:29:53 -07:00
Ignores : ignores ,
2014-05-14 20:26:55 -07:00
BlockSize : scanner . StandardBlockSize ,
2014-04-08 04:45:18 -07:00
TempNamer : defTempNamer ,
CurrentFiler : cFiler { m , repo } ,
2014-05-23 05:31:16 -07:00
IgnorePerms : m . repoCfgs [ repo ] . IgnorePerms ,
2014-03-29 10:53:48 -07:00
}
2014-05-02 08:14:53 -07:00
m . rmut . RUnlock ( )
2014-08-11 11:20:01 -07:00
if ! ok {
return errors . New ( "no such repo" )
}
2014-07-15 05:27:46 -07:00
2014-04-14 00:58:17 -07:00
m . setState ( repo , RepoScanning )
2014-08-26 01:11:25 -07:00
fchan , err := w . Walk ( )
2014-07-15 05:27:46 -07:00
2014-05-04 09:20:25 -07:00
if err != nil {
return err
}
2014-07-15 08:54:00 -07:00
batchSize := 100
batch := make ( [ ] protocol . FileInfo , 0 , 00 )
2014-07-15 05:27:46 -07:00
for f := range fchan {
2014-07-29 02:53:45 -07:00
events . Default . Log ( events . LocalIndexUpdated , map [ string ] interface { } {
"repo" : repo ,
"name" : f . Name ,
"modified" : time . Unix ( f . Modified , 0 ) ,
"flags" : fmt . Sprintf ( "0%o" , f . Flags ) ,
"size" : f . Size ( ) ,
} )
2014-07-15 08:54:00 -07:00
if len ( batch ) == batchSize {
2014-07-15 05:27:46 -07:00
fs . Update ( protocol . LocalNodeID , batch )
batch = batch [ : 0 ]
}
batch = append ( batch , f )
}
if len ( batch ) > 0 {
fs . Update ( protocol . LocalNodeID , batch )
}
batch = batch [ : 0 ]
2014-08-11 11:20:01 -07:00
// TODO: We should limit the Have scanning to start at sub
seenPrefix := false
2014-08-12 04:53:31 -07:00
fs . WithHaveTruncated ( protocol . LocalNodeID , func ( fi protocol . FileIntf ) bool {
f := fi . ( protocol . FileInfoTruncated )
2014-08-11 11:20:01 -07:00
if ! strings . HasPrefix ( f . Name , sub ) {
2014-09-04 13:29:53 -07:00
// Return true so that we keep iterating, until we get to the part
// of the tree we are interested in. Then return false so we stop
// iterating when we've passed the end of the subtree.
2014-08-11 11:20:01 -07:00
return ! seenPrefix
}
2014-09-04 13:29:53 -07:00
2014-08-11 11:20:01 -07:00
seenPrefix = true
2014-07-15 05:27:46 -07:00
if ! protocol . IsDeleted ( f . Flags ) {
2014-09-04 13:29:53 -07:00
if f . IsInvalid ( ) {
return true
}
2014-07-15 08:54:00 -07:00
if len ( batch ) == batchSize {
2014-07-15 05:27:46 -07:00
fs . Update ( protocol . LocalNodeID , batch )
batch = batch [ : 0 ]
}
2014-09-04 13:29:53 -07:00
if ignores . Match ( f . Name ) {
// File has been ignored. Set invalid bit.
nf := protocol . FileInfo {
Name : f . Name ,
Flags : f . Flags | protocol . FlagInvalid ,
Modified : f . Modified ,
Version : f . Version , // The file is still the same, so don't bump version
}
events . Default . Log ( events . LocalIndexUpdated , map [ string ] interface { } {
"repo" : repo ,
"name" : f . Name ,
"modified" : time . Unix ( f . Modified , 0 ) ,
"flags" : fmt . Sprintf ( "0%o" , f . Flags ) ,
"size" : f . Size ( ) ,
} )
batch = append ( batch , nf )
} else if _ , err := os . Stat ( filepath . Join ( dir , f . Name ) ) ; err != nil && os . IsNotExist ( err ) {
2014-07-15 05:27:46 -07:00
// File has been deleted
2014-08-12 04:53:31 -07:00
nf := protocol . FileInfo {
Name : f . Name ,
Flags : f . Flags | protocol . FlagDeleted ,
Modified : f . Modified ,
Version : lamport . Default . Tick ( f . Version ) ,
}
2014-07-29 02:53:45 -07:00
events . Default . Log ( events . LocalIndexUpdated , map [ string ] interface { } {
"repo" : repo ,
"name" : f . Name ,
"modified" : time . Unix ( f . Modified , 0 ) ,
"flags" : fmt . Sprintf ( "0%o" , f . Flags ) ,
"size" : f . Size ( ) ,
} )
2014-08-12 04:53:31 -07:00
batch = append ( batch , nf )
2014-07-15 05:27:46 -07:00
}
}
return true
} )
if len ( batch ) > 0 {
fs . Update ( protocol . LocalNodeID , batch )
}
2014-04-14 00:58:17 -07:00
m . setState ( repo , RepoIdle )
2014-05-04 09:20:25 -07:00
return nil
2014-03-29 10:53:48 -07:00
}
2014-04-13 06:28:26 -07:00
// clusterConfig returns a ClusterConfigMessage that is correct for the given peer node
2014-06-29 16:42:03 -07:00
func ( m * Model ) clusterConfig ( node protocol . NodeID ) protocol . ClusterConfigMessage {
2014-04-13 06:28:26 -07:00
cm := protocol . ClusterConfigMessage {
2014-05-14 20:26:55 -07:00
ClientName : m . clientName ,
ClientVersion : m . clientVersion ,
2014-08-14 15:15:26 -07:00
Options : [ ] protocol . Option {
{
Key : "name" ,
Value : m . nodeName ,
} ,
} ,
2014-04-13 06:28:26 -07:00
}
2014-05-02 08:14:53 -07:00
m . rmut . RLock ( )
2014-04-13 06:28:26 -07:00
for _ , repo := range m . nodeRepos [ node ] {
cr := protocol . Repository {
ID : repo ,
2014-01-09 05:58:35 -07:00
}
2014-04-13 06:28:26 -07:00
for _ , node := range m . repoNodes [ repo ] {
2014-09-19 04:21:58 -07:00
// NodeID is a value type, but with an underlying array. Copy it
// so we don't grab aliases to the same array later on in node[:]
node := node
2014-04-13 06:28:26 -07:00
// TODO: Set read only bit when relevant
2014-09-23 07:04:20 -07:00
cn := protocol . Node {
2014-06-29 16:42:03 -07:00
ID : node [ : ] ,
2014-04-13 06:28:26 -07:00
Flags : protocol . FlagShareTrusted ,
2014-09-23 07:04:20 -07:00
}
if nodeCfg := m . cfg . GetNodeConfiguration ( node ) ; nodeCfg . Introducer {
cn . Flags |= protocol . FlagIntroducer
}
cr . Nodes = append ( cr . Nodes , cn )
2014-01-09 05:58:35 -07:00
}
2014-04-13 06:28:26 -07:00
cm . Repositories = append ( cm . Repositories , cr )
2013-12-29 18:33:57 -07:00
}
2014-05-02 08:14:53 -07:00
m . rmut . RUnlock ( )
2014-04-13 06:28:26 -07:00
return cm
2013-12-29 18:33:57 -07:00
}
2014-04-14 00:58:17 -07:00
func ( m * Model ) setState ( repo string , state repoState ) {
2014-05-20 09:41:01 -07:00
m . smut . Lock ( )
2014-07-17 04:38:36 -07:00
oldState := m . repoState [ repo ]
changed , ok := m . repoStateChanged [ repo ]
if state != oldState {
m . repoState [ repo ] = state
m . repoStateChanged [ repo ] = time . Now ( )
eventData := map [ string ] interface { } {
"repo" : repo ,
"to" : state . String ( ) ,
}
if ok {
eventData [ "duration" ] = time . Since ( changed ) . Seconds ( )
eventData [ "from" ] = oldState . String ( )
}
events . Default . Log ( events . StateChanged , eventData )
}
2014-05-20 09:41:01 -07:00
m . smut . Unlock ( )
2014-04-14 00:58:17 -07:00
}
2014-07-17 04:38:36 -07:00
func ( m * Model ) State ( repo string ) ( string , time . Time ) {
2014-05-20 09:41:01 -07:00
m . smut . RLock ( )
2014-04-14 00:58:17 -07:00
state := m . repoState [ repo ]
2014-07-17 04:38:36 -07:00
changed := m . repoStateChanged [ repo ]
2014-05-20 09:41:01 -07:00
m . smut . RUnlock ( )
2014-07-17 04:38:36 -07:00
return state . String ( ) , changed
2014-04-14 00:58:17 -07:00
}
2014-06-16 01:47:02 -07:00
func ( m * Model ) Override ( repo string ) {
2014-06-23 02:52:13 -07:00
m . rmut . RLock ( )
2014-07-15 08:54:00 -07:00
fs := m . repoFiles [ repo ]
2014-06-23 02:52:13 -07:00
m . rmut . RUnlock ( )
2014-09-04 13:29:53 -07:00
m . setState ( repo , RepoScanning )
2014-07-15 08:54:00 -07:00
batch := make ( [ ] protocol . FileInfo , 0 , indexBatchSize )
2014-08-12 04:53:31 -07:00
fs . WithNeed ( protocol . LocalNodeID , func ( fi protocol . FileIntf ) bool {
need := fi . ( protocol . FileInfo )
2014-07-15 08:54:00 -07:00
if len ( batch ) == indexBatchSize {
fs . Update ( protocol . LocalNodeID , batch )
batch = batch [ : 0 ]
}
have := fs . Get ( protocol . LocalNodeID , need . Name )
if have . Name != need . Name {
2014-06-16 01:47:02 -07:00
// We are missing the file
2014-07-15 08:54:00 -07:00
need . Flags |= protocol . FlagDeleted
need . Blocks = nil
2014-06-16 01:47:02 -07:00
} else {
// We have the file, replace with our version
2014-07-15 08:54:00 -07:00
need = have
2014-06-16 01:47:02 -07:00
}
2014-07-15 08:54:00 -07:00
need . Version = lamport . Default . Tick ( need . Version )
need . LocalVersion = 0
batch = append ( batch , need )
return true
} )
if len ( batch ) > 0 {
fs . Update ( protocol . LocalNodeID , batch )
2014-06-16 01:47:02 -07:00
}
2014-09-04 13:29:53 -07:00
m . setState ( repo , RepoIdle )
2014-06-16 01:47:02 -07:00
}
2014-06-19 15:27:54 -07:00
// Version returns the change version for the given repository. This is
// guaranteed to increment if the contents of the local or global repository
// has changed.
2014-07-15 04:04:37 -07:00
func ( m * Model ) LocalVersion ( repo string ) uint64 {
2014-06-19 15:27:54 -07:00
m . rmut . Lock ( )
2014-07-15 08:54:00 -07:00
defer m . rmut . Unlock ( )
fs , ok := m . repoFiles [ repo ]
if ! ok {
2014-09-22 05:03:58 -07:00
panic ( "bug: LocalVersion called for nonexistent repo " + repo )
2014-07-15 08:54:00 -07:00
}
ver := fs . LocalVersion ( protocol . LocalNodeID )
2014-06-19 15:27:54 -07:00
for _ , n := range m . repoNodes [ repo ] {
2014-07-15 08:54:00 -07:00
ver += fs . LocalVersion ( n )
2014-06-19 15:27:54 -07:00
}
return ver
}