From 3aa4d62526537ec0fe624899a2f05566ea80a198 Mon Sep 17 00:00:00 2001 From: "Pablo M. Bermudo Garay" Date: Wed, 9 Feb 2022 21:37:44 +0100 Subject: Add and use file backend --- .gitignore | 1 + main.go | 32 +++++++++---- objectstore/file_backend.go | 110 ++++++++++++++++++++++++++++++++++++++++++++ objectstore/objecstore.go | 2 +- 4 files changed, 134 insertions(+), 11 deletions(-) create mode 100644 objectstore/file_backend.go diff --git a/.gitignore b/.gitignore index c5a22e2..2cea1dc 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ storehouse +data/ diff --git a/main.go b/main.go index c4104e3..36f51e4 100644 --- a/main.go +++ b/main.go @@ -5,9 +5,11 @@ package main import ( + "errors" "io/ioutil" "log" "net/http" + "os" "github.com/labstack/echo/v4" "github.com/pablombg/storehouse/objectstore" @@ -18,7 +20,8 @@ type storehouse struct { } func InitStorehouse() *storehouse { - objects := objectstore.NewMemBackend() + // objects := objectstore.NewMemBackend() + objects := objectstore.NewFileBackend("data") return &storehouse{objects: objects} } @@ -26,16 +29,15 @@ func (s *storehouse) putObject(c echo.Context) error { bucketId := c.Param("bucketId") objectId := c.Param("objectId") - // Get body as a string + // Get request body bodyBytes, err := ioutil.ReadAll(c.Request().Body) if err != nil { log.Printf("Error reading body: %v", err) m := &errorJSON{Error: "Error reading body"} return c.JSON(http.StatusInternalServerError, m) } - content := string(bodyBytes) - err = s.objects.CreateObject(bucketId, objectId, content) + err = s.objects.CreateObject(bucketId, objectId, bodyBytes) // Check errors creating the object if err != nil { log.Printf("Error creating the object: %v", err) @@ -61,10 +63,15 @@ func (s *storehouse) getObject(c echo.Context) error { objectId := c.Param("objectId") object, err := s.objects.GetObject(bucketId, objectId) - if err != nil { - log.Println(err) - m := &errorJSON{Error: "Object not found"} + if errors.Is(err, os.ErrNotExist) { + msg := "Object not found" + log.Println(msg) + m := &errorJSON{Error: msg} return c.JSON(http.StatusNotFound, m) + } else if err != nil { + log.Println(err) + m := &errorJSON{Error: "Internal error"} + return c.JSON(http.StatusInternalServerError, m) } return c.String(http.StatusOK, object) @@ -75,10 +82,15 @@ func (s *storehouse) deleteObject(c echo.Context) error { objectId := c.Param("objectId") err := s.objects.DeleteObject(bucketId, objectId) - if err != nil { - log.Println(err) - m := &errorJSON{Error: "Object not found"} + if errors.Is(err, os.ErrNotExist) { + msg := "Object not found" + log.Println(msg) + m := &errorJSON{Error: msg} return c.JSON(http.StatusNotFound, m) + } else if err != nil { + log.Println(err) + m := &errorJSON{Error: "Internal error"} + return c.JSON(http.StatusInternalServerError, m) } m := &infoJSON{Info: "Object deleted"} diff --git a/objectstore/file_backend.go b/objectstore/file_backend.go new file mode 100644 index 0000000..df2d6bd --- /dev/null +++ b/objectstore/file_backend.go @@ -0,0 +1,110 @@ +// Basic in-memory object store +// +// Licensed under CC0 1.0 Universal: +// https://creativecommons.org/publicdomain/zero/1.0/legalcode +package objectstore + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "path" + "sync" +) + +type FileObjectStore struct { + sync.Mutex + dataPath string +} + +func NewFileBackend(dataPath string) *FileObjectStore { + err := os.MkdirAll(dataPath, 0755) + if err != nil { + panic(err) + } + fos := &FileObjectStore{dataPath: dataPath} + return fos +} + +func (fos *FileObjectStore) CreateObject(bucketId string, objectId string, payload []byte) error { + fos.Lock() + defer fos.Unlock() + + // Check if the bucket exists + buckets, err := ioutil.ReadDir(fos.dataPath) + if err != nil { + return fmt.Errorf("Error reading data directory") + } + + bucketFound := false + for _, f := range buckets { + if f.Name() == bucketId && f.IsDir() { + bucketFound = true + break + } + } + + // Create bucket if it doesn't exist + if !bucketFound { + err := os.Mkdir(path.Join(fos.dataPath, bucketId), 0755) + if err != nil { + return fmt.Errorf("Error creating bucket directory: %v", bucketId) + } + } + + // Store the object + err = os.WriteFile(path.Join(fos.dataPath, bucketId, objectId), payload, 0644) + if err != nil { + return fmt.Errorf("Error creating the object file %v", bucketId) + } + + return nil +} + +func (fos *FileObjectStore) GetObject(bucketId string, objectId string) (string, error) { + fos.Lock() + defer fos.Unlock() + + objectPath := path.Join(fos.dataPath, bucketId, objectId) + object, err := os.ReadFile(objectPath) + if err != nil { + return "", os.ErrNotExist + } + + return string(object), nil +} + +func (fos *FileObjectStore) DeleteObject(bucketId string, objectId string) error { + fos.Lock() + defer fos.Unlock() + + objectPath := path.Join(fos.dataPath, bucketId, objectId) + + // Check whether the file exists + _, err := os.Stat(objectPath) + if errors.Is(err, os.ErrNotExist) { + return err + } + + // Delete file + err = os.Remove(objectPath) + if err != nil { + return err + } + + // Delete the bucket directory if it's empty + bucketPath := path.Join(fos.dataPath, bucketId) + entries, err := ioutil.ReadDir(bucketPath) + if err != nil { + fmt.Println(err) + } + if len(entries) == 0 { + err := os.Remove(bucketPath) + if err != nil { + fmt.Println(err) + } + } + + return nil +} diff --git a/objectstore/objecstore.go b/objectstore/objecstore.go index f4985c9..7cbb481 100644 --- a/objectstore/objecstore.go +++ b/objectstore/objecstore.go @@ -9,7 +9,7 @@ import ( ) type ObjectStore interface { - CreateObject(bucketId string, objectId string, content string) error + CreateObject(bucketId string, objectId string, content []byte) error GetObject(bucketId string, objectId string) (string, error) DeleteObject(bucketId string, objectId string) error } -- cgit v1.2.3-70-g09d2