diff --git a/store/.gitignore b/store/.gitignore new file mode 100644 index 000000000..32bcdf1ac --- /dev/null +++ b/store/.gitignore @@ -0,0 +1 @@ +/test \ No newline at end of file diff --git a/store/interfaces.go b/store/interfaces.go new file mode 100644 index 000000000..8a8f145d1 --- /dev/null +++ b/store/interfaces.go @@ -0,0 +1,23 @@ +package store + +import "io" + +type ( + store struct { + namespace string + + originalFn func(id uint64, ext string) string + previewFn func(id uint64, ext string) string + } + + Store interface { + Namespace() string + + Original(id uint64, ext string) string + Preview(id uint64, ext string) string + + Save(filename string, contents io.Reader) error + Remove(filename string) error + Open(filename string) (io.Reader, error) + } +) diff --git a/store/store.go b/store/store.go new file mode 100644 index 000000000..acf68804c --- /dev/null +++ b/store/store.go @@ -0,0 +1,75 @@ +package store + +import ( + "fmt" + "io" + "path" + + "github.com/spf13/afero" + "github.com/pkg/errors" +) + +func New(namespace string) (Store, error) { + return &store{ + namespace: namespace, + originalFn: func(id uint64, ext string) string { + return fmt.Sprintf("%d.%s", id, ext) + }, + previewFn: func(id uint64, ext string) string { + return fmt.Sprintf("%d_preview.%s", id, ext) + }, + }, nil +} + +func (s *store) Namespace() string { + return s.namespace +} + +func (s *store) check(filename string) error { + if filename[:len(s.namespace)+1] != s.namespace + "/" { + return errors.Errorf("Invalid namespace when trying to store file: %s (for %s)", filename, s.namespace) + } + return nil +} + +func (s *store) Original(id uint64, ext string) string { + return path.Join(s.namespace, s.originalFn(id, ext)) +} + +func (s *store) Preview(id uint64, ext string) string { + return path.Join(s.namespace, s.previewFn(id, ext)) +} + +func (s *store) Save(filename string, contents io.Reader) error { + // check filename for validity + if err := s.check(filename); err != nil { + return err + } + + folder := path.Dir(filename) + + fs := afero.NewOsFs() + fs.MkdirAll(folder, 0755) + + return afero.WriteReader(fs, filename, contents) +} + +func (s *store) Remove(filename string) error { + // check filename for validity + if err := s.check(filename); err != nil { + return err + } + + fs := afero.NewOsFs() + return fs.Remove(filename) +} + +func (s *store) Open(filename string) (io.Reader, error) { + // check filename for validity + if err := s.check(filename); err != nil { + return nil, err + } + + fs := afero.NewOsFs() + return fs.Open(filename) +} diff --git a/store/store_test.go b/store/store_test.go new file mode 100644 index 000000000..384bcee2e --- /dev/null +++ b/store/store_test.go @@ -0,0 +1,72 @@ +package store_test + +import ( + "io" + "bytes" + "testing" + + "github.com/crusttech/crust/store" +) + +func TestStore(t *testing.T) { + assert := func(ok bool, format string, params ...interface{}) { + if !ok { + t.Fatalf(format, params...) + } + } + readerToString := func(r io.Reader) string { + b := new(bytes.Buffer) + b.ReadFrom(r) + return b.String() + } + + store, err := store.New("test") + assert(err == nil, "Unexpected error when creating store: %+v", err) + assert(store != nil, "Expected non-nil return for new store") + assert(store.Namespace() == "test", "Unexpected store namespace: test != %s", store.Namespace()) + + { + fn := store.Original(123, "jpg") + expected := "test/123.jpg" + assert(fn == expected, "Unexpected filename returned: %s != %s", expected, fn) + } + + { + fn := store.Preview(123, "jpg") + expected := "test/123_preview.jpg" + assert(fn == expected, "Unexpected filename returned: %s != %s", expected, fn) + } + + // write a file + { + buf := bytes.NewBuffer([]byte("This is a testing buffer")) + err := store.Save("test/123.jpg", buf) + assert(err == nil, "Error saving file, %+v", err) + + err = store.Save("test123/123.jpg", buf) + assert(err != nil, "Expected error when saving file outside of namespace") + } + + // read a file + { + buf, err := store.Open("test/123.jpg") + assert(err == nil, "Unexpected error when reading file: %+v", err) + s := readerToString(buf) + assert(s == "This is a testing buffer", "Unexpected response when reading file: %s", s) + + _, err = store.Open("test/1234.jpg") + assert(err != nil, "Expected error when opening non-existent file") + _, err = store.Open("test123/123.jpg") + assert(err != nil, "Expected error when opening file outside of namespace") + } + + // delete a file + { + err := store.Remove("test/123.jpg") + assert(err == nil, "Unexpected error when removing file: %+v", err) + err = store.Remove("test/123.jpg") + assert(err != nil, "Expected error when removing missing file") + err = store.Remove("test123/123.jpg") + assert(err != nil, "Expected error when deleting file outside of namespace") + } +}