Command line flags

This commit is contained in:
Jakob Borg 2013-12-18 19:36:28 +01:00
parent f774b0a5dc
commit eba1c9e649
4 changed files with 62 additions and 90 deletions

126
main.go
View File

@ -17,34 +17,32 @@ import (
"github.com/calmh/ini" "github.com/calmh/ini"
"github.com/calmh/syncthing/discover" "github.com/calmh/syncthing/discover"
"github.com/calmh/syncthing/protocol" "github.com/calmh/syncthing/protocol"
docopt "github.com/docopt/docopt.go" flags "github.com/jessevdk/go-flags"
) )
type Options struct {
ConfDir string `short:"c" long:"cfg" description:"Configuration directory" default:"~/.syncthing" value-name:"DIR"`
Listen string `short:"l" long:"listen" description:"Listen address" default:":22000" value-name:"ADDR"`
ReadOnly bool `long:"ro" description:"Repository is read only"`
Delete bool `long:"delete" description:"Delete files from repo when deleted from cluster"`
NoSymlinks bool `long:"no-symlinks" description:"Don't follow first level symlinks in the repo"`
ScanInterval time.Duration `long:"scan-intv" description:"Repository scan interval" default:"60s" value-name:"INTV"`
ConnInterval time.Duration `long:"conn-intv" description:"Node reconnect interval" default:"60s" value-name:"INTV"`
Debug DebugOptions `group:"Debugging Options"`
}
type DebugOptions struct {
TraceFile bool `long:"trace-file"`
TraceNet bool `long:"trace-net"`
TraceIdx bool `long:"trace-idx"`
Profiler string `long:"profiler"`
}
var opts Options
const ( const (
confDirName = ".syncthing" confDirName = ".syncthing"
confFileName = "syncthing.ini" confFileName = "syncthing.ini"
usage = `Usage:
syncthing [options]
Options:
-l <addr> Listening address [default: :22000]
-p <addr> Enable HTTP profiler on addr
--home <path> Home directory
--delete Delete files that were deleted on a peer node
--ro Local repository is read only
--scan-intv <s> Repository scan interval, in seconds [default: 60]
--conn-intv <s> Node reconnect interval, in seconds [default: 15]
--no-symlinks Don't follow first level symlinks in the repo
Help Options:
-h, --help Show this help
--version Show version
Debug Options:
--trace-file Trace file operations
--trace-net Trace network operations
--trace-idx Trace sent indexes
`
) )
var ( var (
@ -54,66 +52,40 @@ var (
// Options // Options
var ( var (
confDir = path.Join(getHomeDir(), confDirName) ConfDir = path.Join(getHomeDir(), confDirName)
addr string
prof string
readOnly bool
scanIntv int
connIntv int
traceNet bool
traceFile bool
traceIdx bool
doDelete bool
followSymlinks bool
) )
func main() { func main() {
// Useful for debugging; to be adjusted. // Useful for debugging; to be adjusted.
log.SetFlags(log.Ltime | log.Lshortfile) log.SetFlags(log.Ltime | log.Lshortfile)
arguments, _ := docopt.Parse(usage, nil, true, "syncthing 0.1", false) _, err := flags.Parse(&opts)
if err != nil {
addr = arguments["-l"].(string) os.Exit(0)
prof, _ = arguments["-p"].(string)
readOnly, _ = arguments["--ro"].(bool)
if arguments["--home"] != nil {
confDir, _ = arguments["--home"].(string)
} }
if strings.HasPrefix(opts.ConfDir, "~/") {
scanIntv, _ = strconv.Atoi(arguments["--scan-intv"].(string)) opts.ConfDir = strings.Replace(opts.ConfDir, "~", getHomeDir(), 1)
if scanIntv == 0 {
fatalln("Invalid --scan-intv")
} }
connIntv, _ = strconv.Atoi(arguments["--conn-intv"].(string))
if connIntv == 0 {
fatalln("Invalid --conn-intv")
}
doDelete = arguments["--delete"].(bool)
traceFile = arguments["--trace-file"].(bool)
traceNet = arguments["--trace-net"].(bool)
traceIdx = arguments["--trace-idx"].(bool)
followSymlinks = !arguments["--no-symlinks"].(bool)
// Ensure that our home directory exists and that we have a certificate and key. // Ensure that our home directory exists and that we have a certificate and key.
ensureDir(confDir) ensureDir(ConfDir)
cert, err := loadCert(confDir) cert, err := loadCert(ConfDir)
if err != nil { if err != nil {
newCertificate(confDir) newCertificate(ConfDir)
cert, err = loadCert(confDir) cert, err = loadCert(ConfDir)
fatalErr(err) fatalErr(err)
} }
myID := string(certId(cert.Certificate[0])) myID := string(certId(cert.Certificate[0]))
infoln("My ID:", myID) infoln("My ID:", myID)
if prof != "" { if opts.Debug.Profiler != "" {
okln("Profiler listening on", prof)
go func() { go func() {
http.ListenAndServe(prof, nil) err := http.ListenAndServe(opts.Debug.Profiler, nil)
if err != nil {
warnln(err)
}
}() }()
} }
@ -130,7 +102,7 @@ func main() {
// Load the configuration file, if it exists. // Load the configuration file, if it exists.
cf, err := os.Open(path.Join(confDir, confFileName)) cf, err := os.Open(path.Join(ConfDir, confFileName))
if err != nil { if err != nil {
fatalln("No config file") fatalln("No config file")
config = ini.Config{} config = ini.Config{}
@ -159,15 +131,15 @@ func main() {
// Routine to listen for incoming connections // Routine to listen for incoming connections
infoln("Listening for incoming connections") infoln("Listening for incoming connections")
go listen(myID, addr, m, cfg) go listen(myID, opts.Listen, m, cfg)
// Routine to connect out to configured nodes // Routine to connect out to configured nodes
infoln("Attempting to connect to other nodes") infoln("Attempting to connect to other nodes")
go connect(myID, addr, nodeAddrs, m, cfg) go connect(myID, opts.Listen, nodeAddrs, m, cfg)
// Routine to pull blocks from other nodes to synchronize the local // Routine to pull blocks from other nodes to synchronize the local
// repository. Does not run when we are in read only (publish only) mode. // repository. Does not run when we are in read only (publish only) mode.
if !readOnly { if !opts.ReadOnly {
infoln("Cleaning out incomplete synchronizations") infoln("Cleaning out incomplete synchronizations")
CleanTempFiles(dir) CleanTempFiles(dir)
okln("Ready to synchronize") okln("Ready to synchronize")
@ -178,7 +150,7 @@ func main() {
// XXX: Should use some fsnotify mechanism. // XXX: Should use some fsnotify mechanism.
go func() { go func() {
for { for {
time.Sleep(time.Duration(scanIntv) * time.Second) time.Sleep(time.Duration(opts.ScanInterval) * time.Second)
updateLocalModel(m) updateLocalModel(m)
} }
}() }()
@ -198,7 +170,7 @@ listen:
continue continue
} }
if traceNet { if opts.Debug.TraceNet {
debugln("NET: Connect from", conn.RemoteAddr()) debugln("NET: Connect from", conn.RemoteAddr())
} }
@ -267,12 +239,12 @@ func connect(myID string, addr string, nodeAddrs map[string][]string, m *Model,
} }
} }
if traceNet { if opts.Debug.TraceNet {
debugln("NET: Dial", nodeID, addr) debugln("NET: Dial", nodeID, addr)
} }
conn, err := tls.Dial("tcp", addr, cfg) conn, err := tls.Dial("tcp", addr, cfg)
if err != nil { if err != nil {
if traceNet { if opts.Debug.TraceNet {
debugln("NET:", err) debugln("NET:", err)
} }
continue continue
@ -288,7 +260,7 @@ func connect(myID string, addr string, nodeAddrs map[string][]string, m *Model,
nc := protocol.NewConnection(nodeID, conn, conn, m) nc := protocol.NewConnection(nodeID, conn, conn, m)
okln("Connected to node", remoteID, "(out)") okln("Connected to node", remoteID, "(out)")
m.AddNode(nc) m.AddNode(nc)
if traceNet { if opts.Debug.TraceNet {
t0 := time.Now() t0 := time.Now()
nc.Ping() nc.Ping()
timing("NET: Ping reply", t0) timing("NET: Ping reply", t0)
@ -297,19 +269,19 @@ func connect(myID string, addr string, nodeAddrs map[string][]string, m *Model,
} }
} }
time.Sleep(time.Duration(connIntv) * time.Second) time.Sleep(time.Duration(opts.ConnInterval) * time.Second)
} }
} }
func updateLocalModel(m *Model) { func updateLocalModel(m *Model) {
files := Walk(m.Dir(), m, followSymlinks) files := Walk(m.Dir(), m, !opts.NoSymlinks)
m.ReplaceLocal(files) m.ReplaceLocal(files)
saveIndex(m) saveIndex(m)
} }
func saveIndex(m *Model) { func saveIndex(m *Model) {
fname := fmt.Sprintf("%x.idx", sha1.Sum([]byte(m.Dir()))) fname := fmt.Sprintf("%x.idx", sha1.Sum([]byte(m.Dir())))
idxf, err := os.Create(path.Join(confDir, fname)) idxf, err := os.Create(path.Join(ConfDir, fname))
if err != nil { if err != nil {
return return
} }
@ -319,7 +291,7 @@ func saveIndex(m *Model) {
func loadIndex(m *Model) { func loadIndex(m *Model) {
fname := fmt.Sprintf("%x.idx", sha1.Sum([]byte(m.Dir()))) fname := fmt.Sprintf("%x.idx", sha1.Sum([]byte(m.Dir())))
idxf, err := os.Open(path.Join(confDir, fname)) idxf, err := os.Open(path.Join(ConfDir, fname))
if err != nil { if err != nil {
return return
} }

View File

@ -60,13 +60,13 @@ func (m *Model) Index(nodeID string, fs []protocol.FileInfo) {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
if traceNet { if opts.Debug.TraceNet {
debugf("NET IDX(in): %s: %d files", nodeID, len(fs)) debugf("NET IDX(in): %s: %d files", nodeID, len(fs))
} }
m.remote[nodeID] = make(map[string]File) m.remote[nodeID] = make(map[string]File)
for _, f := range fs { for _, f := range fs {
if f.Flags&FlagDeleted != 0 && !doDelete { if f.Flags&FlagDeleted != 0 && !opts.Delete {
// Files marked as deleted do not even enter the model // Files marked as deleted do not even enter the model
continue continue
} }
@ -122,7 +122,7 @@ func (m *Model) Close(node string) {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
if traceNet { if opts.Debug.TraceNet {
debugf("NET CLOSE: %s", node) debugf("NET CLOSE: %s", node)
} }
@ -134,7 +134,7 @@ func (m *Model) Close(node string) {
} }
func (m *Model) Request(nodeID, name string, offset uint64, size uint32, hash []byte) ([]byte, error) { func (m *Model) Request(nodeID, name string, offset uint64, size uint32, hash []byte) ([]byte, error) {
if traceNet && nodeID != "<local>" { if opts.Debug.TraceNet && nodeID != "<local>" {
debugf("NET REQ(in): %s: %q o=%d s=%d h=%x", nodeID, name, offset, size, hash) debugf("NET REQ(in): %s: %q o=%d s=%d h=%x", nodeID, name, offset, size, hash)
} }
fn := path.Join(m.dir, name) fn := path.Join(m.dir, name)
@ -158,7 +158,7 @@ func (m *Model) RequestGlobal(nodeID, name string, offset uint64, size uint32, h
nc := m.nodes[nodeID] nc := m.nodes[nodeID]
m.RUnlock() m.RUnlock()
if traceNet { if opts.Debug.TraceNet {
debugf("NET REQ(out): %s: %q o=%d s=%d h=%x", nodeID, name, offset, size, hash) debugf("NET REQ(out): %s: %q o=%d s=%d h=%x", nodeID, name, offset, size, hash)
} }
@ -200,7 +200,7 @@ func (m *Model) ReplaceLocal(fs []File) {
func (m *Model) broadcastIndex() { func (m *Model) broadcastIndex() {
idx := m.protocolIndex() idx := m.protocolIndex()
for _, node := range m.nodes { for _, node := range m.nodes {
if traceNet { if opts.Debug.TraceNet {
debugf("NET IDX(out): %s: %d files", node.ID, len(idx)) debugf("NET IDX(out): %s: %d files", node.ID, len(idx))
} }
node.Index(idx) node.Index(idx)
@ -346,7 +346,7 @@ func (m *Model) protocolIndex() []protocol.FileInfo {
Hash: b.Hash, Hash: b.Hash,
}) })
} }
if traceIdx { if opts.Debug.TraceIdx {
var flagComment string var flagComment string
if mf.Flags&FlagDeleted != 0 { if mf.Flags&FlagDeleted != 0 {
flagComment = " (deleted)" flagComment = " (deleted)"
@ -366,7 +366,7 @@ func (m *Model) AddNode(node *protocol.Connection) {
idx := m.protocolIndex() idx := m.protocolIndex()
m.RUnlock() m.RUnlock()
if traceNet { if opts.Debug.TraceNet {
debugf("NET IDX(out): %s: %d files", node.ID, len(idx)) debugf("NET IDX(out): %s: %d files", node.ID, len(idx))
} }
node.Index(idx) node.Index(idx)

View File

@ -171,12 +171,12 @@ func (m *Model) puller() {
var err error var err error
if f.Flags&FlagDeleted == 0 { if f.Flags&FlagDeleted == 0 {
if traceFile { if opts.Debug.TraceFile {
debugf("FILE: Pull %q", n) debugf("FILE: Pull %q", n)
} }
err = m.pullFile(n) err = m.pullFile(n)
} else { } else {
if traceFile { if opts.Debug.TraceFile {
debugf("FILE: Remove %q", n) debugf("FILE: Remove %q", n)
} }
// Cheerfully ignore errors here // Cheerfully ignore errors here

View File

@ -62,7 +62,7 @@ func genWalker(base string, res *[]File, model *Model) filepath.WalkFunc {
// No change // No change
*res = append(*res, hf) *res = append(*res, hf)
} else { } else {
if traceFile { if opts.Debug.TraceFile {
debugf("FILE: Hash %q", p) debugf("FILE: Hash %q", p)
} }
fd, err := os.Open(p) fd, err := os.Open(p)
@ -97,7 +97,7 @@ func Walk(dir string, model *Model, followSymlinks bool) []File {
warnln(err) warnln(err)
} }
if followSymlinks { if !opts.NoSymlinks {
d, err := os.Open(dir) d, err := os.Open(dir)
if err != nil { if err != nil {
warnln(err) warnln(err)
@ -127,7 +127,7 @@ func cleanTempFile(path string, info os.FileInfo, err error) error {
return err return err
} }
if info.Mode()&os.ModeType == 0 && isTempName(path) { if info.Mode()&os.ModeType == 0 && isTempName(path) {
if traceFile { if opts.Debug.TraceFile {
debugf("FILE: Remove %q", path) debugf("FILE: Remove %q", path)
} }
os.Remove(path) os.Remove(path)