2014-11-16 20:13:20 +00:00
// Copyright (C) 2014 The Syncthing Authors.
2014-09-29 19:43:32 +00:00
//
2015-03-07 20:36:35 +00:00
// 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 http://mozilla.org/MPL/2.0/.
2014-06-01 20:50:14 +00:00
2014-05-15 03:40:17 +00:00
// Package config implements reading and writing of the syncthing configuration file.
2014-05-15 00:18:09 +00:00
package config
2014-03-02 22:58:14 +00:00
import (
"encoding/xml"
2014-05-14 10:58:33 +00:00
"fmt"
2014-10-06 07:25:45 +00:00
"io"
2014-12-29 12:48:26 +00:00
"math/rand"
2014-04-22 09:46:08 +00:00
"os"
2014-10-11 15:55:11 +00:00
"path/filepath"
2014-03-02 22:58:14 +00:00
"reflect"
"sort"
"strconv"
2015-03-07 19:48:53 +00:00
"strings"
2014-04-19 11:33:51 +00:00
2014-10-26 12:15:14 +00:00
"github.com/calmh/logger"
2015-01-13 12:22:56 +00:00
"github.com/syncthing/protocol"
2014-10-11 15:55:11 +00:00
"github.com/syncthing/syncthing/internal/osutil"
2014-11-29 23:17:00 +00:00
"golang.org/x/crypto/bcrypt"
2014-03-02 22:58:14 +00:00
)
2014-05-15 00:18:09 +00:00
var l = logger . DefaultLogger
2015-02-23 08:44:10 +00:00
const CurrentVersion = 9
2014-10-08 11:52:05 +00:00
2014-03-02 22:58:14 +00:00
type Configuration struct {
2015-03-10 22:45:43 +00:00
Version int ` xml:"version,attr" json:"version" `
Folders [ ] FolderConfiguration ` xml:"folder" json:"folders" `
Devices [ ] DeviceConfiguration ` xml:"device" json:"devices" `
GUI GUIConfiguration ` xml:"gui" json:"gui" `
Options OptionsConfiguration ` xml:"options" json:"options" `
IgnoredDevices [ ] protocol . DeviceID ` xml:"ignoredDevice" json:"ignoredDevices" `
2014-12-27 23:12:12 +00:00
XMLName xml . Name ` xml:"configuration" json:"-" `
2014-09-28 12:22:39 +00:00
2014-10-08 11:52:05 +00:00
OriginalVersion int ` xml:"-" json:"-" ` // The version we read from disk, before any conversion
2014-09-28 12:22:39 +00:00
Deprecated_Repositories [ ] FolderConfiguration ` xml:"repository" json:"-" `
Deprecated_Nodes [ ] DeviceConfiguration ` xml:"node" json:"-" `
2014-03-02 22:58:14 +00:00
}
2014-09-28 11:00:38 +00:00
type FolderConfiguration struct {
2015-03-10 22:45:43 +00:00
ID string ` xml:"id,attr" json:"id" `
Path string ` xml:"path,attr" json:"path" `
Devices [ ] FolderDeviceConfiguration ` xml:"device" json:"devices" `
ReadOnly bool ` xml:"ro,attr" json:"readOnly" `
RescanIntervalS int ` xml:"rescanIntervalS,attr" json:"rescanIntervalS" default:"60" `
IgnorePerms bool ` xml:"ignorePerms,attr" json:"ignorePerms" `
2015-03-18 22:54:50 +00:00
AutoNormalize bool ` xml:"autoNormalize,attr" json:"autoNormalize" default:"true" `
2015-03-10 22:45:43 +00:00
Versioning VersioningConfiguration ` xml:"versioning" json:"versioning" `
LenientMtimes bool ` xml:"lenientMtimes" json:"lenientMTimes" `
Copiers int ` xml:"copiers" json:"copiers" default:"1" ` // This defines how many files are handled concurrently.
Pullers int ` xml:"pullers" json:"pullers" default:"16" ` // Defines how many blocks are fetched at the same time, possibly between separate copier routines.
Hashers int ` xml:"hashers" json:"hashers" default:"0" ` // Less than one sets the value to the number of cores. These are CPU bound due to hashing.
Invalid string ` xml:"-" json:"invalid" ` // Set at runtime when there is an error, not saved
2014-10-06 19:43:38 +00:00
2014-09-28 11:00:38 +00:00
deviceIDs [ ] protocol . DeviceID
2014-09-28 12:22:39 +00:00
2014-09-29 18:07:31 +00:00
Deprecated_Directory string ` xml:"directory,omitempty,attr" json:"-" `
Deprecated_Nodes [ ] FolderDeviceConfiguration ` xml:"node" json:"-" `
2014-05-25 18:49:08 +00:00
}
2014-10-11 15:55:11 +00:00
func ( f * FolderConfiguration ) CreateMarker ( ) error {
if ! f . HasMarker ( ) {
marker := filepath . Join ( f . Path , ".stfolder" )
fd , err := os . Create ( marker )
if err != nil {
return err
}
fd . Close ( )
osutil . HideFile ( marker )
}
return nil
}
func ( f * FolderConfiguration ) HasMarker ( ) bool {
_ , err := os . Stat ( filepath . Join ( f . Path , ".stfolder" ) )
if err != nil {
return false
}
return true
}
2014-12-08 15:36:15 +00:00
func ( f * FolderConfiguration ) DeviceIDs ( ) [ ] protocol . DeviceID {
if f . deviceIDs == nil {
for _ , n := range f . Devices {
f . deviceIDs = append ( f . deviceIDs , n . DeviceID )
2014-10-06 07:25:45 +00:00
}
}
2014-12-08 15:36:15 +00:00
return f . deviceIDs
2014-10-06 07:25:45 +00:00
}
2014-05-25 18:49:08 +00:00
type VersioningConfiguration struct {
2015-03-10 22:45:43 +00:00
Type string ` xml:"type,attr" json:"type" `
Params map [ string ] string ` json:"params" `
2014-05-25 18:49:08 +00:00
}
type InternalVersioningConfiguration struct {
Type string ` xml:"type,attr,omitempty" `
Params [ ] InternalParam ` xml:"param" `
}
type InternalParam struct {
Key string ` xml:"key,attr" `
Val string ` xml:"val,attr" `
}
func ( c * VersioningConfiguration ) MarshalXML ( e * xml . Encoder , start xml . StartElement ) error {
var tmp InternalVersioningConfiguration
tmp . Type = c . Type
for k , v := range c . Params {
tmp . Params = append ( tmp . Params , InternalParam { k , v } )
}
return e . EncodeElement ( tmp , start )
}
func ( c * VersioningConfiguration ) UnmarshalXML ( d * xml . Decoder , start xml . StartElement ) error {
var tmp InternalVersioningConfiguration
err := d . DecodeElement ( & tmp , & start )
if err != nil {
return err
}
c . Type = tmp . Type
c . Params = make ( map [ string ] string , len ( tmp . Params ) )
for _ , p := range tmp . Params {
c . Params [ p . Key ] = p . Val
}
return nil
2014-04-08 11:45:18 +00:00
}
2014-09-28 11:00:38 +00:00
type DeviceConfiguration struct {
2015-03-10 22:45:43 +00:00
DeviceID protocol . DeviceID ` xml:"id,attr" json:"deviceID" `
Name string ` xml:"name,attr,omitempty" json:"name" `
Addresses [ ] string ` xml:"address,omitempty" json:"addresses" `
Compression protocol . Compression ` xml:"compression,attr" json:"compression" `
CertName string ` xml:"certName,attr,omitempty" json:"certName" `
Introducer bool ` xml:"introducer,attr" json:"introducer" `
2014-03-02 22:58:14 +00:00
}
2014-09-28 11:00:38 +00:00
type FolderDeviceConfiguration struct {
2015-03-10 22:45:43 +00:00
DeviceID protocol . DeviceID ` xml:"id,attr" json:"deviceID" `
2014-08-16 22:20:21 +00:00
Deprecated_Name string ` xml:"name,attr,omitempty" json:"-" `
Deprecated_Addresses [ ] string ` xml:"address,omitempty" json:"-" `
}
2014-03-02 22:58:14 +00:00
type OptionsConfiguration struct {
2015-03-10 22:45:43 +00:00
ListenAddress [ ] string ` xml:"listenAddress" json:"listenAddress" default:"0.0.0.0:22000" `
GlobalAnnServers [ ] string ` xml:"globalAnnounceServer" json:"globalAnnounceServers" json:"globalAnnounceServer" default:"udp4://announce.syncthing.net:22026, udp6://announce-v6.syncthing.net:22026" `
GlobalAnnEnabled bool ` xml:"globalAnnounceEnabled" json:"globalAnnounceEnabled" default:"true" `
LocalAnnEnabled bool ` xml:"localAnnounceEnabled" json:"localAnnounceEnabled" default:"true" `
LocalAnnPort int ` xml:"localAnnouncePort" json:"localAnnouncePort" default:"21025" `
LocalAnnMCAddr string ` xml:"localAnnounceMCAddr" json:"localAnnounceMCAddr" default:"[ff32::5222]:21026" `
MaxSendKbps int ` xml:"maxSendKbps" json:"maxSendKbps" `
MaxRecvKbps int ` xml:"maxRecvKbps" json:"maxRecvKbps" `
ReconnectIntervalS int ` xml:"reconnectionIntervalS" json:"reconnectionIntervalS" default:"60" `
StartBrowser bool ` xml:"startBrowser" json:"startBrowser" default:"true" `
UPnPEnabled bool ` xml:"upnpEnabled" json:"upnpEnabled" default:"true" `
UPnPLease int ` xml:"upnpLeaseMinutes" json:"upnpLeaseMinutes" default:"0" `
UPnPRenewal int ` xml:"upnpRenewalMinutes" json:"upnpRenewalMinutes" default:"30" `
URAccepted int ` xml:"urAccepted" json:"urAccepted" ` // Accepted usage reporting version; 0 for off (undecided), -1 for off (permanently)
URUniqueID string ` xml:"urUniqueID" json:"urUniqueId" ` // Unique ID for reporting purposes, regenerated when UR is turned on.
RestartOnWakeup bool ` xml:"restartOnWakeup" json:"restartOnWakeup" default:"true" `
AutoUpgradeIntervalH int ` xml:"autoUpgradeIntervalH" json:"autoUpgradeIntervalH" default:"12" ` // 0 for off
KeepTemporariesH int ` xml:"keepTemporariesH" json:"keepTemporariesH" default:"24" ` // 0 for off
CacheIgnoredFiles bool ` xml:"cacheIgnoredFiles" json:"cacheIgnoredFiles" default:"true" `
ProgressUpdateIntervalS int ` xml:"progressUpdateIntervalS" json:"progressUpdateIntervalS" default:"5" `
SymlinksEnabled bool ` xml:"symlinksEnabled" json:"symlinksEnabled" default:"true" `
LimitBandwidthInLan bool ` xml:"limitBandwidthInLan" json:"limitBandwidthInLan" default:"false" `
2014-04-08 13:56:12 +00:00
2014-08-18 21:05:47 +00:00
Deprecated_RescanIntervalS int ` xml:"rescanIntervalS,omitempty" json:"-" `
Deprecated_UREnabled bool ` xml:"urEnabled,omitempty" json:"-" `
Deprecated_URDeclined bool ` xml:"urDeclined,omitempty" json:"-" `
Deprecated_ReadOnly bool ` xml:"readOnly,omitempty" json:"-" `
Deprecated_GUIEnabled bool ` xml:"guiEnabled,omitempty" json:"-" `
Deprecated_GUIAddress string ` xml:"guiAddress,omitempty" json:"-" `
2014-04-08 13:56:12 +00:00
}
type GUIConfiguration struct {
2015-03-10 22:45:43 +00:00
Enabled bool ` xml:"enabled,attr" json:"enabled" default:"true" `
Address string ` xml:"address" json:"address" default:"127.0.0.1:8080" `
User string ` xml:"user,omitempty" json:"user" `
Password string ` xml:"password,omitempty" json:"password" `
UseTLS bool ` xml:"tls,attr" json:"useTLS" `
APIKey string ` xml:"apikey,omitempty" json:"apiKey" `
2014-03-02 22:58:14 +00:00
}
2014-10-06 07:25:45 +00:00
func New ( myID protocol . DeviceID ) Configuration {
var cfg Configuration
2014-10-08 11:52:05 +00:00
cfg . Version = CurrentVersion
cfg . OriginalVersion = CurrentVersion
2014-10-06 07:25:45 +00:00
setDefaults ( & cfg )
setDefaults ( & cfg . Options )
setDefaults ( & cfg . GUI )
cfg . prepare ( myID )
return cfg
}
func ReadXML ( r io . Reader , myID protocol . DeviceID ) ( Configuration , error ) {
var cfg Configuration
setDefaults ( & cfg )
setDefaults ( & cfg . Options )
setDefaults ( & cfg . GUI )
err := xml . NewDecoder ( r ) . Decode ( & cfg )
2014-10-08 11:52:05 +00:00
cfg . OriginalVersion = cfg . Version
2014-10-06 07:25:45 +00:00
cfg . prepare ( myID )
return cfg , err
}
func ( cfg * Configuration ) WriteXML ( w io . Writer ) error {
e := xml . NewEncoder ( w )
e . Indent ( "" , " " )
err := e . Encode ( cfg )
if err != nil {
return err
}
_ , err = w . Write ( [ ] byte ( "\n" ) )
return err
}
2014-09-28 11:00:38 +00:00
func ( cfg * Configuration ) prepare ( myID protocol . DeviceID ) {
2014-03-04 21:29:43 +00:00
fillNilSlices ( & cfg . Options )
2014-03-04 10:25:10 +00:00
2014-12-27 23:12:12 +00:00
// Initialize an empty slices
2014-09-28 11:00:38 +00:00
if cfg . Folders == nil {
cfg . Folders = [ ] FolderConfiguration { }
2014-05-13 02:57:38 +00:00
}
2014-12-27 23:12:12 +00:00
if cfg . IgnoredDevices == nil {
cfg . IgnoredDevices = [ ] protocol . DeviceID { }
}
2014-05-13 02:57:38 +00:00
2014-09-28 11:00:38 +00:00
// Check for missing, bad or duplicate folder ID:s
var seenFolders = map [ string ] * FolderConfiguration { }
2014-05-14 10:58:33 +00:00
var uniqueCounter int
2014-09-28 11:00:38 +00:00
for i := range cfg . Folders {
folder := & cfg . Folders [ i ]
2014-04-27 19:53:27 +00:00
2014-09-28 11:56:35 +00:00
if len ( folder . Path ) == 0 {
2014-09-28 11:00:38 +00:00
folder . Invalid = "no directory configured"
2014-05-13 00:30:04 +00:00
continue
}
2015-02-09 23:56:16 +00:00
// The reason it's done like this:
// C: -> C:\ -> C:\ (issue that this is trying to fix)
// C:\somedir -> C:\somedir\ -> C:\somedir
// C:\somedir\ -> C:\somedir\\ -> C:\somedir
// This way in the tests, we get away without OS specific separators
// in the test configs.
folder . Path = filepath . Dir ( folder . Path + string ( filepath . Separator ) )
2015-01-22 23:22:30 +00:00
2014-09-28 11:00:38 +00:00
if folder . ID == "" {
folder . ID = "default"
2014-03-29 17:53:48 +00:00
}
2014-09-28 11:00:38 +00:00
if seen , ok := seenFolders [ folder . ID ] ; ok {
l . Warnf ( "Multiple folders with ID %q; disabling" , folder . ID )
2014-05-14 10:58:33 +00:00
2014-09-28 11:00:38 +00:00
seen . Invalid = "duplicate folder ID"
if seen . ID == folder . ID {
2014-05-14 10:58:33 +00:00
uniqueCounter ++
2014-09-28 11:00:38 +00:00
seen . ID = fmt . Sprintf ( "%s~%d" , folder . ID , uniqueCounter )
2014-05-14 10:58:33 +00:00
}
2014-09-28 11:00:38 +00:00
folder . Invalid = "duplicate folder ID"
2014-05-14 10:58:33 +00:00
uniqueCounter ++
2014-09-28 11:00:38 +00:00
folder . ID = fmt . Sprintf ( "%s~%d" , folder . ID , uniqueCounter )
2014-04-27 19:53:27 +00:00
} else {
2014-09-28 11:00:38 +00:00
seenFolders [ folder . ID ] = folder
2014-03-29 17:53:48 +00:00
}
}
2014-06-17 21:22:19 +00:00
if cfg . Options . Deprecated_URDeclined {
cfg . Options . URAccepted = - 1
2014-11-27 09:00:07 +00:00
cfg . Options . URUniqueID = ""
2014-06-17 21:22:19 +00:00
}
cfg . Options . Deprecated_URDeclined = false
cfg . Options . Deprecated_UREnabled = false
2015-03-07 19:48:53 +00:00
// Upgrade configuration versions as appropriate
2014-04-08 11:45:18 +00:00
if cfg . Version == 1 {
2014-09-06 12:11:18 +00:00
convertV1V2 ( cfg )
2014-04-08 11:45:18 +00:00
}
2014-08-01 14:48:06 +00:00
if cfg . Version == 2 {
2014-09-06 12:11:18 +00:00
convertV2V3 ( cfg )
2014-08-01 14:48:06 +00:00
}
2014-08-16 22:20:21 +00:00
if cfg . Version == 3 {
2014-09-06 12:11:18 +00:00
convertV3V4 ( cfg )
2014-08-16 22:20:21 +00:00
}
2014-09-28 12:22:39 +00:00
if cfg . Version == 4 {
convertV4V5 ( cfg )
}
2014-10-11 15:55:11 +00:00
if cfg . Version == 5 {
convertV5V6 ( cfg )
}
2014-11-20 21:24:11 +00:00
if cfg . Version == 6 {
convertV6V7 ( cfg )
}
2015-03-07 19:48:53 +00:00
if cfg . Version == 7 {
convertV7V8 ( cfg )
}
2015-02-23 08:44:10 +00:00
if cfg . Version == 8 {
convertV8V9 ( cfg )
}
2014-11-20 21:24:11 +00:00
2014-04-22 09:46:08 +00:00
// Hash old cleartext passwords
2014-04-19 11:33:51 +00:00
if len ( cfg . GUI . Password ) > 0 && cfg . GUI . Password [ 0 ] != '$' {
hash , err := bcrypt . GenerateFromPassword ( [ ] byte ( cfg . GUI . Password ) , 0 )
if err != nil {
2014-08-17 08:28:36 +00:00
l . Warnln ( "bcrypting password:" , err )
2014-04-19 11:33:51 +00:00
} else {
cfg . GUI . Password = string ( hash )
}
}
2014-09-28 11:00:38 +00:00
// Build a list of available devices
existingDevices := make ( map [ protocol . DeviceID ] bool )
for _ , device := range cfg . Devices {
existingDevices [ device . DeviceID ] = true
2014-07-22 21:27:00 +00:00
}
2014-10-06 19:43:38 +00:00
// Ensure this device is present in the config
if ! existingDevices [ myID ] {
2014-08-16 22:20:21 +00:00
myName , _ := os . Hostname ( )
2014-09-28 11:00:38 +00:00
cfg . Devices = append ( cfg . Devices , DeviceConfiguration {
DeviceID : myID ,
2014-09-28 11:05:25 +00:00
Name : myName ,
2014-08-16 22:20:21 +00:00
} )
2014-10-06 19:43:38 +00:00
existingDevices [ myID ] = true
2014-08-16 22:20:21 +00:00
}
2014-10-06 19:28:16 +00:00
2014-09-28 11:00:38 +00:00
sort . Sort ( DeviceConfigurationList ( cfg . Devices ) )
// Ensure that any loose devices are not present in the wrong places
// Ensure that there are no duplicate devices
2014-11-23 18:43:49 +00:00
// Ensure that puller settings are sane
2014-09-28 11:00:38 +00:00
for i := range cfg . Folders {
cfg . Folders [ i ] . Devices = ensureDevicePresent ( cfg . Folders [ i ] . Devices , myID )
cfg . Folders [ i ] . Devices = ensureExistingDevices ( cfg . Folders [ i ] . Devices , existingDevices )
cfg . Folders [ i ] . Devices = ensureNoDuplicates ( cfg . Folders [ i ] . Devices )
2014-11-23 18:43:49 +00:00
if cfg . Folders [ i ] . Copiers == 0 {
cfg . Folders [ i ] . Copiers = 1
}
if cfg . Folders [ i ] . Pullers == 0 {
cfg . Folders [ i ] . Pullers = 16
}
2014-09-28 11:00:38 +00:00
sort . Sort ( FolderDeviceConfigurationList ( cfg . Folders [ i ] . Devices ) )
2014-04-22 09:46:08 +00:00
}
// An empty address list is equivalent to a single "dynamic" entry
2014-09-28 11:00:38 +00:00
for i := range cfg . Devices {
n := & cfg . Devices [ i ]
2014-04-22 09:46:08 +00:00
if len ( n . Addresses ) == 0 || len ( n . Addresses ) == 1 && n . Addresses [ 0 ] == "" {
n . Addresses = [ ] string { "dynamic" }
}
}
2014-11-18 22:57:21 +00:00
cfg . Options . ListenAddress = uniqueStrings ( cfg . Options . ListenAddress )
cfg . Options . GlobalAnnServers = uniqueStrings ( cfg . Options . GlobalAnnServers )
2014-12-29 12:48:26 +00:00
if cfg . GUI . APIKey == "" {
cfg . GUI . APIKey = randomString ( 32 )
}
2014-09-06 12:11:18 +00:00
}
2014-09-20 16:42:09 +00:00
// ChangeRequiresRestart returns true if updating the configuration requires a
// complete restart.
func ChangeRequiresRestart ( from , to Configuration ) bool {
2014-09-28 11:00:38 +00:00
// Adding, removing or changing folders requires restart
2014-10-06 19:28:16 +00:00
if ! reflect . DeepEqual ( from . Folders , to . Folders ) {
2014-09-20 16:42:09 +00:00
return true
}
2014-10-06 19:28:16 +00:00
// Removing a device requres restart
toDevs := make ( map [ protocol . DeviceID ] bool , len ( from . Devices ) )
for _ , dev := range to . Devices {
toDevs [ dev . DeviceID ] = true
}
for _ , dev := range from . Devices {
if _ , ok := toDevs [ dev . DeviceID ] ; ! ok {
2014-09-20 16:42:09 +00:00
return true
}
}
2014-11-27 09:00:07 +00:00
// Changing usage reporting to on or off does not require a restart.
to . Options . URAccepted = from . Options . URAccepted
to . Options . URUniqueID = from . Options . URUniqueID
2014-09-20 16:42:09 +00:00
// All of the generic options require restart
if ! reflect . DeepEqual ( from . Options , to . Options ) || ! reflect . DeepEqual ( from . GUI , to . GUI ) {
return true
}
return false
}
2015-02-23 08:44:10 +00:00
func convertV8V9 ( cfg * Configuration ) {
// Compression is interpreted and serialized differently, but no enforced
// changes. Still need a new version number since the compression stuff
// isn't understandable by earlier versions.
cfg . Version = 9
}
2015-03-07 19:48:53 +00:00
func convertV7V8 ( cfg * Configuration ) {
// Add IPv6 announce server
if len ( cfg . Options . GlobalAnnServers ) == 1 && cfg . Options . GlobalAnnServers [ 0 ] == "udp4://announce.syncthing.net:22026" {
cfg . Options . GlobalAnnServers = append ( cfg . Options . GlobalAnnServers , "udp6://announce-v6.syncthing.net:22026" )
}
cfg . Version = 8
}
2014-11-20 21:24:11 +00:00
func convertV6V7 ( cfg * Configuration ) {
// Migrate announce server addresses to the new URL based format
for i := range cfg . Options . GlobalAnnServers {
cfg . Options . GlobalAnnServers [ i ] = "udp4://" + cfg . Options . GlobalAnnServers [ i ]
}
cfg . Version = 7
}
2014-10-11 15:55:11 +00:00
func convertV5V6 ( cfg * Configuration ) {
// Added ".stfolder" file at folder roots to identify mount issues
// Doesn't affect the config itself, but uses config migrations to identify
// the migration point.
2014-10-12 13:09:25 +00:00
for _ , folder := range Wrap ( "" , * cfg ) . Folders ( ) {
2014-10-15 09:20:40 +00:00
// Best attempt, if it fails, it fails, the user will have to fix
// it up manually, as the repo will not get started.
folder . CreateMarker ( )
2014-10-11 15:55:11 +00:00
}
cfg . Version = 6
}
2014-09-28 12:22:39 +00:00
func convertV4V5 ( cfg * Configuration ) {
// Renamed a bunch of fields in the structs.
if cfg . Deprecated_Nodes == nil {
cfg . Deprecated_Nodes = [ ] DeviceConfiguration { }
}
if cfg . Deprecated_Repositories == nil {
cfg . Deprecated_Repositories = [ ] FolderConfiguration { }
}
cfg . Devices = cfg . Deprecated_Nodes
cfg . Folders = cfg . Deprecated_Repositories
for i := range cfg . Folders {
2014-09-29 18:07:31 +00:00
cfg . Folders [ i ] . Path = cfg . Folders [ i ] . Deprecated_Directory
cfg . Folders [ i ] . Deprecated_Directory = ""
cfg . Folders [ i ] . Devices = cfg . Folders [ i ] . Deprecated_Nodes
cfg . Folders [ i ] . Deprecated_Nodes = nil
2014-09-28 12:22:39 +00:00
}
cfg . Deprecated_Nodes = nil
cfg . Deprecated_Repositories = nil
cfg . Version = 5
}
2014-08-16 22:20:21 +00:00
func convertV3V4 ( cfg * Configuration ) {
2014-09-28 11:00:38 +00:00
// In previous versions, rescan interval was common for each folder.
2014-08-18 21:05:47 +00:00
// From now, it can be set independently. We have to make sure, that after upgrade
2014-09-28 11:00:38 +00:00
// the individual rescan interval will be defined for every existing folder.
2014-09-28 12:22:39 +00:00
for i := range cfg . Deprecated_Repositories {
cfg . Deprecated_Repositories [ i ] . RescanIntervalS = cfg . Options . Deprecated_RescanIntervalS
2014-08-18 21:05:47 +00:00
}
cfg . Options . Deprecated_RescanIntervalS = 0
2014-09-28 11:00:38 +00:00
// In previous versions, folders held full device configurations.
// Since that's the only place where device configs were in V1, we still have
2014-08-16 22:20:21 +00:00
// to define the deprecated fields to be able to upgrade from V1 to V4.
2014-09-28 12:22:39 +00:00
for i , folder := range cfg . Deprecated_Repositories {
2014-08-18 21:05:47 +00:00
2014-09-29 18:07:31 +00:00
for j := range folder . Deprecated_Nodes {
rncfg := cfg . Deprecated_Repositories [ i ] . Deprecated_Nodes [ j ]
2014-08-16 22:20:21 +00:00
rncfg . Deprecated_Name = ""
rncfg . Deprecated_Addresses = nil
}
}
cfg . Version = 4
}
2014-08-01 14:48:06 +00:00
func convertV2V3 ( cfg * Configuration ) {
// In previous versions, compression was always on. When upgrading, enable
2014-09-28 11:00:38 +00:00
// compression on all existing new. New devices will get compression on by
2014-08-01 14:48:06 +00:00
// default by the GUI.
2014-09-28 12:22:39 +00:00
for i := range cfg . Deprecated_Nodes {
2015-02-23 08:44:10 +00:00
cfg . Deprecated_Nodes [ i ] . Compression = protocol . CompressMetadata
2014-08-01 14:48:06 +00:00
}
2014-08-02 06:37:10 +00:00
// The global discovery format and port number changed in v0.9. Having the
// default announce server but old port number is guaranteed to be legacy.
2014-11-18 22:57:21 +00:00
if len ( cfg . Options . GlobalAnnServers ) == 1 && cfg . Options . GlobalAnnServers [ 0 ] == "announce.syncthing.net:22025" {
cfg . Options . GlobalAnnServers = [ ] string { "announce.syncthing.net:22026" }
2014-08-02 06:37:10 +00:00
}
2014-08-01 14:48:06 +00:00
cfg . Version = 3
}
2014-04-08 11:45:18 +00:00
func convertV1V2 ( cfg * Configuration ) {
2014-09-28 11:00:38 +00:00
// Collect the list of devices.
2014-09-28 11:39:39 +00:00
// Replace device configs inside folders with only a reference to the
// device ID. Set all folders to read only if the global read only flag is
// set.
2014-09-28 11:00:38 +00:00
var devices = map [ string ] FolderDeviceConfiguration { }
2014-09-28 12:22:39 +00:00
for i , folder := range cfg . Deprecated_Repositories {
cfg . Deprecated_Repositories [ i ] . ReadOnly = cfg . Options . Deprecated_ReadOnly
2014-09-29 18:07:31 +00:00
for j , device := range folder . Deprecated_Nodes {
2014-09-28 11:00:38 +00:00
id := device . DeviceID . String ( )
if _ , ok := devices [ id ] ; ! ok {
devices [ id ] = device
2014-04-08 11:45:18 +00:00
}
2014-09-29 18:07:31 +00:00
cfg . Deprecated_Repositories [ i ] . Deprecated_Nodes [ j ] = FolderDeviceConfiguration { DeviceID : device . DeviceID }
2014-04-08 11:45:18 +00:00
}
}
2014-04-08 13:56:12 +00:00
cfg . Options . Deprecated_ReadOnly = false
2014-04-08 11:45:18 +00:00
2014-09-28 11:00:38 +00:00
// Set and sort the list of devices.
for _ , device := range devices {
2014-09-28 12:22:39 +00:00
cfg . Deprecated_Nodes = append ( cfg . Deprecated_Nodes , DeviceConfiguration {
2014-09-28 11:05:25 +00:00
DeviceID : device . DeviceID ,
2014-09-28 11:00:38 +00:00
Name : device . Deprecated_Name ,
Addresses : device . Deprecated_Addresses ,
2014-08-16 22:20:21 +00:00
} )
2014-04-08 11:45:18 +00:00
}
2014-09-28 12:22:39 +00:00
sort . Sort ( DeviceConfigurationList ( cfg . Deprecated_Nodes ) )
2014-04-08 11:45:18 +00:00
2014-04-08 13:56:12 +00:00
// GUI
cfg . GUI . Address = cfg . Options . Deprecated_GUIAddress
cfg . GUI . Enabled = cfg . Options . Deprecated_GUIEnabled
cfg . Options . Deprecated_GUIEnabled = false
cfg . Options . Deprecated_GUIAddress = ""
2014-04-08 11:45:18 +00:00
cfg . Version = 2
}
2014-10-06 19:28:16 +00:00
2014-10-06 07:25:45 +00:00
func setDefaults ( data interface { } ) error {
s := reflect . ValueOf ( data ) . Elem ( )
t := s . Type ( )
2014-04-08 11:45:18 +00:00
2014-10-06 07:25:45 +00:00
for i := 0 ; i < s . NumField ( ) ; i ++ {
f := s . Field ( i )
tag := t . Field ( i ) . Tag
2014-03-02 22:58:14 +00:00
2014-10-06 07:25:45 +00:00
v := tag . Get ( "default" )
if len ( v ) > 0 {
switch f . Interface ( ) . ( type ) {
case string :
f . SetString ( v )
2014-03-02 22:58:14 +00:00
2014-10-06 07:25:45 +00:00
case int :
i , err := strconv . ParseInt ( v , 10 , 64 )
if err != nil {
return err
}
f . SetInt ( i )
2014-08-16 22:20:21 +00:00
2014-10-06 07:25:45 +00:00
case bool :
f . SetBool ( v == "true" )
case [ ] string :
// We don't do anything with string slices here. Any default
// we set will be appended to by the XML decoder, so we fill
// those after decoding.
default :
panic ( f . Type ( ) )
}
}
}
return nil
2014-08-16 22:20:21 +00:00
}
2014-10-06 07:25:45 +00:00
// fillNilSlices sets default value on slices that are still nil.
func fillNilSlices ( data interface { } ) error {
s := reflect . ValueOf ( data ) . Elem ( )
t := s . Type ( )
for i := 0 ; i < s . NumField ( ) ; i ++ {
f := s . Field ( i )
tag := t . Field ( i ) . Tag
v := tag . Get ( "default" )
if len ( v ) > 0 {
switch f . Interface ( ) . ( type ) {
case [ ] string :
if f . IsNil ( ) {
2015-03-07 19:48:53 +00:00
// Treat the default as a comma separated slice
vs := strings . Split ( v , "," )
for i := range vs {
vs [ i ] = strings . TrimSpace ( vs [ i ] )
}
rv := reflect . MakeSlice ( reflect . TypeOf ( [ ] string { } ) , len ( vs ) , len ( vs ) )
for i , v := range vs {
rv . Index ( i ) . SetString ( v )
}
2014-10-06 07:25:45 +00:00
f . Set ( rv )
}
}
}
}
return nil
2014-08-16 22:20:21 +00:00
}
2014-10-06 07:25:45 +00:00
func uniqueStrings ( ss [ ] string ) [ ] string {
var m = make ( map [ string ] bool , len ( ss ) )
for _ , s := range ss {
m [ s ] = true
}
var us = make ( [ ] string , 0 , len ( m ) )
for k := range m {
us = append ( us , k )
}
2015-03-07 20:05:30 +00:00
sort . Strings ( us )
2014-10-06 07:25:45 +00:00
return us
2014-08-16 22:20:21 +00:00
}
2014-09-28 11:00:38 +00:00
func ensureDevicePresent ( devices [ ] FolderDeviceConfiguration , myID protocol . DeviceID ) [ ] FolderDeviceConfiguration {
for _ , device := range devices {
if device . DeviceID . Equals ( myID ) {
return devices
2014-03-02 22:58:14 +00:00
}
}
2014-09-28 11:00:38 +00:00
devices = append ( devices , FolderDeviceConfiguration {
DeviceID : myID ,
2014-07-27 23:20:36 +00:00
} )
2014-03-02 22:58:14 +00:00
2014-09-28 11:00:38 +00:00
return devices
2014-03-02 22:58:14 +00:00
}
2014-07-22 21:27:00 +00:00
2014-09-28 11:00:38 +00:00
func ensureExistingDevices ( devices [ ] FolderDeviceConfiguration , existingDevices map [ protocol . DeviceID ] bool ) [ ] FolderDeviceConfiguration {
count := len ( devices )
2014-07-22 21:27:00 +00:00
i := 0
2014-07-27 23:08:15 +00:00
loop :
for i < count {
2014-09-28 11:00:38 +00:00
if _ , ok := existingDevices [ devices [ i ] . DeviceID ] ; ! ok {
devices [ i ] = devices [ count - 1 ]
2014-07-27 23:08:15 +00:00
count --
continue loop
2014-07-22 21:27:00 +00:00
}
2014-07-27 23:08:15 +00:00
i ++
2014-07-22 21:27:00 +00:00
}
2014-09-28 11:00:38 +00:00
return devices [ 0 : count ]
2014-07-22 21:27:00 +00:00
}
2014-07-27 23:15:16 +00:00
2014-09-28 11:00:38 +00:00
func ensureNoDuplicates ( devices [ ] FolderDeviceConfiguration ) [ ] FolderDeviceConfiguration {
count := len ( devices )
2014-07-27 23:15:16 +00:00
i := 0
2014-09-28 11:00:38 +00:00
seenDevices := make ( map [ protocol . DeviceID ] bool )
2014-07-27 23:15:16 +00:00
loop :
for i < count {
2014-09-28 11:00:38 +00:00
id := devices [ i ] . DeviceID
if _ , ok := seenDevices [ id ] ; ok {
devices [ i ] = devices [ count - 1 ]
2014-07-27 23:15:16 +00:00
count --
continue loop
}
2014-09-28 11:00:38 +00:00
seenDevices [ id ] = true
2014-07-27 23:15:16 +00:00
i ++
}
2014-09-28 11:00:38 +00:00
return devices [ 0 : count ]
2014-07-27 23:15:16 +00:00
}
2014-10-06 07:25:45 +00:00
type DeviceConfigurationList [ ] DeviceConfiguration
func ( l DeviceConfigurationList ) Less ( a , b int ) bool {
return l [ a ] . DeviceID . Compare ( l [ b ] . DeviceID ) == - 1
}
func ( l DeviceConfigurationList ) Swap ( a , b int ) {
l [ a ] , l [ b ] = l [ b ] , l [ a ]
}
func ( l DeviceConfigurationList ) Len ( ) int {
return len ( l )
}
type FolderDeviceConfigurationList [ ] FolderDeviceConfiguration
func ( l FolderDeviceConfigurationList ) Less ( a , b int ) bool {
return l [ a ] . DeviceID . Compare ( l [ b ] . DeviceID ) == - 1
}
func ( l FolderDeviceConfigurationList ) Swap ( a , b int ) {
l [ a ] , l [ b ] = l [ b ] , l [ a ]
}
func ( l FolderDeviceConfigurationList ) Len ( ) int {
return len ( l )
}
2014-12-29 12:48:26 +00:00
// randomCharset contains the characters that can make up a randomString().
const randomCharset = "01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
// randomString returns a string of random characters (taken from
// randomCharset) of the specified length.
func randomString ( l int ) string {
bs := make ( [ ] byte , l )
for i := range bs {
bs [ i ] = randomCharset [ rand . Intn ( len ( randomCharset ) ) ]
}
return string ( bs )
}