From d53235d8ef36d7d7855d06a63659e5a86952f4e9 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 3 Dec 2021 23:51:48 +0100 Subject: [PATCH] Add filesystem store --- store/filesystem.go | 94 ++++++++++++++++++++++++++++++++++++++++ store/filesystem_test.go | 22 ++++++++++ store/memory_test.go | 12 +++++ store/store_test.go | 52 ++++++++++++++++++++++ 4 files changed, 180 insertions(+) create mode 100644 store/filesystem.go create mode 100644 store/filesystem_test.go create mode 100644 store/memory_test.go create mode 100644 store/store_test.go diff --git a/store/filesystem.go b/store/filesystem.go new file mode 100644 index 0000000..2906d39 --- /dev/null +++ b/store/filesystem.go @@ -0,0 +1,94 @@ +package store + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "gitea.benny.dog/torjus/ezshare/pb" + "github.com/google/uuid" +) + +var _ FileStore = &FileSystemStore{} + +type FileSystemStore struct { + dir string +} + +func NewFileSystemStore(dir string) *FileSystemStore { + return &FileSystemStore{dir: dir} +} + +func (s *FileSystemStore) GetFile(id string) (*pb.File, error) { + path := filepath.Join(s.dir, id) + f, err := os.Open(path) + if err != nil { + if os.IsNotExist(os.ErrNotExist) { + return nil, ErrNoSuchFile + } + return nil, fmt.Errorf("unable to open file: %w", err) + } + defer f.Close() + + fm, err := os.Open(fmt.Sprintf("%s.metadata", path)) + if err != nil { + // TODO: Different error if file not found + return nil, fmt.Errorf("unable to open file: %w", err) + } + defer fm.Close() + + var file pb.File + if file.Data, err = ioutil.ReadAll(f); err != nil { + return nil, fmt.Errorf("unable to read file: %w", err) + } + decoder := json.NewDecoder(fm) + if err := decoder.Decode(&file.Metadata); err != nil { + return nil, fmt.Errorf("unable to read file metadata: %w", err) + } + + return &file, nil +} + +func (s *FileSystemStore) StoreFile(file *pb.File) (string, error) { + id := uuid.Must(uuid.NewRandom()).String() + file.FileId = id + + path := filepath.Join(s.dir, file.FileId) + f, err := os.Create(path) + if err != nil { + return "", fmt.Errorf("unable to store file: %w", err) + } + defer f.Close() + + fm, err := os.Create(fmt.Sprintf("%s.metadata", path)) + if err != nil { + return "", fmt.Errorf("unable to store metadata: %w", err) + } + defer fm.Close() + + if _, err := f.Write(file.Data); err != nil { + return "", fmt.Errorf("unable to write file to store: %w", err) + } + + // TODO: Write metadata + encoder := json.NewEncoder(fm) + if err := encoder.Encode(file.Metadata); err != nil { + return "", fmt.Errorf("unable to write file to store: %w", err) + } + return file.FileId, nil +} + +func (s *FileSystemStore) DeleteFile(id string) error { + path := filepath.Join(s.dir, id) + metadataPath := fmt.Sprintf("%s.metadata", path) + if err := os.Remove(path); err != nil { + return fmt.Errorf("error deleting file: %w", err) + } + if err := os.Remove(metadataPath); err != nil { + return fmt.Errorf("error deleting file metadata: %w", err) + } + + return nil +} diff --git a/store/filesystem_test.go b/store/filesystem_test.go new file mode 100644 index 0000000..ed109e6 --- /dev/null +++ b/store/filesystem_test.go @@ -0,0 +1,22 @@ +package store_test + +import ( + "io/ioutil" + "os" + "testing" + + "gitea.benny.dog/torjus/ezshare/store" +) + +func TestFileStore(t *testing.T) { + dir, err := ioutil.TempDir(os.TempDir(), "ezshare-test") + if err != nil { + t.Fatalf("unable to create temp-dir") + } + defer func() { + _ = os.RemoveAll(dir) + }() + + s := store.NewFileSystemStore(dir) + doFileStoreTest(s, t) +} diff --git a/store/memory_test.go b/store/memory_test.go new file mode 100644 index 0000000..fcd0091 --- /dev/null +++ b/store/memory_test.go @@ -0,0 +1,12 @@ +package store_test + +import ( + "testing" + + "gitea.benny.dog/torjus/ezshare/store" +) + +func TestMemoryStore(t *testing.T) { + s := store.NewMemoryFileStore() + doFileStoreTest(s, t) +} diff --git a/store/store_test.go b/store/store_test.go new file mode 100644 index 0000000..d111888 --- /dev/null +++ b/store/store_test.go @@ -0,0 +1,52 @@ +package store_test + +import ( + "testing" + "time" + + "gitea.benny.dog/torjus/ezshare/pb" + "gitea.benny.dog/torjus/ezshare/store" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func doFileStoreTest(s store.FileStore, t *testing.T) { + t.Run("Basics", func(t *testing.T) { + // Create + file := &pb.File{ + Data: []byte("testdata lol!"), + Metadata: &pb.File_Metadata{ + UploadedOn: timestamppb.New(time.Now()), + ExpiresOn: timestamppb.New(time.Now().Add(24 * time.Hour)), + OriginalFilename: "data.txt", + }, + } + + id, err := s.StoreFile(file) + if err != nil { + t.Fatalf("Unable to store file: %s", err) + } + + retrieved, err := s.GetFile(id) + if err != nil { + t.Fatalf("Unable to get file: %s", err) + } + + if len(file.Data) != len(retrieved.Data) { + t.Fatalf("Mismatch in size between stored and retrieved. Got %d want %d", len(retrieved.Data), len(file.Data)) + } + + for i := range file.Data { + if file.Data[i] != retrieved.Data[i] { + t.Fatalf("Mismatch at %d", i) + } + } + + if err := s.DeleteFile(id); err != nil { + t.Fatalf("Unable to delete file: %s", err) + } + + if _, err := s.GetFile(id); err != store.ErrNoSuchFile { + t.Fatalf("Getting deleted file returned wrong error: %s", err) + } + }) +}