This commit is contained in:
Alexander Neumann 2014-08-04 22:46:14 +02:00
parent 022f514b09
commit e8b83e460f
8 changed files with 80 additions and 111 deletions

View File

@ -24,7 +24,7 @@ func hash(filename string) (khepri.ID, error) {
} }
func store_file(repo *khepri.Repository, path string) (khepri.ID, error) { func store_file(repo *khepri.Repository, path string) (khepri.ID, error) {
obj, err := repo.NewObject(khepri.TYPE_BLOB) obj, idch, err := repo.Create(khepri.TYPE_BLOB)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -44,7 +44,7 @@ func store_file(repo *khepri.Repository, path string) (khepri.ID, error) {
return nil, err return nil, err
} }
return obj.ID(), nil return <-idch, nil
} }
func archive_dir(repo *khepri.Repository, path string) (khepri.ID, error) { func archive_dir(repo *khepri.Repository, path string) (khepri.ID, error) {
@ -92,7 +92,7 @@ func archive_dir(repo *khepri.Repository, path string) (khepri.ID, error) {
log.Printf(" dir %q: %v entries", path, len(t.Nodes)) log.Printf(" dir %q: %v entries", path, len(t.Nodes))
obj, err := repo.NewObject(khepri.TYPE_BLOB) obj, idch, err := repo.Create(khepri.TYPE_BLOB)
if err != nil { if err != nil {
log.Printf("error creating object for tree: %v", err) log.Printf("error creating object for tree: %v", err)
@ -106,7 +106,7 @@ func archive_dir(repo *khepri.Repository, path string) (khepri.ID, error) {
obj.Close() obj.Close()
id := obj.ID() id := <-idch
log.Printf("tree for %q saved at %s", path, id) log.Printf("tree for %q saved at %s", path, id)
return id, nil return id, nil
@ -124,11 +124,15 @@ func commandBackup(repo *khepri.Repository, args []string) error {
return err return err
} }
sn := repo.NewSnapshot(target) sn := khepri.NewSnapshot(target)
sn.Tree = id sn.Tree = id
sn.Save() snid, err := sn.Save(repo)
fmt.Printf("%q archived as %v\n", target, sn.ID()) if err != nil {
log.Printf("error saving snapshopt: %v", err)
}
fmt.Printf("%q archived as %v\n", target, snid)
return nil return nil
} }

View File

@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"os" "os"
"path" "path"
@ -49,7 +50,7 @@ func restore_file(repo *khepri.Repository, node khepri.Node, target string) erro
func restore_dir(repo *khepri.Repository, id khepri.ID, target string) error { func restore_dir(repo *khepri.Repository, id khepri.ID, target string) error {
fmt.Printf(" restore dir %q\n", target) fmt.Printf(" restore dir %q\n", target)
rd, err := repo.Get(khepri.TYPE_REF, id) rd, err := repo.Get(khepri.TYPE_BLOB, id)
if err != nil { if err != nil {
return err return err
} }
@ -121,7 +122,12 @@ func commandRestore(repo *khepri.Repository, args []string) error {
return err return err
} }
err = restore_dir(repo, id, target) sn, err := khepri.LoadSnapshot(repo, id)
if err != nil {
log.Fatalf("error loading snapshot %s", id)
}
err = restore_dir(repo, sn.Tree, target)
if err != nil { if err != nil {
return err return err
} }

109
object.go
View File

@ -1,78 +1,60 @@
package khepri package khepri
import "os" import (
"io"
"os"
)
type Object struct { type createObject struct {
repo *Repository repo *Repository
id ID
tpe Type tpe Type
hw HashingWriter hw HashingWriter
file *os.File file *os.File
ch chan ID
} }
func (repo *Repository) NewObject(t Type) (*Object, error) { func (repo *Repository) Create(t Type) (io.WriteCloser, <-chan ID, error) {
obj := &Object{ obj := &createObject{
repo: repo, repo: repo,
tpe: t, tpe: t,
ch: make(chan ID, 1),
} }
return obj, obj.open() // save contents to tempfile in repository, hash while writing
var err error
obj.file, err = obj.repo.tempFile()
if err != nil {
return nil, nil, err
}
// create hashing writer
obj.hw = NewHashingWriter(obj.file, obj.repo.hash)
return obj, obj.ch, nil
} }
func (obj *Object) open() error { func (obj *createObject) Write(data []byte) (int, error) {
if obj.isFinal() {
panic("object is finalized")
}
if obj.isOpen() {
panic("object already open")
}
// create tempfile in repository
if obj.hw == nil { if obj.hw == nil {
// save contents to tempfile, hash while writing panic("createObject: already closed!")
var err error
obj.file, err = obj.repo.tempFile()
if err != nil {
return err
}
// create hashing writer
obj.hw = NewHashingWriter(obj.file, obj.repo.hash)
}
return nil
}
func (obj *Object) isOpen() bool {
return obj.file != nil && obj.hw != nil
}
func (obj *Object) isFinal() bool {
return obj.id != nil
}
func (obj *Object) Write(data []byte) (int, error) {
if !obj.isOpen() {
panic("object not open")
} }
return obj.hw.Write(data) return obj.hw.Write(data)
} }
func (obj *Object) Close() error { func (obj *createObject) Close() error {
if obj.file == nil || obj.hw == nil { if obj.hw == nil {
panic("object is not open") panic("createObject: already closed!")
} }
obj.file.Close() obj.file.Close()
hash := obj.hw.Hash() id := ID(obj.hw.Hash())
obj.ch <- id
// move file to final name using hash of contents // move file to final name using hash of contents
id := ID(hash)
err := obj.repo.renameFile(obj.file, obj.tpe, id) err := obj.repo.renameFile(obj.file, obj.tpe, id)
if err != nil { if err != nil {
return err return err
@ -80,40 +62,5 @@ func (obj *Object) Close() error {
obj.hw = nil obj.hw = nil
obj.file = nil obj.file = nil
obj.id = id
return nil
}
func (obj *Object) ID() ID {
if !obj.isFinal() {
panic("object not finalized")
}
return obj.id
}
func (obj *Object) Type() Type {
return obj.tpe
}
func (obj *Object) Remove() error {
if obj.id != nil {
return obj.repo.Remove(obj.tpe, obj.id)
}
if obj.file != nil {
file := obj.file
obj.hw = nil
obj.file = nil
err := file.Close()
if err != nil {
return err
}
return os.Remove(file.Name())
}
return nil return nil
} }

View File

@ -16,16 +16,18 @@ func TestObjects(t *testing.T) {
}() }()
for _, test := range TestStrings { for _, test := range TestStrings {
obj, err := repo.NewObject(khepri.TYPE_BLOB) obj, ch, err := repo.Create(khepri.TYPE_BLOB)
ok(t, err) ok(t, err)
_, err = obj.Write([]byte(test.data)) _, err = obj.Write([]byte(test.data))
ok(t, err) ok(t, err)
obj.Close() err = obj.Close()
ok(t, err)
id, err := khepri.ParseID(test.id) id, err := khepri.ParseID(test.id)
ok(t, err) ok(t, err)
equals(t, id, obj.ID()) equals(t, id, <-ch)
} }
} }

View File

@ -151,7 +151,7 @@ func (r *Repository) Test(t Type, id ID) (bool, error) {
} }
// Get returns a reader for the content stored under the given ID. // Get returns a reader for the content stored under the given ID.
func (r *Repository) Get(t Type, id ID) (io.Reader, error) { func (r *Repository) Get(t Type, id ID) (io.ReadCloser, error) {
// try to open file // try to open file
file, err := os.Open(r.filename(t, id)) file, err := os.Open(r.filename(t, id))
if err != nil { if err != nil {

View File

@ -75,7 +75,7 @@ func TestRepository(t *testing.T) {
// add files // add files
for _, test := range TestStrings { for _, test := range TestStrings {
// store string in repository // store string in repository
obj, err := repo.NewObject(test.t) obj, id_ch, err := repo.Create(test.t)
ok(t, err) ok(t, err)
_, err = obj.Write([]byte(test.data)) _, err = obj.Write([]byte(test.data))
@ -84,7 +84,7 @@ func TestRepository(t *testing.T) {
err = obj.Close() err = obj.Close()
ok(t, err) ok(t, err)
id := obj.ID() id := <-id_ch
equals(t, test.id, id.String()) equals(t, test.id, id.String())
// try to get it out again // try to get it out again

View File

@ -15,14 +15,11 @@ type Snapshot struct {
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
UID string `json:"uid,omitempty"` UID string `json:"uid,omitempty"`
GID string `json:"gid,omitempty"` GID string `json:"gid,omitempty"`
id ID
repo *Repository
} }
func (repo *Repository) NewSnapshot(dir string) *Snapshot { func NewSnapshot(dir string) *Snapshot {
sn := &Snapshot{ sn := &Snapshot{
Dir: dir, Dir: dir,
repo: repo,
Time: time.Now(), Time: time.Now(),
} }
@ -41,31 +38,43 @@ func (repo *Repository) NewSnapshot(dir string) *Snapshot {
return sn return sn
} }
func (sn *Snapshot) Save() error { func (sn *Snapshot) Save(repo *Repository) (ID, error) {
if sn.Tree == nil { if sn.Tree == nil {
panic("Snapshot.Save() called with nil tree id") panic("Snapshot.Save() called with nil tree id")
} }
obj, err := sn.repo.NewObject(TYPE_REF) obj, id_ch, err := repo.Create(TYPE_REF)
if err != nil { if err != nil {
return err return nil, err
} }
enc := json.NewEncoder(obj) enc := json.NewEncoder(obj)
err = enc.Encode(sn) err = enc.Encode(sn)
if err != nil { if err != nil {
return err return nil, err
} }
err = obj.Close() err = obj.Close()
if err != nil { if err != nil {
return err return nil, err
} }
sn.id = obj.ID() return <-id_ch, nil
return nil
} }
func (sn *Snapshot) ID() ID { func LoadSnapshot(repo *Repository, id ID) (*Snapshot, error) {
return sn.id rd, err := repo.Get(TYPE_REF, id)
if err != nil {
return nil, err
}
dec := json.NewDecoder(rd)
sn := &Snapshot{}
err = dec.Decode(sn)
if err != nil {
return nil, err
}
return sn, nil
} }

View File

@ -16,11 +16,12 @@ func TestSnapshot(t *testing.T) {
ok(t, err) ok(t, err)
}() }()
sn := repo.NewSnapshot("/home/foobar") sn := khepri.NewSnapshot("/home/foobar")
sn.Tree, err = khepri.ParseID("c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2") sn.Tree, err = khepri.ParseID("c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2")
ok(t, err) ok(t, err)
sn.Time, err = time.Parse(time.RFC3339Nano, "2014-08-03T17:49:05.378595539+02:00") sn.Time, err = time.Parse(time.RFC3339Nano, "2014-08-03T17:49:05.378595539+02:00")
ok(t, err) ok(t, err)
ok(t, sn.Save()) _, err = sn.Save(repo)
ok(t, err)
} }