2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-22 04:45:15 +00:00

First test implementation

This commit is contained in:
Alexander Neumann 2014-04-21 23:25:31 +02:00
parent b24390909c
commit 4f3a54dc40
6 changed files with 293 additions and 42 deletions

59
storage/id.go Normal file
View File

@ -0,0 +1,59 @@
package storage
import (
"bytes"
"encoding/hex"
"encoding/json"
)
// References content within a repository.
type ID []byte
// ParseID converts the given string to an ID.
func ParseID(s string) (ID, error) {
b, err := hex.DecodeString(s)
if err != nil {
return nil, err
}
return ID(b), nil
}
func (id ID) String() string {
return hex.EncodeToString(id)
}
// Equal compares an ID to another other.
func (id ID) Equal(other ID) bool {
return bytes.Equal(id, other)
}
// EqualString compares this ID to another one, given as a string.
func (id ID) EqualString(other string) (bool, error) {
s, err := hex.DecodeString(other)
if err != nil {
return false, err
}
return id.Equal(ID(s)), nil
}
func (id ID) MarshalJSON() ([]byte, error) {
return json.Marshal(id.String())
}
func (id *ID) UnmarshalJSON(b []byte) error {
var s string
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*id = make([]byte, len(s)/2)
_, err = hex.Decode(*id, []byte(s))
if err != nil {
return err
}
return nil
}

View File

@ -1,7 +1,6 @@
package storage package storage
import ( import (
"bytes"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"errors" "errors"
@ -25,6 +24,7 @@ const (
type Repository interface { type Repository interface {
Put(reader io.Reader) (ID, error) Put(reader io.Reader) (ID, error)
PutFile(path string) (ID, error) PutFile(path string) (ID, error)
PutRaw([]byte) (ID, error)
Get(ID) (io.Reader, error) Get(ID) (io.Reader, error)
Test(ID) (bool, error) Test(ID) (bool, error)
Remove(ID) error Remove(ID) error
@ -37,28 +37,6 @@ var (
ErrIDDoesNotExist = errors.New("ID does not exist") ErrIDDoesNotExist = errors.New("ID does not exist")
) )
// References content within a repository.
type ID []byte
func (id ID) String() string {
return hex.EncodeToString(id)
}
// Equal compares an ID to another other.
func (id ID) Equal(other ID) bool {
return bytes.Equal(id, other)
}
// EqualString compares this ID to another one, given as a string.
func (id ID) EqualString(other string) (bool, error) {
s, err := hex.DecodeString(other)
if err != nil {
return false, err
}
return id.Equal(ID(s)), nil
}
// Name stands for the alias given to an ID. // Name stands for the alias given to an ID.
type Name string type Name string
@ -156,6 +134,40 @@ func (r *DirRepository) PutFile(path string) (ID, error) {
return r.Put(f) return r.Put(f)
} }
// PutRaw saves a []byte's content to the repository and returns the ID.
func (r *DirRepository) PutRaw(buf []byte) (ID, error) {
// save contents to tempfile, hash while writing
file, err := ioutil.TempFile(path.Join(r.path, tempPath), "temp-")
if err != nil {
return nil, err
}
wr := hashing.NewWriter(file, r.hash)
n, err := wr.Write(buf)
if err != nil {
return nil, err
}
if n != len(buf) {
return nil, errors.New("not all bytes written")
}
err = file.Close()
if err != nil {
return nil, err
}
// move file to final name using hash of contents
id := ID(wr.Hash())
filename := path.Join(r.path, objectPath, id.String())
err = os.Rename(file.Name(), filename)
if err != nil {
return nil, err
}
return id, nil
}
// Test returns true if the given ID exists in the repository. // Test returns true if the given ID exists in the repository.
func (r *DirRepository) Test(id ID) (bool, error) { func (r *DirRepository) Test(id ID) (bool, error) {
// try to open file // try to open file

View File

@ -2,7 +2,6 @@ package storage_test
import ( import (
"bytes" "bytes"
"encoding/hex"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
@ -55,7 +54,7 @@ var _ = Describe("Storage", func() {
Context("File Operations", func() { Context("File Operations", func() {
It("Should detect non-existing file", func() { It("Should detect non-existing file", func() {
for _, test := range TestStrings { for _, test := range TestStrings {
id, err := hex.DecodeString(test.id) id, err := storage.ParseID(test.id)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
// try to get string out, should fail // try to get string out, should fail
@ -96,6 +95,15 @@ var _ = Describe("Storage", func() {
Expect(repo.Remove(id)) Expect(repo.Remove(id))
} }
}) })
It("Should Add Buffer", func() {
for _, test := range TestStrings {
// store buf in repository
id, err := repo.PutRaw([]byte(test.data))
Expect(err).NotTo(HaveOccurred())
Expect(id.String()).To(Equal(test.id))
}
})
}) })
}) })
}) })

53
storage/tree.go Normal file
View File

@ -0,0 +1,53 @@
package storage
import (
"encoding/json"
"io"
"os"
"syscall"
"time"
)
type Tree struct {
Nodes []Node `json:"nodes"`
}
type Node struct {
Name string `json:"name"`
Mode os.FileMode `json:"mode"`
ModTime time.Time `json:"mtime"`
User uint32 `json:"user"`
Group uint32 `json:"group"`
Content ID `json:"content"`
}
func NewTree() *Tree {
return &Tree{
Nodes: []Node{},
}
}
func (t *Tree) Restore(r io.Reader) error {
dec := json.NewDecoder(r)
return dec.Decode(t)
}
func (t *Tree) Save(w io.Writer) error {
enc := json.NewEncoder(w)
return enc.Encode(t)
}
func NodeFromFileInfo(fi os.FileInfo) Node {
node := Node{
Name: fi.Name(),
Mode: fi.Mode(),
ModTime: fi.ModTime(),
}
if stat, ok := fi.Sys().(*syscall.Stat_t); ok {
node.User = stat.Uid
node.Group = stat.Gid
}
return node
}

77
storage/tree_test.go Normal file
View File

@ -0,0 +1,77 @@
package storage_test
import (
"bytes"
"strings"
"time"
"github.com/fd0/khepri/storage"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func parseTime(str string) time.Time {
t, err := time.Parse(time.RFC3339Nano, str)
if err != nil {
panic(err)
}
return t
}
var _ = Describe("Tree", func() {
var t *storage.Tree
var raw string
BeforeEach(func() {
t = new(storage.Tree)
t.Nodes = []storage.Node{
storage.Node{
Name: "foobar",
Mode: 0755,
ModTime: parseTime("2014-04-20T22:16:54.161401+02:00"),
User: 1000,
Group: 1001,
Content: []byte{0x41, 0x42, 0x43},
},
storage.Node{
Name: "baz",
Mode: 0755,
User: 1000,
ModTime: parseTime("2014-04-20T22:16:54.161401+02:00"),
Group: 1001,
Content: []byte("\xde\xad\xbe\xef\xba\xdc\x0d\xe0"),
},
}
raw = `{"nodes":[{"name":"foobar","mode":493,"mtime":"2014-04-20T22:16:54.161401+02:00","user":1000,"group":1001,"content":"414243"},{"name":"baz","mode":493,"mtime":"2014-04-20T22:16:54.161401+02:00","user":1000,"group":1001,"content":"deadbeefbadc0de0"}]}`
})
It("Should save", func() {
var buf bytes.Buffer
t.Save(&buf)
Expect(strings.TrimRight(buf.String(), "\n")).To(Equal(raw))
t2 := new(storage.Tree)
err := t2.Restore(&buf)
Expect(err).NotTo(HaveOccurred())
// test tree for equality
Expect(t2).To(Equal(t))
// test nodes for equality
for i, n := range t.Nodes {
Expect(n.Content).To(Equal(t2.Nodes[i].Content))
}
})
It("Should restore", func() {
buf := bytes.NewBufferString(raw)
t2 := new(storage.Tree)
err := t2.Restore(buf)
Expect(err).NotTo(HaveOccurred())
// test if tree has correctly been restored
Expect(t2).To(Equal(t))
})
})

View File

@ -1,10 +1,12 @@
package main package main
import ( import (
"bytes"
"crypto/sha256" "crypto/sha256"
"io" "io"
"log" "log"
"os" "os"
"path/filepath"
"github.com/fd0/khepri/storage" "github.com/fd0/khepri/storage"
) )
@ -20,31 +22,71 @@ func hash(filename string) (storage.ID, error) {
return h.Sum([]byte{}), nil return h.Sum([]byte{}), nil
} }
func archive_dir(repo storage.Repository, path string) { func archive_dir(repo storage.Repository, path string) (storage.ID, error) {
log.Printf("archiving dir %q", path) log.Printf("archiving dir %q", path)
// items := make() dir, err := os.Open(path)
// filepath.Walk(path, func(item string, info os.FileInfo, err error) error { if err != nil {
// log.Printf(" archiving %q", item) log.Printf("open(%q): %v\n", path, err)
return nil, err
}
// if item != path && info.IsDir() { entries, err := dir.Readdir(-1)
// archive_dir(repo, item) if err != nil {
// } else { log.Printf("readdir(%q): %v\n", path, err)
return nil, err
}
// } t := storage.NewTree()
for _, e := range entries {
node := storage.NodeFromFileInfo(e)
// return nil var id storage.ID
// }) var err error
if e.IsDir() {
id, err = archive_dir(repo, filepath.Join(path, e.Name()))
} else {
id, err = repo.PutFile(filepath.Join(path, e.Name()))
}
node.Content = id
t.Nodes = append(t.Nodes, node)
if err != nil {
log.Printf(" error storing %q: %v\n", e.Name(), err)
continue
}
}
log.Printf(" dir %q: %v entries", path, len(t.Nodes))
var buf bytes.Buffer
t.Save(&buf)
id, err := repo.PutRaw(buf.Bytes())
if err != nil {
log.Printf("error saving tree to repo: %v", err)
}
log.Printf("tree for %q saved at %s", path, id)
return id, nil
} }
func main() { func main() {
repo, err := storage.NewDir("repo") if len(os.Args) <= 2 {
log.Fatalf("usage: %s repo [add|link|putdir] ...", os.Args[0])
}
repo, err := storage.NewDirRepository(os.Args[1])
if err != nil { if err != nil {
log.Fatalf("error: %v", err) log.Fatalf("error: %v", err)
} }
switch os.Args[1] { switch os.Args[2] {
case "add": case "add":
for _, file := range os.Args[2:] { for _, file := range os.Args[3:] {
f, err := os.Open(file) f, err := os.Open(file)
if err != nil { if err != nil {
log.Printf("error opening file %q: %v", file, err) log.Printf("error opening file %q: %v", file, err)
@ -59,8 +101,8 @@ func main() {
log.Printf("archived file %q as ID %v", file, id) log.Printf("archived file %q as ID %v", file, id)
} }
case "link": case "link":
file := os.Args[2] file := os.Args[3]
name := os.Args[3] name := os.Args[4]
id, err := hash(file) id, err := hash(file)
if err != nil { if err != nil {
@ -85,10 +127,10 @@ func main() {
log.Fatalf("error linking name %q to id %v", name, id) log.Fatalf("error linking name %q to id %v", name, id)
} }
case "putdir": case "putdir":
for _, dir := range os.Args[2:] { for _, dir := range os.Args[3:] {
archive_dir(repo, dir) archive_dir(repo, dir)
} }
default: default:
log.Fatalf("unknown command: %q", os.Args[1]) log.Fatalf("unknown command: %q", os.Args[2])
} }
} }