all: Use new reflect based CLI (#5487)

This commit is contained in:
Audrius Butkevicius
2019-02-12 06:58:24 +00:00
committed by Jakob Borg
parent 7bac927ac8
commit dc929946fe
37 changed files with 944 additions and 1723 deletions

81
lib/build/build.go Normal file
View File

@@ -0,0 +1,81 @@
// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package build
import (
"fmt"
"log"
"regexp"
"runtime"
"strconv"
"strings"
"time"
)
var (
// Injected by build script
Version = "unknown-dev"
Host = "unknown" // Set by build script
User = "unknown" // Set by build script
Stamp = "0" // Set by build script
// Static
Codename = "Erbium Earthworm"
// Set by init()
Date time.Time
IsRelease bool
IsCandidate bool
IsBeta bool
LongVersion string
// Set by Go build tags
Tags []string
allowedVersionExp = regexp.MustCompile(`^v\d+\.\d+\.\d+(-[a-z0-9]+)*(\.\d+)*(\+\d+-g[0-9a-f]+)?(-[^\s]+)?$`)
)
func init() {
if Version != "unknown-dev" {
// If not a generic dev build, version string should come from git describe
if !allowedVersionExp.MatchString(Version) {
log.Fatalf("Invalid version string %q;\n\tdoes not match regexp %v", Version, allowedVersionExp)
}
}
setBuildData()
}
func setBuildData() {
// Check for a clean release build. A release is something like
// "v0.1.2", with an optional suffix of letters and dot separated
// numbers like "-beta3.47". If there's more stuff, like a plus sign and
// a commit hash and so on, then it's not a release. If it has a dash in
// it, it's some sort of beta, release candidate or special build. If it
// has "-rc." in it, like "v0.14.35-rc.42", then it's a candidate build.
//
// So, every build that is not a stable release build has IsBeta = true.
// This is used to enable some extra debugging (the deadlock detector).
//
// Release candidate builds are also "betas" from this point of view and
// will have that debugging enabled. In addition, some features are
// forced for release candidates - auto upgrade, and usage reporting.
exp := regexp.MustCompile(`^v\d+\.\d+\.\d+(-[a-z]+[\d\.]+)?$`)
IsRelease = exp.MatchString(Version)
IsCandidate = strings.Contains(Version, "-rc.")
IsBeta = strings.Contains(Version, "-")
stamp, _ := strconv.Atoi(Stamp)
Date = time.Unix(int64(stamp), 0)
date := Date.UTC().Format("2006-01-02 15:04:05 MST")
LongVersion = fmt.Sprintf(`syncthing %s "%s" (%s %s-%s) %s@%s %s`, Version, Codename, runtime.Version(), runtime.GOOS, runtime.GOARCH, User, Host, date)
if len(Tags) > 0 {
LongVersion = fmt.Sprintf("%s [%s]", LongVersion, strings.Join(Tags, ", "))
}
}

37
lib/build/build_test.go Normal file
View File

@@ -0,0 +1,37 @@
// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package build
import (
"testing"
)
func TestAllowedVersions(t *testing.T) {
testcases := []struct {
ver string
allowed bool
}{
{"v0.13.0", true},
{"v0.12.11+22-gabcdef0", true},
{"v0.13.0-beta0", true},
{"v0.13.0-beta47", true},
{"v0.13.0-beta47+1-gabcdef0", true},
{"v0.13.0-beta.0", true},
{"v0.13.0-beta.47", true},
{"v0.13.0-beta.0+1-gabcdef0", true},
{"v0.13.0-beta.47+1-gabcdef0", true},
{"v0.13.0-some-weird-but-allowed-tag", true},
{"v0.13.0-allowed.to.do.this", true},
{"v0.13.0+not.allowed.to.do.this", false},
}
for i, c := range testcases {
if allowed := allowedVersionExp.MatchString(c.ver); allowed != c.allowed {
t.Errorf("%d: incorrect result %v != %v for %q", i, allowed, c.allowed, c.ver)
}
}
}

View File

@@ -0,0 +1,13 @@
// Copyright (C) 2017 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
//+build noupgrade
package build
func init() {
Tags = append(Tags, "noupgrade")
}

13
lib/build/tags_race.go Normal file
View File

@@ -0,0 +1,13 @@
// Copyright (C) 2017 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
//+build race
package build
func init() {
Tags = append(Tags, "race")
}

View File

@@ -10,12 +10,13 @@ import (
"sort"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/util"
)
type DeviceConfiguration struct {
DeviceID protocol.DeviceID `xml:"id,attr" json:"deviceID"`
Name string `xml:"name,attr,omitempty" json:"name"`
Addresses []string `xml:"address,omitempty" json:"addresses"`
Addresses []string `xml:"address,omitempty" json:"addresses" default:"dynamic"`
Compression protocol.Compression `xml:"compression,attr" json:"compression"`
CertName string `xml:"certName,attr,omitempty" json:"certName"`
Introducer bool `xml:"introducer,attr" json:"introducer"`
@@ -36,6 +37,9 @@ func NewDeviceConfiguration(id protocol.DeviceID, name string) DeviceConfigurati
DeviceID: id,
Name: name,
}
util.SetDefaults(&d)
d.prepare(nil)
return d
}

View File

@@ -32,12 +32,12 @@ type FolderConfiguration struct {
Path string `xml:"path,attr" json:"path"`
Type FolderType `xml:"type,attr" json:"type"`
Devices []FolderDeviceConfiguration `xml:"device" json:"devices"`
RescanIntervalS int `xml:"rescanIntervalS,attr" json:"rescanIntervalS"`
FSWatcherEnabled bool `xml:"fsWatcherEnabled,attr" json:"fsWatcherEnabled"`
FSWatcherDelayS int `xml:"fsWatcherDelayS,attr" json:"fsWatcherDelayS"`
RescanIntervalS int `xml:"rescanIntervalS,attr" json:"rescanIntervalS" default:"3600"`
FSWatcherEnabled bool `xml:"fsWatcherEnabled,attr" json:"fsWatcherEnabled" default:"true"`
FSWatcherDelayS int `xml:"fsWatcherDelayS,attr" json:"fsWatcherDelayS" default:"10"`
IgnorePerms bool `xml:"ignorePerms,attr" json:"ignorePerms"`
AutoNormalize bool `xml:"autoNormalize,attr" json:"autoNormalize"`
MinDiskFree Size `xml:"minDiskFree" json:"minDiskFree"`
AutoNormalize bool `xml:"autoNormalize,attr" json:"autoNormalize" default:"true"`
MinDiskFree Size `xml:"minDiskFree" json:"minDiskFree" default:"1%"`
Versioning VersioningConfiguration `xml:"versioning" json:"versioning"`
Copiers int `xml:"copiers" json:"copiers"` // This defines how many files are handled concurrently.
PullerMaxPendingKiB int `xml:"pullerMaxPendingKiB" json:"pullerMaxPendingKiB"`
@@ -46,7 +46,7 @@ type FolderConfiguration struct {
IgnoreDelete bool `xml:"ignoreDelete" json:"ignoreDelete"`
ScanProgressIntervalS int `xml:"scanProgressIntervalS" json:"scanProgressIntervalS"` // Set to a negative value to disable. Value of 0 will get replaced with value of 2 (default value)
PullerPauseS int `xml:"pullerPauseS" json:"pullerPauseS"`
MaxConflicts int `xml:"maxConflicts" json:"maxConflicts"`
MaxConflicts int `xml:"maxConflicts" json:"maxConflicts" default:"-1"`
DisableSparseFiles bool `xml:"disableSparseFiles" json:"disableSparseFiles"`
DisableTempIndexes bool `xml:"disableTempIndexes" json:"disableTempIndexes"`
Paused bool `xml:"paused" json:"paused"`
@@ -69,18 +69,15 @@ type FolderDeviceConfiguration struct {
func NewFolderConfiguration(myID protocol.DeviceID, id, label string, fsType fs.FilesystemType, path string) FolderConfiguration {
f := FolderConfiguration{
ID: id,
Label: label,
RescanIntervalS: 3600,
FSWatcherEnabled: true,
FSWatcherDelayS: 10,
MinDiskFree: Size{Value: 1, Unit: "%"},
Devices: []FolderDeviceConfiguration{{DeviceID: myID}},
AutoNormalize: true,
MaxConflicts: -1,
FilesystemType: fsType,
Path: path,
ID: id,
Label: label,
Devices: []FolderDeviceConfiguration{{DeviceID: myID}},
FilesystemType: fsType,
Path: path,
}
util.SetDefaults(&f)
f.prepare()
return f
}

View File

@@ -14,7 +14,7 @@ import (
type OptionsConfiguration struct {
ListenAddresses []string `xml:"listenAddress" json:"listenAddresses" default:"default"`
GlobalAnnServers []string `xml:"globalAnnounceServer" json:"globalAnnounceServers" json:"globalAnnounceServer" default:"default" restart:"true"`
GlobalAnnServers []string `xml:"globalAnnounceServer" json:"globalAnnounceServers" default:"default" restart:"true"`
GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" json:"globalAnnounceEnabled" default:"true" restart:"true"`
LocalAnnEnabled bool `xml:"localAnnounceEnabled" json:"localAnnounceEnabled" default:"true" restart:"true"`
LocalAnnPort int `xml:"localAnnouncePort" json:"localAnnouncePort" default:"21027" restart:"true"`

View File

@@ -72,8 +72,10 @@ func (s Size) String() string {
return fmt.Sprintf("%v %s", s.Value, s.Unit)
}
func (Size) ParseDefault(s string) (interface{}, error) {
return ParseSize(s)
func (s *Size) ParseDefault(str string) error {
sz, err := ParseSize(str)
*s = sz
return err
}
func checkFreeSpace(req Size, usage fs.Usage) error {

View File

@@ -6,7 +6,28 @@
package config
import "testing"
import (
"testing"
"github.com/syncthing/syncthing/lib/util"
)
type TestStruct struct {
Size Size `default:"10%"`
}
func TestSizeDefaults(t *testing.T) {
x := &TestStruct{}
util.SetDefaults(x)
if !x.Size.Percentage() {
t.Error("not percentage")
}
if x.Size.Value != 10 {
t.Error("not ten")
}
}
func TestParseSize(t *testing.T) {
cases := []struct {

161
lib/locations/locations.go Normal file
View File

@@ -0,0 +1,161 @@
// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package locations
import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/syncthing/syncthing/lib/fs"
)
type LocationEnum string
// Use strings as keys to make printout and serialization of the locations map
// more meaningful.
const (
ConfigFile LocationEnum = "config"
CertFile LocationEnum = "certFile"
KeyFile LocationEnum = "keyFile"
HTTPSCertFile LocationEnum = "httpsCertFile"
HTTPSKeyFile LocationEnum = "httpsKeyFile"
Database LocationEnum = "database"
LogFile LocationEnum = "logFile"
CsrfTokens LocationEnum = "csrfTokens"
PanicLog LocationEnum = "panicLog"
AuditLog LocationEnum = "auditLog"
GUIAssets LocationEnum = "GUIAssets"
DefFolder LocationEnum = "defFolder"
)
type BaseDirEnum string
const (
ConfigBaseDir BaseDirEnum = "config"
HomeBaseDir BaseDirEnum = "home"
)
func init() {
err := expandLocations()
if err != nil {
panic(err)
}
}
func SetBaseDir(baseDirName BaseDirEnum, path string) error {
_, ok := baseDirs[baseDirName]
if !ok {
return fmt.Errorf("unknown base dir: %s", baseDirName)
}
baseDirs[baseDirName] = filepath.Clean(path)
return expandLocations()
}
func Get(location LocationEnum) string {
return locations[location]
}
func GetBaseDir(baseDir BaseDirEnum) string {
return baseDirs[baseDir]
}
// Platform dependent directories
var baseDirs = map[BaseDirEnum]string{
ConfigBaseDir: defaultConfigDir(), // Overridden by -home flag
HomeBaseDir: homeDir(), // User's home directory, *not* -home flag
}
// Use the variables from baseDirs here
var locationTemplates = map[LocationEnum]string{
ConfigFile: "${config}/config.xml",
CertFile: "${config}/cert.pem",
KeyFile: "${config}/key.pem",
HTTPSCertFile: "${config}/https-cert.pem",
HTTPSKeyFile: "${config}/https-key.pem",
Database: "${config}/index-v0.14.0.db",
LogFile: "${config}/syncthing.log", // -logfile on Windows
CsrfTokens: "${config}/csrftokens.txt",
PanicLog: "${config}/panic-${timestamp}.log",
AuditLog: "${config}/audit-${timestamp}.log",
GUIAssets: "${config}/gui",
DefFolder: "${home}/Sync",
}
var locations = make(map[LocationEnum]string)
// expandLocations replaces the variables in the locations map with actual
// directory locations.
func expandLocations() error {
newLocations := make(map[LocationEnum]string)
for key, dir := range locationTemplates {
for varName, value := range baseDirs {
dir = strings.Replace(dir, "${"+string(varName)+"}", value, -1)
}
var err error
dir, err = fs.ExpandTilde(dir)
if err != nil {
return err
}
newLocations[key] = filepath.Clean(dir)
}
locations = newLocations
return nil
}
// defaultConfigDir returns the default configuration directory, as figured
// out by various the environment variables present on each platform, or dies
// trying.
func defaultConfigDir() string {
switch runtime.GOOS {
case "windows":
if p := os.Getenv("LocalAppData"); p != "" {
return filepath.Join(p, "Syncthing")
}
return filepath.Join(os.Getenv("AppData"), "Syncthing")
case "darwin":
dir, err := fs.ExpandTilde("~/Library/Application Support/Syncthing")
if err != nil {
panic(err)
}
return dir
default:
if xdgCfg := os.Getenv("XDG_CONFIG_HOME"); xdgCfg != "" {
return filepath.Join(xdgCfg, "syncthing")
}
dir, err := fs.ExpandTilde("~/.config/syncthing")
if err != nil {
panic(err)
}
return dir
}
}
// homeDir returns the user's home directory, or dies trying.
func homeDir() string {
home, err := fs.ExpandTilde("~")
if err != nil {
panic(err)
}
return home
}
func GetTimestamped(key LocationEnum) string {
// We take the roundtrip via "${timestamp}" instead of passing the path
// directly through time.Format() to avoid issues when the path we are
// expanding contains numbers; otherwise for example
// /home/user2006/.../panic-20060102-150405.log would get both instances of
// 2006 replaced by 2015...
tpl := locations[key]
now := time.Now().Format("20060102-150405")
return strings.Replace(tpl, "${timestamp}", now, -1)
}

View File

@@ -15,8 +15,12 @@ import (
"strings"
)
type defaultParser interface {
ParseDefault(string) error
}
// SetDefaults sets default values on a struct, based on the default annotation.
func SetDefaults(data interface{}) error {
func SetDefaults(data interface{}) {
s := reflect.ValueOf(data).Elem()
t := s.Type()
@@ -26,15 +30,22 @@ func SetDefaults(data interface{}) error {
v := tag.Get("default")
if len(v) > 0 {
if parser, ok := f.Interface().(interface {
ParseDefault(string) (interface{}, error)
}); ok {
val, err := parser.ParseDefault(v)
if err != nil {
panic(err)
if f.CanInterface() {
if parser, ok := f.Interface().(defaultParser); ok {
if err := parser.ParseDefault(v); err != nil {
panic(err)
}
continue
}
}
if f.CanAddr() && f.Addr().CanInterface() {
if parser, ok := f.Addr().Interface().(defaultParser); ok {
if err := parser.ParseDefault(v); err != nil {
panic(err)
}
continue
}
f.Set(reflect.ValueOf(val))
continue
}
switch f.Interface().(type) {
@@ -44,14 +55,14 @@ func SetDefaults(data interface{}) error {
case int:
i, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return err
panic(err)
}
f.SetInt(i)
case float64:
i, err := strconv.ParseFloat(v, 64)
if err != nil {
return err
panic(err)
}
f.SetFloat(i)
@@ -68,7 +79,6 @@ func SetDefaults(data interface{}) error {
}
}
}
return nil
}
// CopyMatchingTag copies fields tagged tag:"value" from "from" struct onto "to" struct.

View File

@@ -12,8 +12,9 @@ type Defaulter struct {
Value string
}
func (Defaulter) ParseDefault(v string) (interface{}, error) {
return Defaulter{Value: v}, nil
func (d *Defaulter) ParseDefault(v string) error {
*d = Defaulter{Value: v}
return nil
}
func TestSetDefaults(t *testing.T) {
@@ -37,9 +38,7 @@ func TestSetDefaults(t *testing.T) {
t.Errorf("defaulter failed")
}
if err := SetDefaults(x); err != nil {
t.Error(err)
}
SetDefaults(x)
if x.A != "string" {
t.Error("string failed")