From c553a57e0d5eb04495d21c8f67fa640b3b8fb1b5 Mon Sep 17 00:00:00 2001
From: Alexander Neumann <alexander@bumpern.de>
Date: Thu, 2 Jul 2015 22:36:31 +0200
Subject: [PATCH] repository: Refactor Config

---
 repository/config.go      | 87 +++++++++++++++++++++++++++++++++++++++
 repository/config_test.go | 53 ++++++++++++++++++++++++
 repository/repository.go  | 60 +++------------------------
 3 files changed, 146 insertions(+), 54 deletions(-)
 create mode 100644 repository/config.go
 create mode 100644 repository/config_test.go

diff --git a/repository/config.go b/repository/config.go
new file mode 100644
index 000000000..46a9e778c
--- /dev/null
+++ b/repository/config.go
@@ -0,0 +1,87 @@
+package repository
+
+import (
+	"crypto/rand"
+	"crypto/sha256"
+	"encoding/hex"
+	"errors"
+	"io"
+
+	"github.com/restic/restic/backend"
+	"github.com/restic/restic/chunker"
+	"github.com/restic/restic/debug"
+)
+
+// Config contains the configuration for a repository.
+type Config struct {
+	Version           uint        `json:"version"`
+	ID                string      `json:"id"`
+	ChunkerPolynomial chunker.Pol `json:"chunker_polynomial"`
+}
+
+// repositoryIDSize is the length of the ID chosen at random for a new repository.
+const repositoryIDSize = sha256.Size
+
+// RepoVersion is the version that is written to the config when a repository
+// is newly created with Init().
+const RepoVersion = 1
+
+// JSONUnpackedSaver saves unpacked JSON.
+type JSONUnpackedSaver interface {
+	SaveJSONUnpacked(backend.Type, interface{}) (backend.ID, error)
+}
+
+// JSONUnpackedLoader loads unpacked JSON.
+type JSONUnpackedLoader interface {
+	LoadJSONUnpacked(backend.Type, backend.ID, interface{}) error
+}
+
+// CreateConfig creates a config file with a randomly selected polynomial and
+// ID and saves the config in the repository.
+func CreateConfig(r JSONUnpackedSaver) (Config, error) {
+	var (
+		err error
+		cfg Config
+	)
+
+	cfg.ChunkerPolynomial, err = chunker.RandomPolynomial()
+	if err != nil {
+		return Config{}, err
+	}
+
+	newID := make([]byte, repositoryIDSize)
+	_, err = io.ReadFull(rand.Reader, newID)
+	if err != nil {
+		return Config{}, err
+	}
+
+	cfg.ID = hex.EncodeToString(newID)
+	cfg.Version = RepoVersion
+
+	debug.Log("Repo.CreateConfig", "New config: %#v", cfg)
+
+	_, err = r.SaveJSONUnpacked(backend.Config, cfg)
+	return cfg, err
+}
+
+// LoadConfig returns loads, checks and returns the config for a repository.
+func LoadConfig(r JSONUnpackedLoader) (Config, error) {
+	var (
+		cfg Config
+	)
+
+	err := r.LoadJSONUnpacked(backend.Config, nil, &cfg)
+	if err != nil {
+		return Config{}, err
+	}
+
+	if cfg.Version != RepoVersion {
+		return Config{}, errors.New("unsupported repository version")
+	}
+
+	if !cfg.ChunkerPolynomial.Irreducible() {
+		return Config{}, errors.New("invalid chunker polynomial")
+	}
+
+	return cfg, nil
+}
diff --git a/repository/config_test.go b/repository/config_test.go
new file mode 100644
index 000000000..3b5dc381d
--- /dev/null
+++ b/repository/config_test.go
@@ -0,0 +1,53 @@
+package repository_test
+
+import (
+	"testing"
+
+	"github.com/restic/restic/backend"
+	"github.com/restic/restic/repository"
+	. "github.com/restic/restic/test"
+)
+
+type saver func(backend.Type, interface{}) (backend.ID, error)
+
+func (s saver) SaveJSONUnpacked(t backend.Type, arg interface{}) (backend.ID, error) {
+	return s(t, arg)
+}
+
+type loader func(backend.Type, backend.ID, interface{}) error
+
+func (l loader) LoadJSONUnpacked(t backend.Type, id backend.ID, arg interface{}) error {
+	return l(t, id, arg)
+}
+
+func TestConfig(t *testing.T) {
+	resultConfig := repository.Config{}
+	save := func(tpe backend.Type, arg interface{}) (backend.ID, error) {
+		Assert(t, tpe == backend.Config,
+			"wrong backend type: got %v, wanted %v",
+			tpe, backend.Config)
+
+		cfg := arg.(repository.Config)
+		resultConfig = cfg
+		return backend.ID{}, nil
+	}
+
+	cfg1, err := repository.CreateConfig(saver(save))
+	OK(t, err)
+
+	load := func(tpe backend.Type, id backend.ID, arg interface{}) error {
+		Assert(t, tpe == backend.Config,
+			"wrong backend type: got %v, wanted %v",
+			tpe, backend.Config)
+
+		cfg := arg.(*repository.Config)
+		*cfg = resultConfig
+		return nil
+	}
+
+	cfg2, err := repository.LoadConfig(loader(load))
+	OK(t, err)
+
+	Assert(t, cfg1 == cfg2,
+		"configs aren't equal: %v != %v", cfg1, cfg2)
+}
diff --git a/repository/repository.go b/repository/repository.go
index 5817fb622..616295f2c 100644
--- a/repository/repository.go
+++ b/repository/repository.go
@@ -2,9 +2,7 @@ package repository
 
 import (
 	"bytes"
-	"crypto/rand"
 	"crypto/sha256"
-	"encoding/hex"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -19,13 +17,6 @@ import (
 	"github.com/restic/restic/pack"
 )
 
-// Config contains the configuration for a repository.
-type Config struct {
-	Version           uint        `json:"version"`
-	ID                string      `json:"id"`
-	ChunkerPolynomial chunker.Pol `json:"chunker_polynomial"`
-}
-
 // Repository is used to access a repository in a backend.
 type Repository struct {
 	be      backend.Backend
@@ -526,47 +517,6 @@ func (r *Repository) loadIndex(id string) error {
 	return nil
 }
 
-const repositoryIDSize = sha256.Size
-const RepoVersion = 1
-
-func createConfig(r *Repository) (err error) {
-	r.Config.ChunkerPolynomial, err = chunker.RandomPolynomial()
-	if err != nil {
-		return err
-	}
-
-	newID := make([]byte, repositoryIDSize)
-	_, err = io.ReadFull(rand.Reader, newID)
-	if err != nil {
-		return err
-	}
-
-	r.Config.ID = hex.EncodeToString(newID)
-	r.Config.Version = RepoVersion
-
-	debug.Log("Repo.createConfig", "New config: %#v", r.Config)
-
-	_, err = r.SaveJSONUnpacked(backend.Config, r.Config)
-	return err
-}
-
-func (r *Repository) loadConfig(cfg *Config) error {
-	err := r.LoadJSONUnpacked(backend.Config, nil, cfg)
-	if err != nil {
-		return err
-	}
-
-	if cfg.Version != RepoVersion {
-		return errors.New("unsupported repository version")
-	}
-
-	if !cfg.ChunkerPolynomial.Irreducible() {
-		return errors.New("invalid chunker polynomial")
-	}
-
-	return nil
-}
-
 // SearchKey finds a key with the supplied password, afterwards the config is
 // read and parsed.
 func (r *Repository) SearchKey(password string) error {
@@ -577,11 +527,12 @@ func (r *Repository) SearchKey(password string) error {
 
 	r.key = key.master
 	r.keyName = key.Name()
-	return r.loadConfig(&r.Config)
+	r.Config, err = LoadConfig(r)
+	return err
 }
 
-// Init creates a new master key with the supplied password and initializes the
-// repository config.
+// Init creates a new master key with the supplied password, initializes and
+// saves the repository config.
 func (r *Repository) Init(password string) error {
 	has, err := r.be.Test(backend.Config, "")
 	if err != nil {
@@ -598,7 +549,8 @@ func (r *Repository) Init(password string) error {
 
 	r.key = key.master
 	r.keyName = key.Name()
-	return createConfig(r)
+	r.Config, err = CreateConfig(r)
+	return err
 }
 
 func (r *Repository) Decrypt(ciphertext []byte) ([]byte, error) {