package mila import ( "context" "encoding/json" "errors" "os" "path/filepath" "sync" ) type FileStore struct { path string mu sync.Mutex mem *MemoryStore } func NewFileStore(path string) *FileStore { return &FileStore{ path: path, mem: NewMemoryStore(), } } func (s *FileStore) Get(ctx context.Context) (ContentState, error) { if state, err := s.mem.Get(ctx); err == nil { return state, nil } s.mu.Lock() defer s.mu.Unlock() data, err := os.ReadFile(s.path) if err != nil { if errors.Is(err, os.ErrNotExist) { return ContentState{}, ErrNotReady } return ContentState{}, err } var state ContentState if err := json.Unmarshal(data, &state); err != nil { return ContentState{}, err } if state.IsZero() { return ContentState{}, ErrNotReady } if err := s.mem.Set(ctx, state); err != nil { return ContentState{}, err } return state, nil } func (s *FileStore) Set(ctx context.Context, state ContentState) error { select { case <-ctx.Done(): return ctx.Err() default: } s.mu.Lock() defer s.mu.Unlock() if err := os.MkdirAll(filepath.Dir(s.path), 0o755); err != nil { return err } data, err := json.MarshalIndent(state, "", " ") if err != nil { return err } tmp := s.path + ".tmp" if err := os.WriteFile(tmp, data, 0o644); err != nil { return err } if err := os.Rename(tmp, s.path); err != nil { _ = os.Remove(tmp) return err } return s.mem.Set(ctx, state) }