package file import ( "errors" "fmt" "io/fs" "os" "path/filepath" "strings" "sync" "git.andreafazzi.eu/andrea/probo/store" ) var ( ErrorMetaHeaderIsNotPresent = errors.New("Meta header was not found in file.") BaseDir = "data" QuizzesDir = "quizzes" CollectionsDir = "collections" ) type Storer[T store.Storable] interface { store.Storer[T] // store.FilterStorer[T] } type FileStore[T store.Storable, K Storer[T]] struct { Storer K Dir string FilePrefix string FileSuffix string MarshalFunc func(K, string, []byte) (T, error) UnmarshalFunc func(K, string, T) error lock sync.RWMutex paths map[string]string } 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 } func (s *FileStore[T, K]) Create(entity T) (T, error) { e, err := s.Storer.Create(entity) if err != nil { return e, err } filePath := filepath.Join(s.Dir, fmt.Sprintf("%s_%v%s", s.FilePrefix, e.GetID(), s.FileSuffix)) err = s.UnmarshalFunc(s.Storer, filePath, e) if err != nil { return e, err } s.SetPath(e, filePath) return e, nil } 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 } filePath := filepath.Join(s.Dir, fmt.Sprintf("%s_%v%s", s.FilePrefix, e.GetID(), s.FileSuffix)) err = s.UnmarshalFunc(s.Storer, filePath, e) if err != nil { return e, err } 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) if err != nil { return err } 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) } return nil } 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 }