restic/repository.go

235 lines
4 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-08-11 20:47:24 +00:00
type HashFunc func() hash.Hash
2014-08-04 18:47:04 +00:00
type Repository struct {
2014-04-19 17:37:34 +00:00
path string
2014-08-11 20:47:24 +00:00
hash HashFunc
2014-04-19 17:37:34 +00:00
}
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.
2014-08-04 18:47:04 +00:00
func NewRepository(path string) (*Repository, error) {
d := &Repository{
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-08-04 18:47:04 +00:00
func (r *Repository) 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
}
2014-08-11 20:47:24 +00:00
// SetHash changes the hash function used for deriving IDs. Default is SHA256.
func (r *Repository) SetHash(h HashFunc) {
r.hash = h
}
2014-04-19 17:37:34 +00:00
// Path returns the directory used for this repository.
2014-08-04 18:47:04 +00:00
func (r *Repository) Path() string {
2014-04-19 17:37:34 +00:00
return r.path
}
// Return temp directory in correct directory for this repository.
2014-08-04 18:47:04 +00:00
func (r *Repository) tempFile() (*os.File, error) {
return ioutil.TempFile(path.Join(r.path, tempPath), "temp-")
}
// Rename temp file to final name according to type and ID.
2014-08-04 18:47:04 +00:00
func (r *Repository) renameFile(file *os.File, t Type, id ID) error {
filename := path.Join(r.dir(t), id.String())
return os.Rename(file.Name(), filename)
}
// Construct directory for given Type.
2014-08-04 18:47:04 +00:00
func (r *Repository) 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.
2014-08-04 18:47:04 +00:00
func (r *Repository) filename(t Type, id ID) string {
return path.Join(r.dir(t), id.String())
}
2014-04-19 17:37:34 +00:00
// Test returns true if the given ID exists in the repository.
2014-08-04 18:47:04 +00:00
func (r *Repository) 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.
2014-08-04 20:46:14 +00:00
func (r *Repository) Get(t Type, id ID) (io.ReadCloser, 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.
2014-08-04 18:47:04 +00:00
func (r *Repository) 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.
2014-08-04 20:15:04 +00:00
func (r *Repository) List(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
}