restic/repository.go

301 lines
5.3 KiB
Go
Raw Normal View History

2014-07-28 18:20:32 +00:00
package khepri
2014-04-19 17:37:34 +00:00
import (
"crypto/sha256"
"errors"
"fmt"
2014-04-19 17:37:34 +00:00
"hash"
"io"
"io/ioutil"
"net/url"
"os"
"path"
"path/filepath"
2014-04-19 17:37:34 +00:00
)
const (
dirMode = 0700
blobPath = "blobs"
refPath = "refs"
tempPath = "tmp"
2014-04-19 17:37:34 +00:00
)
var (
ErrIDDoesNotExist = errors.New("ID does not exist")
)
// Name stands for the alias given to an ID.
type Name string
func (n Name) Encode() string {
return url.QueryEscape(string(n))
}
2014-04-20 14:48:47 +00:00
type DirRepository struct {
2014-04-19 17:37:34 +00:00
path string
hash func() hash.Hash
}
type Type int
const (
2014-08-03 13:16:56 +00:00
TYPE_BLOB = iota
TYPE_REF
)
func NewTypeFromString(s string) Type {
switch s {
case "blob":
2014-08-03 13:16:56 +00:00
return TYPE_BLOB
case "ref":
2014-08-03 13:16:56 +00:00
return TYPE_REF
}
panic(fmt.Sprintf("unknown type %q", s))
}
func (t Type) String() string {
switch t {
2014-08-03 13:16:56 +00:00
case TYPE_BLOB:
return "blob"
2014-08-03 13:16:56 +00:00
case TYPE_REF:
return "ref"
}
panic(fmt.Sprintf("unknown type %d", t))
}
2014-04-20 14:48:47 +00:00
// NewDirRepository creates a new dir-baked repository at the given path.
func NewDirRepository(path string) (*DirRepository, error) {
d := &DirRepository{
2014-04-19 17:37:34 +00:00
path: path,
hash: sha256.New,
}
err := d.create()
if err != nil {
return nil, err
}
return d, nil
}
2014-04-20 14:48:47 +00:00
func (r *DirRepository) create() error {
2014-04-19 17:37:34 +00:00
dirs := []string{
r.path,
path.Join(r.path, blobPath),
2014-04-19 17:37:34 +00:00
path.Join(r.path, refPath),
path.Join(r.path, tempPath),
}
for _, dir := range dirs {
err := os.MkdirAll(dir, dirMode)
if err != nil {
return err
}
}
return nil
}
// SetHash changes the hash function used for deriving IDs. Default is SHA256.
2014-04-20 14:48:47 +00:00
func (r *DirRepository) SetHash(h func() hash.Hash) {
2014-04-19 17:37:34 +00:00
r.hash = h
}
// Path returns the directory used for this repository.
2014-04-20 14:48:47 +00:00
func (r *DirRepository) Path() string {
2014-04-19 17:37:34 +00:00
return r.path
}
// Return temp directory in correct directory for this repository.
func (r *DirRepository) tempFile() (*os.File, error) {
return ioutil.TempFile(path.Join(r.path, tempPath), "temp-")
}
// Rename temp file to final name according to type and ID.
func (r *DirRepository) renameFile(file *os.File, t Type, id ID) error {
filename := path.Join(r.dir(t), id.String())
return os.Rename(file.Name(), filename)
}
2014-04-19 17:37:34 +00:00
// Put saves content and returns the ID.
func (r *DirRepository) Put(t Type, reader io.Reader) (ID, error) {
2014-04-19 17:37:34 +00:00
// save contents to tempfile, hash while writing
file, err := r.tempFile()
2014-04-19 17:37:34 +00:00
if err != nil {
return nil, err
}
2014-07-28 18:20:32 +00:00
rd := NewHashingReader(reader, r.hash)
2014-04-19 17:37:34 +00:00
_, err = io.Copy(file, rd)
if err != nil {
return nil, err
}
err = file.Close()
if err != nil {
return nil, err
}
// move file to final name using hash of contents
id := ID(rd.Hash())
err = r.renameFile(file, t, id)
2014-04-19 17:37:34 +00:00
if err != nil {
return nil, err
}
return id, nil
}
// Construct directory for given Type.
func (r *DirRepository) dir(t Type) string {
switch t {
2014-08-03 13:16:56 +00:00
case TYPE_BLOB:
return path.Join(r.path, blobPath)
2014-08-03 13:16:56 +00:00
case TYPE_REF:
return path.Join(r.path, refPath)
}
panic(fmt.Sprintf("unknown type %d", t))
}
// Construct path for given Type and ID.
func (r *DirRepository) filename(t Type, id ID) string {
return path.Join(r.dir(t), id.String())
}
2014-04-19 17:37:34 +00:00
// PutFile saves a file's content to the repository and returns the ID.
2014-04-20 14:48:47 +00:00
func (r *DirRepository) PutFile(path string) (ID, error) {
2014-04-19 17:37:34 +00:00
f, err := os.Open(path)
defer f.Close()
if err != nil {
return nil, err
}
2014-08-03 13:16:56 +00:00
return r.Put(TYPE_BLOB, f)
2014-04-19 17:37:34 +00:00
}
2014-04-21 21:25:31 +00:00
// PutRaw saves a []byte's content to the repository and returns the ID.
func (r *DirRepository) PutRaw(t Type, buf []byte) (ID, error) {
2014-04-21 21:25:31 +00:00
// save contents to tempfile, hash while writing
file, err := r.tempFile()
2014-04-21 21:25:31 +00:00
if err != nil {
return nil, err
}
2014-07-28 18:20:32 +00:00
wr := NewHashingWriter(file, r.hash)
_, err = wr.Write(buf)
2014-04-21 21:25:31 +00:00
if err != nil {
return nil, err
}
err = file.Close()
if err != nil {
return nil, err
}
// move file to final name using hash of contents
id := ID(wr.Hash())
err = r.renameFile(file, t, id)
2014-04-21 21:25:31 +00:00
if err != nil {
return nil, err
}
return id, nil
}
2014-04-19 17:37:34 +00:00
// Test returns true if the given ID exists in the repository.
func (r *DirRepository) Test(t Type, id ID) (bool, error) {
2014-04-19 17:37:34 +00:00
// try to open file
file, err := os.Open(r.filename(t, id))
2014-04-19 17:37:34 +00:00
defer func() {
file.Close()
}()
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
return true, nil
}
// Get returns a reader for the content stored under the given ID.
func (r *DirRepository) Get(t Type, id ID) (io.Reader, error) {
2014-04-19 17:37:34 +00:00
// try to open file
file, err := os.Open(r.filename(t, id))
2014-04-19 17:37:34 +00:00
if err != nil {
return nil, err
}
return file, nil
}
2014-04-19 17:46:20 +00:00
// Remove removes the content stored at ID.
func (r *DirRepository) Remove(t Type, id ID) error {
return os.Remove(r.filename(t, id))
2014-04-19 17:46:20 +00:00
}
type IDs []ID
// Lists all objects of a given type.
func (r *DirRepository) ListIDs(t Type) (IDs, error) {
// TODO: use os.Open() and d.Readdirnames() instead of Glob()
pattern := path.Join(r.dir(t), "*")
2014-04-19 17:37:34 +00:00
matches, err := filepath.Glob(pattern)
2014-04-19 17:37:34 +00:00
if err != nil {
return nil, err
2014-04-19 17:37:34 +00:00
}
ids := make(IDs, 0, len(matches))
2014-04-19 17:37:34 +00:00
for _, m := range matches {
base := filepath.Base(m)
2014-04-19 17:37:34 +00:00
if base == "" {
continue
}
id, err := ParseID(base)
if err != nil {
continue
}
ids = append(ids, id)
2014-04-19 17:37:34 +00:00
}
return ids, nil
2014-04-19 17:37:34 +00:00
}
func (ids IDs) Len() int {
return len(ids)
}
func (ids IDs) Less(i, j int) bool {
if len(ids[i]) < len(ids[j]) {
return true
2014-04-19 17:37:34 +00:00
}
for k, b := range ids[i] {
if b == ids[j][k] {
continue
}
2014-04-19 17:46:20 +00:00
if b < ids[j][k] {
return true
} else {
return false
}
2014-04-19 17:46:20 +00:00
}
2014-04-19 17:37:34 +00:00
return false
}
2014-04-19 17:37:34 +00:00
func (ids IDs) Swap(i, j int) {
ids[i], ids[j] = ids[j], ids[i]
2014-04-19 17:37:34 +00:00
}