read support for length and offset

This commit is contained in:
Chapuis Bertil 2015-08-15 10:06:10 +02:00
parent 71163e75d1
commit 59621ca2d6
6 changed files with 321 additions and 62 deletions

View file

@ -1,7 +1,6 @@
package main package main
import ( import (
"log"
"net/http" "net/http"
) )

View file

@ -1,6 +1,11 @@
package main package main
import () import (
"os"
"path/filepath"
"github.com/restic/restic/backend"
)
// A Context specifies the root directory where all repositories are stored // A Context specifies the root directory where all repositories are stored
type Context struct { type Context struct {
@ -8,10 +13,18 @@ type Context struct {
} }
func NewContext(path string) Context { func NewContext(path string) Context {
return Context{path} return Context{filepath.Clean(path)}
} }
// Creates the file structure of the Context // Creates the file structure of the Context
func (c *Context) Init() { func (c *Context) Init() error {
if _, err := os.Stat(c.path); err != nil {
return os.MkdirAll(c.path, backend.Modes.Dir)
}
return nil
}
func (c *Context) Repository(name string) (Repository, error) {
name, err := ParseRepositoryName(name)
return Repository{filepath.Join(c.path, name)}, err
} }

View file

@ -1,40 +1,223 @@
package main package main
import ( import (
"fmt" "encoding/json"
"io/ioutil"
"net/http" "net/http"
"time"
) )
type Handler func(w http.ResponseWriter, r *http.Request, c *Context) type Handler func(w http.ResponseWriter, r *http.Request, c *Context)
func HeadConfig(w http.ResponseWriter, r *http.Request, c *Context) { func HeadConfig(w http.ResponseWriter, r *http.Request, c *Context) {
fmt.Fprintln(w, "head config") uri := r.RequestURI
name, errrn := RepositoryName(uri)
if errrn != nil {
http.NotFound(w, r)
return
}
repo, errr := c.Repository(name)
if errr != nil {
http.NotFound(w, r)
return
}
if !repo.HasConfig() {
http.NotFound(w, r)
return
}
} }
func GetConfig(w http.ResponseWriter, r *http.Request, c *Context) { func GetConfig(w http.ResponseWriter, r *http.Request, c *Context) {
fmt.Fprintln(w, "get config") uri := r.RequestURI
name, errrn := RepositoryName(uri)
if errrn != nil {
http.NotFound(w, r)
return
}
repo, errr := c.Repository(name)
if errr != nil {
http.NotFound(w, r)
return
}
config, errrc := repo.ReadConfig()
if errrc != nil {
http.NotFound(w, r)
return
}
w.Write(config)
} }
func PostConfig(w http.ResponseWriter, r *http.Request, c *Context) { func PostConfig(w http.ResponseWriter, r *http.Request, c *Context) {
fmt.Fprintln(w, "post config") uri := r.RequestURI
name, errrn := RepositoryName(uri)
if errrn != nil {
http.NotFound(w, r)
return
}
repo, errr := c.Repository(name)
if errr != nil {
http.NotFound(w, r)
return
}
config, errc := ioutil.ReadAll(r.Body)
if errc != nil {
http.NotFound(w, r)
return
}
errwc := repo.WriteConfig(config)
if errwc != nil {
http.NotFound(w, r)
return
}
} }
func ListBlob(w http.ResponseWriter, r *http.Request, c *Context) { func ListBlob(w http.ResponseWriter, r *http.Request, c *Context) {
fmt.Fprintln(w, "list blob") uri := r.RequestURI
name, errrn := RepositoryName(uri)
if errrn != nil {
http.NotFound(w, r)
return
}
repo, errr := c.Repository(name)
if errr != nil {
http.NotFound(w, r)
return
}
bt := BackendType(uri)
if bt.IsNull() {
http.NotFound(w, r)
return
}
blobs, errb := repo.ListBlob(bt)
if errb != nil {
http.NotFound(w, r)
return
}
json, errj := json.Marshal(blobs)
if errj != nil {
http.NotFound(w, r)
return
}
w.Write(json)
} }
func HeadBlob(w http.ResponseWriter, r *http.Request, c *Context) { func HeadBlob(w http.ResponseWriter, r *http.Request, c *Context) {
fmt.Fprintln(w, "head blob") uri := r.RequestURI
name, errrn := RepositoryName(uri)
if errrn != nil {
http.NotFound(w, r)
return
}
repo, errr := c.Repository(name)
if errr != nil {
http.NotFound(w, r)
return
}
bt := BackendType(uri)
if bt.IsNull() {
http.NotFound(w, r)
return
}
id := BlobID(uri)
if id.IsNull() {
http.NotFound(w, r)
return
}
if !repo.HasBlob(bt, id) {
http.NotFound(w, r)
return
}
} }
func GetBlob(w http.ResponseWriter, r *http.Request, c *Context) { func GetBlob(w http.ResponseWriter, r *http.Request, c *Context) {
fmt.Fprintln(w, "get blob") uri := r.RequestURI
name, errrn := RepositoryName(uri)
if errrn != nil {
http.NotFound(w, r)
return
}
repo, errr := c.Repository(name)
if errr != nil {
http.NotFound(w, r)
return
}
bt := BackendType(uri)
if bt.IsNull() {
http.NotFound(w, r)
return
}
id := BlobID(uri)
if id.IsNull() {
http.NotFound(w, r)
return
}
blob, errb := repo.ReadBlob(bt, id)
if errb != nil {
http.NotFound(w, r)
return
}
http.ServeContent(w, r, "", time.Unix(0, 0), blob)
} }
func PostBlob(w http.ResponseWriter, r *http.Request, c *Context) { func PostBlob(w http.ResponseWriter, r *http.Request, c *Context) {
fmt.Fprintln(w, "post blob") uri := r.RequestURI
name, errrn := RepositoryName(uri)
if errrn != nil {
http.NotFound(w, r)
return
}
repo, errr := c.Repository(name)
if errr != nil {
http.NotFound(w, r)
return
}
bt := BackendType(uri)
if bt.IsNull() {
http.NotFound(w, r)
return
}
id := BlobID(uri)
if id.IsNull() {
http.NotFound(w, r)
return
}
blob, errb := ioutil.ReadAll(r.Body)
if errb != nil {
http.NotFound(w, r)
return
}
errwb := repo.WriteBlob(bt, id, blob)
if errwb != nil {
http.NotFound(w, r)
return
}
} }
func DeleteBlob(w http.ResponseWriter, r *http.Request, c *Context) { func DeleteBlob(w http.ResponseWriter, r *http.Request, c *Context) {
fmt.Fprintln(w, "delete blob") uri := r.RequestURI
name, errrn := RepositoryName(uri)
if errrn != nil {
http.NotFound(w, r)
return
}
repo, errr := c.Repository(name)
if errr != nil {
http.NotFound(w, r)
return
}
bt := BackendType(uri)
if bt.IsNull() {
http.NotFound(w, r)
return
}
id := BlobID(uri)
if id.IsNull() {
http.NotFound(w, r)
return
}
errd := repo.DeleteBlob(bt, id)
if errd != nil {
http.NotFound(w, r)
return
}
} }

View file

@ -1,6 +1,12 @@
package main package main
import () import (
"io/ioutil"
"os"
"path/filepath"
"github.com/restic/restic/backend"
)
// A Repository is the place where backups are stored // A Repository is the place where backups are stored
type Repository struct { type Repository struct {
@ -8,6 +14,81 @@ type Repository struct {
} }
// Creates the file structure of the Repository // Creates the file structure of the Repository
func (r *Repository) Init() { func (r *Repository) Init() error {
dirs := []string{
r.path,
filepath.Join(r.path, string(backend.Data)),
filepath.Join(r.path, string(backend.Snapshot)),
filepath.Join(r.path, string(backend.Index)),
filepath.Join(r.path, string(backend.Lock)),
filepath.Join(r.path, string(backend.Key)),
}
for _, d := range dirs {
if _, errs := os.Stat(d); errs != nil {
errmk := os.MkdirAll(d, backend.Modes.Dir)
if errmk != nil {
return errmk
}
}
}
return nil
}
func (r *Repository) HasConfig() bool {
file := filepath.Join(r.path, string(backend.Config))
if _, err := os.Stat(file); err != nil {
return false
}
return true
}
func (r *Repository) ReadConfig() ([]byte, error) {
file := filepath.Join(r.path, string(backend.Config))
return ioutil.ReadFile(file)
}
func (r *Repository) WriteConfig(data []byte) error {
file := filepath.Join(r.path, string(backend.Config))
return ioutil.WriteFile(file, data, backend.Modes.File)
}
func (r *Repository) ListBlob(t backend.Type) ([]string, error) {
var blobs []string
dir := filepath.Join(r.path, string(t))
files, err := ioutil.ReadDir(dir)
if err != nil {
return blobs, err
}
blobs = make([]string, len(files))
for i, f := range files {
blobs[i] = f.Name()
}
return blobs, nil
}
func (r *Repository) HasBlob(bt backend.Type, id backend.ID) bool {
file := filepath.Join(r.path, string(bt), id.String())
if _, err := os.Stat(file); err != nil {
return false
}
return true
}
func (r *Repository) ReadBlob(bt backend.Type, id backend.ID) (*os.File, error) {
file := filepath.Join(r.path, string(bt), id.String())
f, err := os.Open(file)
if err != nil {
return f, err
}
return f, nil
}
func (r *Repository) WriteBlob(bt backend.Type, id backend.ID, data []byte) error {
file := filepath.Join(r.path, string(bt), id.String())
return ioutil.WriteFile(file, data, backend.Modes.File)
}
func (r *Repository) DeleteBlob(bt backend.Type, id backend.ID) error {
file := filepath.Join(r.path, string(bt), id.String())
return os.Remove(file)
} }

View file

@ -1,25 +1,26 @@
package main package main
import ( import (
"errors"
"log" "log"
"net/http" "net/http"
"regexp"
"strings" "strings"
"github.com/restic/restic/backend" "github.com/restic/restic/backend"
) )
// Route all the server requests type Router struct {
func Router(w http.ResponseWriter, r *http.Request) { Context
}
func (router Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
m := r.Method m := r.Method
u := r.RequestURI u := r.RequestURI
log.Println("%s %s", m, u) log.Printf("%s %s", m, u)
if Authorize(r) { if Authorize(r) {
if handler := RestAPI(m, u); handler != nil { if handler := RestAPI(m, u); handler != nil {
handler(w, r, nil) handler(w, r, &router.Context)
} else { } else {
http.Error(w, "not found", 404) http.Error(w, "not found", 404)
} }
@ -28,42 +29,6 @@ func Router(w http.ResponseWriter, r *http.Request) {
} }
} }
// Returns the repository name for a given path
func RepositoryName(u string) (string, error) {
s := strings.Split(u, "/")
if len(s) <= 1 {
return "", errors.New("path does not contain repository name")
}
if len(s[1]) < 1 {
return "", errors.New("repository name should contain at least 1 character")
}
match, err := regexp.MatchString("^[a-zA-Z0-9_-]*$", s[1])
if !match || err != nil {
return "", errors.New("repository name should not contains special characters")
}
return s[1], nil
}
// Returns the backend type for a given path
func BackendType(u string) backend.Type {
s := strings.Split(u, "/")
var bt backend.Type
if len(s) > 2 {
bt, _ = backend.ParseType(s[2])
}
return bt
}
// Returns the blob ID for a given path
func BlobID(u string) backend.ID {
s := strings.Split(u, "/")
var id backend.ID
if len(s) > 3 {
id, _ = backend.ParseID(s[3])
}
return id
}
// The Rest API returns a Handler when a match occur or nil. // The Rest API returns a Handler when a match occur or nil.
func RestAPI(m string, u string) Handler { func RestAPI(m string, u string) Handler {
s := strings.Split(u, "/") s := strings.Split(u, "/")

View file

@ -1,10 +1,28 @@
package main package main
import () import (
//"io/ioutil"
"log"
"net/http"
)
func main() { func main() {
//path, _ := ioutil.TempDir("", "restic-repository-") //path, _ := ioutil.TempDir("", "restic-repository-")
//log.Printf("initialize context at %s", path)
//http.ListenAndServe(":8000", r) context := Context{"/tmp/restic"}
repo, _ := context.Repository("repo")
repo.Init()
errc := context.Init()
if errc != nil {
log.Println("context initialization failed")
return
}
router := Router{context}
port := ":8000"
log.Printf("start server on port %s", port)
http.ListenAndServe(port, router)
} }