Browse Source

Merge pull request #645 from AudriusButkevicius/cfg

Allow saving config from anywhere
Audrius Butkevicius 11 years ago
parent
commit
78c6a68db9

+ 2 - 1
cmd/syncthing/gui.go

@@ -361,8 +361,9 @@ func restPostConfig(m *model.Model, w http.ResponseWriter, r *http.Request) {
 
 		// Activate and save
 
+		newCfg.Location = cfg.Location
+		newCfg.Save()
 		cfg = newCfg
-		saveConfig()
 	}
 }
 

+ 3 - 45
cmd/syncthing/main.go

@@ -33,7 +33,6 @@ import (
 	"github.com/syncthing/syncthing/files"
 	"github.com/syncthing/syncthing/logger"
 	"github.com/syncthing/syncthing/model"
-	"github.com/syncthing/syncthing/osutil"
 	"github.com/syncthing/syncthing/protocol"
 	"github.com/syncthing/syncthing/upgrade"
 	"github.com/syncthing/syncthing/upnp"
@@ -310,21 +309,14 @@ func syncthingMain() {
 	// Prepare to be able to save configuration
 
 	cfgFile := filepath.Join(confDir, "config.xml")
-	go saveConfigLoop(cfgFile)
 
 	var myName string
 
 	// Load the configuration file, if it exists.
 	// If it does not, create a template.
 
-	cf, err := os.Open(cfgFile)
+	cfg, err = config.Load(cfgFile, myID)
 	if err == nil {
-		// Read config.xml
-		cfg, err = config.Load(cf, myID)
-		if err != nil {
-			l.Fatalln(err)
-		}
-		cf.Close()
 		myCfg := cfg.GetNodeConfiguration(myID)
 		if myCfg == nil || myCfg.Name == "" {
 			myName, _ = os.Hostname()
@@ -336,7 +328,7 @@ func syncthingMain() {
 		myName, _ = os.Hostname()
 		defaultRepo := filepath.Join(getHomeDir(), "Sync")
 
-		cfg, err = config.Load(nil, myID)
+		cfg = config.New(cfgFile, myID)
 		cfg.Repositories = []config.RepositoryConfiguration{
 			{
 				ID:              "default",
@@ -361,7 +353,7 @@ func syncthingMain() {
 		l.FatalErr(err)
 		cfg.Options.ListenAddress = []string{fmt.Sprintf("0.0.0.0:%d", port)}
 
-		saveConfig()
+		cfg.Save()
 		l.Infof("Edit %s to taste or use the GUI\n", cfgFile)
 	}
 
@@ -745,40 +737,6 @@ func shutdown() {
 	stop <- exitSuccess
 }
 
-var saveConfigCh = make(chan struct{})
-
-func saveConfigLoop(cfgFile string) {
-	for _ = range saveConfigCh {
-		fd, err := os.Create(cfgFile + ".tmp")
-		if err != nil {
-			l.Warnln("Saving config:", err)
-			continue
-		}
-
-		err = config.Save(fd, cfg)
-		if err != nil {
-			l.Warnln("Saving config:", err)
-			fd.Close()
-			continue
-		}
-
-		err = fd.Close()
-		if err != nil {
-			l.Warnln("Saving config:", err)
-			continue
-		}
-
-		err = osutil.Rename(cfgFile+".tmp", cfgFile)
-		if err != nil {
-			l.Warnln("Saving config:", err)
-		}
-	}
-}
-
-func saveConfig() {
-	saveConfigCh <- struct{}{}
-}
-
 func listenConnect(myID protocol.NodeID, m *model.Model, tlsCfg *tls.Config) {
 	var conns = make(chan *tls.Conn)
 

+ 67 - 20
config/config.go

@@ -8,7 +8,6 @@ package config
 import (
 	"encoding/xml"
 	"fmt"
-	"io"
 	"os"
 	"reflect"
 	"sort"
@@ -16,12 +15,14 @@ import (
 
 	"code.google.com/p/go.crypto/bcrypt"
 	"github.com/syncthing/syncthing/logger"
+	"github.com/syncthing/syncthing/osutil"
 	"github.com/syncthing/syncthing/protocol"
 )
 
 var l = logger.DefaultLogger
 
 type Configuration struct {
+	Location     string                    `xml:"-" json:"-"`
 	Version      int                       `xml:"version,attr" default:"3"`
 	Repositories []RepositoryConfiguration `xml:"repository"`
 	Nodes        []NodeConfiguration       `xml:"node"`
@@ -227,14 +228,38 @@ func fillNilSlices(data interface{}) error {
 	return nil
 }
 
-func Save(wr io.Writer, cfg Configuration) error {
-	e := xml.NewEncoder(wr)
+func (cfg *Configuration) Save() error {
+	fd, err := os.Create(cfg.Location + ".tmp")
+	if err != nil {
+		l.Warnln("Saving config:", err)
+		return err
+	}
+
+	e := xml.NewEncoder(fd)
 	e.Indent("", "    ")
-	err := e.Encode(cfg)
+	err = e.Encode(cfg)
+	if err != nil {
+		fd.Close()
+		return err
+	}
+	_, err = fd.Write([]byte("\n"))
+
+	if err != nil {
+		l.Warnln("Saving config:", err)
+		fd.Close()
+		return err
+	}
+
+	err = fd.Close()
 	if err != nil {
+		l.Warnln("Saving config:", err)
 		return err
 	}
-	_, err = wr.Write([]byte("\n"))
+
+	err = osutil.Rename(cfg.Location+".tmp", cfg.Location)
+	if err != nil {
+		l.Warnln("Saving config:", err)
+	}
 	return err
 }
 
@@ -252,18 +277,7 @@ func uniqueStrings(ss []string) []string {
 	return us
 }
 
-func Load(rd io.Reader, myID protocol.NodeID) (Configuration, error) {
-	var cfg Configuration
-
-	setDefaults(&cfg)
-	setDefaults(&cfg.Options)
-	setDefaults(&cfg.GUI)
-
-	var err error
-	if rd != nil {
-		err = xml.NewDecoder(rd).Decode(&cfg)
-	}
-
+func (cfg *Configuration) prepare(myID protocol.NodeID) {
 	fillNilSlices(&cfg.Options)
 
 	cfg.Options.ListenAddress = uniqueStrings(cfg.Options.ListenAddress)
@@ -312,17 +326,17 @@ func Load(rd io.Reader, myID protocol.NodeID) (Configuration, error) {
 
 	// Upgrade to v2 configuration if appropriate
 	if cfg.Version == 1 {
-		convertV1V2(&cfg)
+		convertV1V2(cfg)
 	}
 
 	// Upgrade to v3 configuration if appropriate
 	if cfg.Version == 2 {
-		convertV2V3(&cfg)
+		convertV2V3(cfg)
 	}
 
 	// Upgrade to v4 configuration if appropriate
 	if cfg.Version == 3 {
-		convertV3V4(&cfg)
+		convertV3V4(cfg)
 	}
 
 	// Hash old cleartext passwords
@@ -368,6 +382,39 @@ func Load(rd io.Reader, myID protocol.NodeID) (Configuration, error) {
 			n.Addresses = []string{"dynamic"}
 		}
 	}
+}
+
+func New(location string, myID protocol.NodeID) Configuration {
+	var cfg Configuration
+
+	cfg.Location = location
+
+	setDefaults(&cfg)
+	setDefaults(&cfg.Options)
+	setDefaults(&cfg.GUI)
+
+	cfg.prepare(myID)
+
+	return cfg
+}
+
+func Load(location string, myID protocol.NodeID) (Configuration, error) {
+	var cfg Configuration
+
+	cfg.Location = location
+
+	setDefaults(&cfg)
+	setDefaults(&cfg.Options)
+	setDefaults(&cfg.GUI)
+
+	fd, err := os.Open(location)
+	if err != nil {
+		return Configuration{}, err
+	}
+	err = xml.NewDecoder(fd).Decode(&cfg)
+	fd.Close()
+
+	cfg.prepare(myID)
 
 	return cfg, err
 }

+ 72 - 157
config/config_test.go

@@ -5,8 +5,6 @@
 package config
 
 import (
-	"bytes"
-	"io"
 	"os"
 	"reflect"
 	"testing"
@@ -40,10 +38,7 @@ func TestDefaultValues(t *testing.T) {
 		UPnPRenewal:        30,
 	}
 
-	cfg, err := Load(bytes.NewReader(nil), node1)
-	if err != io.EOF {
-		t.Error(err)
-	}
+	cfg := New("test", node1)
 
 	if !reflect.DeepEqual(cfg.Options, expected) {
 		t.Errorf("Default config differs;\n  E: %#v\n  A: %#v", expected, cfg.Options)
@@ -51,84 +46,8 @@ func TestDefaultValues(t *testing.T) {
 }
 
 func TestNodeConfig(t *testing.T) {
-	v1data := []byte(`
-<configuration version="1">
-    <repository id="test" directory="~/Sync">
-        <node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ" name="node one">
-            <address>a</address>
-        </node>
-        <node id="P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ" name="node two">
-            <address>b</address>
-        </node>
-        <node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ" name="node one">
-            <address>a</address>
-        </node>
-        <node id="P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ" name="node two">
-            <address>b</address>
-        </node>
-    </repository>
-    <options>
-        <readOnly>true</readOnly>
-        <rescanIntervalS>600</rescanIntervalS>
-    </options>
-</configuration>
-`)
-
-	v2data := []byte(`
-<configuration version="2">
-    <repository id="test" directory="~/Sync" ro="true">
-        <node id="P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ"/>
-        <node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ"/>
-        <node id="C4YBIESWDUAIGU62GOSRXCRAAJDWVE3TKCPMURZE2LH5QHAF576A"/>
-        <node id="P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ"/>
-        <node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ"/>
-        <node id="C4YBIESWDUAIGU62GOSRXCRAAJDWVE3TKCPMURZE2LH5QHAF576A"/>
-    </repository>
-    <node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ" name="node one">
-        <address>a</address>
-    </node>
-    <node id="P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ" name="node two">
-        <address>b</address>
-    </node>
-    <options>
-        <rescanIntervalS>600</rescanIntervalS>
-    </options>
-</configuration>
-`)
-
-	v3data := []byte(`
-<configuration version="3">
-    <repository id="test" directory="~/Sync" ro="true" ignorePerms="false">
-        <node id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR" compression="false"></node>
-        <node id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2" compression="false"></node>
-    </repository>
-    <node id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR" name="node one" compression="true">
-        <address>a</address>
-    </node>
-    <node id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2" name="node two" compression="true">
-        <address>b</address>
-    </node>
-    <options>
-        <rescanIntervalS>600</rescanIntervalS>
-    </options>
-</configuration>`)
-
-	v4data := []byte(`
-<configuration version="4">
-    <repository id="test" directory="~/Sync" ro="true" ignorePerms="false" rescanIntervalS="600">
-        <node id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR"></node>
-        <node id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"></node>
-    </repository>
-    <node id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR" name="node one" compression="true">
-        <address>a</address>
-    </node>
-    <node id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2" name="node two" compression="true">
-        <address>b</address>
-    </node>
-</configuration>`)
-
-	for i, data := range [][]byte{v1data, v2data, v3data, v4data} {
-		cfg, err := Load(bytes.NewReader(data), node1)
+	for i, ver := range []string{"v1", "v2", "v3", "v4"} {
+		cfg, err := Load("testdata/"+ver+".xml", node1)
 		if err != nil {
 			t.Error(err)
 		}
@@ -181,14 +100,7 @@ func TestNodeConfig(t *testing.T) {
 }
 
 func TestNoListenAddress(t *testing.T) {
-	data := []byte(`<configuration version="1">
-    <options>
-        <listenAddress></listenAddress>
-    </options>
-</configuration>
-`)
-
-	cfg, err := Load(bytes.NewReader(data), node1)
+	cfg, err := Load("testdata/nolistenaddress.xml", node1)
 	if err != nil {
 		t.Error(err)
 	}
@@ -200,26 +112,6 @@ func TestNoListenAddress(t *testing.T) {
 }
 
 func TestOverriddenValues(t *testing.T) {
-	data := []byte(`<configuration version="2">
-    <options>
-       <listenAddress>:23000</listenAddress>
-        <allowDelete>false</allowDelete>
-        <globalAnnounceServer>syncthing.nym.se:22026</globalAnnounceServer>
-        <globalAnnounceEnabled>false</globalAnnounceEnabled>
-        <localAnnounceEnabled>false</localAnnounceEnabled>
-        <localAnnouncePort>42123</localAnnouncePort>
-        <localAnnounceMCAddr>quux:3232</localAnnounceMCAddr>
-        <parallelRequests>32</parallelRequests>
-        <maxSendKbps>1234</maxSendKbps>
-        <reconnectionIntervalS>6000</reconnectionIntervalS>
-        <startBrowser>false</startBrowser>
-        <upnpEnabled>false</upnpEnabled>
-        <upnpLeaseMinutes>60</upnpLeaseMinutes>
-        <upnpRenewalMinutes>15</upnpRenewalMinutes>
-    </options>
-</configuration>
-`)
-
 	expected := OptionsConfiguration{
 		ListenAddress:      []string{":23000"},
 		GlobalAnnServer:    "syncthing.nym.se:22026",
@@ -236,7 +128,7 @@ func TestOverriddenValues(t *testing.T) {
 		UPnPRenewal:        15,
 	}
 
-	cfg, err := Load(bytes.NewReader(data), node1)
+	cfg, err := Load("testdata/overridenvalues.xml", node1)
 	if err != nil {
 		t.Error(err)
 	}
@@ -247,19 +139,6 @@ func TestOverriddenValues(t *testing.T) {
 }
 
 func TestNodeAddressesDynamic(t *testing.T) {
-	data := []byte(`
-<configuration version="2">
-    <node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ">
-        <address></address>
-    </node>
-    <node id="GYRZZQBIRNPV4T7TC52WEQYJ3TFDQW6MWDFLMU4SSSU6EMFBK2VA">
-    </node>
-    <node id="LGFPDIT7SKNNJVJZA4FC7QNCRKCE753K72BW5QD2FOZ7FRFEP57Q">
-        <address>dynamic</address>
-    </node>
-</configuration>
-`)
-
 	name, _ := os.Hostname()
 	expected := []NodeConfiguration{
 		{
@@ -284,7 +163,7 @@ func TestNodeAddressesDynamic(t *testing.T) {
 		},
 	}
 
-	cfg, err := Load(bytes.NewReader(data), node4)
+	cfg, err := Load("testdata/nodeaddressesdynamic.xml", node4)
 	if err != nil {
 		t.Error(err)
 	}
@@ -295,23 +174,6 @@ func TestNodeAddressesDynamic(t *testing.T) {
 }
 
 func TestNodeAddressesStatic(t *testing.T) {
-	data := []byte(`
-<configuration version="3">
-    <node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ">
-        <address>192.0.2.1</address>
-        <address>192.0.2.2</address>
-    </node>
-    <node id="GYRZZQBIRNPV4T7TC52WEQYJ3TFDQW6MWDFLMU4SSSU6EMFBK2VA">
-        <address>192.0.2.3:6070</address>
-        <address>[2001:db8::42]:4242</address>
-    </node>
-    <node id="LGFPDIT7SKNNJVJZA4FC7QNCRKCE753K72BW5QD2FOZ7FRFEP57Q">
-        <address>[2001:db8::44]:4444</address>
-        <address>192.0.2.4:6090</address>
-    </node>
-</configuration>
-`)
-
 	name, _ := os.Hostname()
 	expected := []NodeConfiguration{
 		{
@@ -333,7 +195,7 @@ func TestNodeAddressesStatic(t *testing.T) {
 		},
 	}
 
-	cfg, err := Load(bytes.NewReader(data), node4)
+	cfg, err := Load("testdata/nodeaddressesstatic.xml", node4)
 	if err != nil {
 		t.Error(err)
 	}
@@ -344,18 +206,7 @@ func TestNodeAddressesStatic(t *testing.T) {
 }
 
 func TestVersioningConfig(t *testing.T) {
-	data := []byte(`
-		<configuration version="2">
-			<repository id="test" directory="~/Sync" ro="true">
-				<versioning type="simple">
-					<param key="foo" val="bar"/>
-					<param key="baz" val="quux"/>
-				</versioning>
-			</repository>
-		</configuration>
-		`)
-
-	cfg, err := Load(bytes.NewReader(data), node4)
+	cfg, err := Load("testdata/versioningconfig.xml", node4)
 	if err != nil {
 		t.Error(err)
 	}
@@ -376,3 +227,67 @@ func TestVersioningConfig(t *testing.T) {
 		t.Errorf("vc.Params differ;\n  E: %#v\n  A: %#v", expected, vc.Params)
 	}
 }
+
+func TestNewSaveLoad(t *testing.T) {
+	path := "testdata/temp.xml"
+	os.Remove(path)
+
+	exists := func(path string) bool {
+		_, err := os.Stat(path)
+		return err == nil
+	}
+
+	cfg := New(path, node1)
+
+	// To make the equality pass later
+	cfg.XMLName.Local = "configuration"
+
+	if exists(path) {
+		t.Error(path, "exists")
+	}
+
+	err := cfg.Save()
+	if err != nil {
+		t.Error(err)
+	}
+	if !exists(path) {
+		t.Error(path, "does not exist")
+	}
+
+	cfg2, err := Load(path, node1)
+	if err != nil {
+		t.Error(err)
+	}
+
+	if !reflect.DeepEqual(cfg, cfg2) {
+		t.Errorf("Configs are not equal;\n  E:  %#v\n  A:  %#v", cfg, cfg2)
+	}
+
+	cfg.GUI.User = "test"
+	cfg.Save()
+
+	cfg2, err = Load(path, node1)
+	if err != nil {
+		t.Error(err)
+	}
+
+	if cfg2.GUI.User != "test" || !reflect.DeepEqual(cfg, cfg2) {
+		t.Errorf("Configs are not equal;\n  E:  %#v\n  A:  %#v", cfg, cfg2)
+	}
+
+	os.Remove(path)
+}
+
+func TestPrepare(t *testing.T) {
+	var cfg Configuration
+
+	if cfg.Repositories != nil || cfg.Nodes != nil || cfg.Options.ListenAddress != nil {
+		t.Error("Expected nil")
+	}
+
+	cfg.prepare(node1)
+
+	if cfg.Repositories == nil || cfg.Nodes == nil || cfg.Options.ListenAddress == nil {
+		t.Error("Unexpected nil")
+	}
+}

+ 10 - 0
config/testdata/nodeaddressesdynamic.xml

@@ -0,0 +1,10 @@
+<configuration version="2">
+    <node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ">
+        <address></address>
+    </node>
+    <node id="GYRZZQBIRNPV4T7TC52WEQYJ3TFDQW6MWDFLMU4SSSU6EMFBK2VA">
+    </node>
+    <node id="LGFPDIT7SKNNJVJZA4FC7QNCRKCE753K72BW5QD2FOZ7FRFEP57Q">
+        <address>dynamic</address>
+    </node>
+</configuration>

+ 14 - 0
config/testdata/nodeaddressesstatic.xml

@@ -0,0 +1,14 @@
+<configuration version="3">
+    <node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ">
+        <address>192.0.2.1</address>
+        <address>192.0.2.2</address>
+    </node>
+    <node id="GYRZZQBIRNPV4T7TC52WEQYJ3TFDQW6MWDFLMU4SSSU6EMFBK2VA">
+        <address>192.0.2.3:6070</address>
+        <address>[2001:db8::42]:4242</address>
+    </node>
+    <node id="LGFPDIT7SKNNJVJZA4FC7QNCRKCE753K72BW5QD2FOZ7FRFEP57Q">
+        <address>[2001:db8::44]:4444</address>
+        <address>192.0.2.4:6090</address>
+    </node>
+</configuration>

+ 5 - 0
config/testdata/nolistenaddress.xml

@@ -0,0 +1,5 @@
+<configuration version="1">
+    <options>
+        <listenAddress></listenAddress>
+    </options>
+</configuration>

+ 18 - 0
config/testdata/overridenvalues.xml

@@ -0,0 +1,18 @@
+<configuration version="2">
+    <options>
+       <listenAddress>:23000</listenAddress>
+        <allowDelete>false</allowDelete>
+        <globalAnnounceServer>syncthing.nym.se:22026</globalAnnounceServer>
+        <globalAnnounceEnabled>false</globalAnnounceEnabled>
+        <localAnnounceEnabled>false</localAnnounceEnabled>
+        <localAnnouncePort>42123</localAnnouncePort>
+        <localAnnounceMCAddr>quux:3232</localAnnounceMCAddr>
+        <parallelRequests>32</parallelRequests>
+        <maxSendKbps>1234</maxSendKbps>
+        <reconnectionIntervalS>6000</reconnectionIntervalS>
+        <startBrowser>false</startBrowser>
+        <upnpEnabled>false</upnpEnabled>
+        <upnpLeaseMinutes>60</upnpLeaseMinutes>
+        <upnpRenewalMinutes>15</upnpRenewalMinutes>
+    </options>
+</configuration>

+ 20 - 0
config/testdata/v1.xml

@@ -0,0 +1,20 @@
+<configuration version="1">
+    <repository id="test" directory="~/Sync">
+        <node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ" name="node one">
+            <address>a</address>
+        </node>
+        <node id="P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ" name="node two">
+            <address>b</address>
+        </node>
+        <node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ" name="node one">
+            <address>a</address>
+        </node>
+        <node id="P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ" name="node two">
+            <address>b</address>
+        </node>
+    </repository>
+    <options>
+        <readOnly>true</readOnly>
+        <rescanIntervalS>600</rescanIntervalS>
+    </options>
+</configuration>

+ 19 - 0
config/testdata/v2.xml

@@ -0,0 +1,19 @@
+<configuration version="2">
+    <repository id="test" directory="~/Sync" ro="true">
+        <node id="P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ"/>
+        <node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ"/>
+        <node id="C4YBIESWDUAIGU62GOSRXCRAAJDWVE3TKCPMURZE2LH5QHAF576A"/>
+        <node id="P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ"/>
+        <node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ"/>
+        <node id="C4YBIESWDUAIGU62GOSRXCRAAJDWVE3TKCPMURZE2LH5QHAF576A"/>
+    </repository>
+    <node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ" name="node one">
+        <address>a</address>
+    </node>
+    <node id="P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ" name="node two">
+        <address>b</address>
+    </node>
+    <options>
+        <rescanIntervalS>600</rescanIntervalS>
+    </options>
+</configuration>

+ 15 - 0
config/testdata/v3.xml

@@ -0,0 +1,15 @@
+<configuration version="3">
+    <repository id="test" directory="~/Sync" ro="true" ignorePerms="false">
+        <node id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR" compression="false"></node>
+        <node id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2" compression="false"></node>
+    </repository>
+    <node id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR" name="node one" compression="true">
+        <address>a</address>
+    </node>
+    <node id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2" name="node two" compression="true">
+        <address>b</address>
+    </node>
+    <options>
+        <rescanIntervalS>600</rescanIntervalS>
+    </options>
+</configuration>

+ 12 - 0
config/testdata/v4.xml

@@ -0,0 +1,12 @@
+<configuration version="4">
+    <repository id="test" directory="~/Sync" ro="true" ignorePerms="false" rescanIntervalS="600">
+        <node id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR"></node>
+        <node id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"></node>
+    </repository>
+    <node id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR" name="node one" compression="true">
+        <address>a</address>
+    </node>
+    <node id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2" name="node two" compression="true">
+        <address>b</address>
+    </node>
+</configuration>

+ 8 - 0
config/testdata/versioningconfig.xml

@@ -0,0 +1,8 @@
+<configuration version="2">
+    <repository id="test" directory="~/Sync" ro="true">
+        <versioning type="simple">
+            <param key="foo" val="bar"/>
+            <param key="baz" val="quux"/>
+        </versioning>
+    </repository>
+</configuration>

+ 1 - 1
model/model_test.go

@@ -266,7 +266,7 @@ func TestNodeRename(t *testing.T) {
 		ClientVersion: "v0.9.4",
 	}
 
-	cfg, _ := config.Load(nil, node1)
+	cfg := config.New("test", node1)
 	cfg.Nodes = []config.NodeConfiguration{
 		{
 			NodeID: node1,