2023-06-28 17:21:59 +02:00
|
|
|
package file
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
2023-11-13 21:01:12 +01:00
|
|
|
"fmt"
|
|
|
|
"io/fs"
|
|
|
|
"os"
|
2023-06-28 17:21:59 +02:00
|
|
|
"path/filepath"
|
2023-11-13 21:01:12 +01:00
|
|
|
"strings"
|
2023-07-10 13:23:46 +02:00
|
|
|
"sync"
|
2023-06-28 17:21:59 +02:00
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
"git.andreafazzi.eu/andrea/probo/store"
|
2023-06-28 17:21:59 +02:00
|
|
|
)
|
|
|
|
|
2023-10-07 11:43:12 +02:00
|
|
|
var (
|
|
|
|
ErrorMetaHeaderIsNotPresent = errors.New("Meta header was not found in file.")
|
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
BaseDir = "data"
|
|
|
|
QuizzesDir = "quizzes"
|
|
|
|
CollectionsDir = "collections"
|
2023-10-07 11:43:12 +02:00
|
|
|
)
|
2023-09-22 10:29:10 +02:00
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
type Storer[T store.Storable] interface {
|
|
|
|
store.Storer[T]
|
|
|
|
// store.FilterStorer[T]
|
|
|
|
}
|
2023-06-28 17:21:59 +02:00
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
type FileStore[T store.Storable, K Storer[T]] struct {
|
|
|
|
Storer K
|
2023-10-07 11:43:12 +02:00
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
Dir string
|
|
|
|
FilePrefix string
|
|
|
|
FileSuffix string
|
2023-10-07 11:43:12 +02:00
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
MarshalFunc func(K, string, []byte) (T, error)
|
|
|
|
UnmarshalFunc func(K, string, T) error
|
2023-07-10 13:23:46 +02:00
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
lock sync.RWMutex
|
|
|
|
paths map[string]string
|
2023-06-28 17:21:59 +02:00
|
|
|
}
|
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
func NewFileStore[T store.Storable, K Storer[T]](
|
|
|
|
storer K,
|
|
|
|
dir string,
|
|
|
|
prefix string,
|
|
|
|
suffix string,
|
|
|
|
marshalFunc func(K, string, []byte) (T, error),
|
|
|
|
unmarshalFunc func(K, string, T) error,
|
|
|
|
) (*FileStore[T, K], error) {
|
|
|
|
store := &FileStore[T, K]{
|
|
|
|
Storer: storer,
|
|
|
|
Dir: dir,
|
|
|
|
FilePrefix: prefix,
|
|
|
|
FileSuffix: suffix,
|
|
|
|
MarshalFunc: marshalFunc,
|
|
|
|
UnmarshalFunc: unmarshalFunc,
|
|
|
|
paths: make(map[string]string, 0),
|
|
|
|
}
|
|
|
|
|
|
|
|
err := store.IndexDir()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return store, nil
|
|
|
|
}
|
2023-06-28 17:21:59 +02:00
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
func (s *FileStore[T, K]) Create(entity T) (T, error) {
|
|
|
|
e, err := s.Storer.Create(entity)
|
|
|
|
if err != nil {
|
|
|
|
return e, err
|
|
|
|
}
|
2023-07-10 13:23:46 +02:00
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
filePath := filepath.Join(s.Dir, fmt.Sprintf("%s_%v%s", s.FilePrefix, e.GetID(), s.FileSuffix))
|
2023-10-07 11:43:12 +02:00
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
err = s.UnmarshalFunc(s.Storer, filePath, e)
|
2023-06-28 17:21:59 +02:00
|
|
|
if err != nil {
|
2023-11-13 21:01:12 +01:00
|
|
|
return e, err
|
2023-06-28 17:21:59 +02:00
|
|
|
}
|
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
s.SetPath(e, filePath)
|
|
|
|
|
|
|
|
return e, nil
|
2023-07-10 13:23:46 +02:00
|
|
|
}
|
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
func (s *FileStore[T, K]) Update(entity T, id string) (T, error) {
|
|
|
|
e, err := s.Storer.Update(entity, id)
|
|
|
|
if err != nil {
|
|
|
|
return e, err
|
|
|
|
}
|
2023-10-07 11:43:12 +02:00
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
filePath := filepath.Join(s.Dir, fmt.Sprintf("%s_%v%s", s.FilePrefix, e.GetID(), s.FileSuffix))
|
2023-10-07 11:43:12 +02:00
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
err = s.UnmarshalFunc(s.Storer, filePath, e)
|
2023-10-07 11:43:12 +02:00
|
|
|
if err != nil {
|
2023-11-13 21:01:12 +01:00
|
|
|
return e, err
|
2023-10-07 11:43:12 +02:00
|
|
|
}
|
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
s.SetPath(e, filePath)
|
|
|
|
|
|
|
|
return e, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *FileStore[T, K]) Read(id string) (T, error) {
|
|
|
|
return s.Storer.Read(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *FileStore[T, K]) ReadAll() []T {
|
|
|
|
return s.Storer.ReadAll()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *FileStore[T, K]) Delete(id string) (T, error) {
|
|
|
|
e, err := s.Storer.Delete(id)
|
|
|
|
if err != nil {
|
|
|
|
return e, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = os.Remove(s.GetPath(e))
|
|
|
|
if err != nil {
|
|
|
|
return e, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return e, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *FileStore[T, K]) IndexDir() error {
|
|
|
|
files, err := os.ReadDir(s.Dir)
|
2023-10-07 11:43:12 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2023-07-10 13:23:46 +02:00
|
|
|
}
|
2023-06-28 17:21:59 +02:00
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
entityFiles := make([]fs.DirEntry, 0)
|
|
|
|
|
|
|
|
for _, file := range files {
|
|
|
|
filename := file.Name()
|
|
|
|
if !file.IsDir() && strings.HasSuffix(filename, s.FileSuffix) {
|
|
|
|
entityFiles = append(entityFiles, file)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, file := range entityFiles {
|
|
|
|
filename := file.Name()
|
|
|
|
fullPath := filepath.Join(s.Dir, filename)
|
|
|
|
|
|
|
|
content, err := os.ReadFile(fullPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
entity, err := s.MarshalFunc(s.Storer, fullPath, content)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
s.SetPath(entity, fullPath)
|
|
|
|
}
|
|
|
|
|
2023-07-10 13:23:46 +02:00
|
|
|
return nil
|
2023-11-13 21:01:12 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *FileStore[T, K]) GetPath(entity T) string {
|
|
|
|
s.lock.RLock()
|
|
|
|
defer s.lock.RUnlock()
|
|
|
|
|
|
|
|
return s.paths[entity.GetID()]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *FileStore[T, K]) SetPath(entity T, path string) string {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
s.paths[entity.GetID()] = path
|
|
|
|
|
|
|
|
return path
|
2023-06-28 17:21:59 +02:00
|
|
|
}
|