From 59621ca2d69aa145878d3ae7cc334f7f7a1aee8f Mon Sep 17 00:00:00 2001 From: Chapuis Bertil Date: Sat, 15 Aug 2015 10:06:10 +0200 Subject: [PATCH] read support for length and offset --- auth.go | 1 - context.go | 21 +++++- handlers.go | 201 +++++++++++++++++++++++++++++++++++++++++++++++--- repository.go | 87 +++++++++++++++++++++- router.go | 49 ++---------- server.go | 24 +++++- 6 files changed, 321 insertions(+), 62 deletions(-) diff --git a/auth.go b/auth.go index 48a0555..4171697 100644 --- a/auth.go +++ b/auth.go @@ -1,7 +1,6 @@ package main import ( - "log" "net/http" ) diff --git a/context.go b/context.go index dadeb09..d1907b9 100644 --- a/context.go +++ b/context.go @@ -1,6 +1,11 @@ package main -import () +import ( + "os" + "path/filepath" + + "github.com/restic/restic/backend" +) // A Context specifies the root directory where all repositories are stored type Context struct { @@ -8,10 +13,18 @@ type Context struct { } func NewContext(path string) Context { - return Context{path} + return Context{filepath.Clean(path)} } // 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 } diff --git a/handlers.go b/handlers.go index 41bb284..875ec0a 100644 --- a/handlers.go +++ b/handlers.go @@ -1,40 +1,223 @@ package main import ( - "fmt" + "encoding/json" + "io/ioutil" "net/http" + "time" ) type Handler func(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) { - 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) { - 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) { - 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) { - 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) { - 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) { - 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) { - 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 + } } diff --git a/repository.go b/repository.go index 57cf0f9..04247f8 100644 --- a/repository.go +++ b/repository.go @@ -1,6 +1,12 @@ package main -import () +import ( + "io/ioutil" + "os" + "path/filepath" + + "github.com/restic/restic/backend" +) // A Repository is the place where backups are stored type Repository struct { @@ -8,6 +14,81 @@ type Repository struct { } // 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) } diff --git a/router.go b/router.go index 96ad7d4..4c2d3be 100644 --- a/router.go +++ b/router.go @@ -1,25 +1,26 @@ package main import ( - "errors" "log" "net/http" - "regexp" "strings" "github.com/restic/restic/backend" ) -// Route all the server requests -func Router(w http.ResponseWriter, r *http.Request) { +type Router struct { + Context +} + +func (router Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { m := r.Method u := r.RequestURI - log.Println("%s %s", m, u) + log.Printf("%s %s", m, u) if Authorize(r) { if handler := RestAPI(m, u); handler != nil { - handler(w, r, nil) + handler(w, r, &router.Context) } else { 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. func RestAPI(m string, u string) Handler { s := strings.Split(u, "/") diff --git a/server.go b/server.go index 6281204..20a869a 100644 --- a/server.go +++ b/server.go @@ -1,10 +1,28 @@ package main -import () +import ( + //"io/ioutil" + "log" + "net/http" +) func main() { - //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) }