package store import ( "crypto/rand" "encoding/hex" "fmt" "io" "os" "path/filepath" ) type Store struct { dir string } func New() (*Store, error) { dir := filepath.Join(os.TempDir(), "aroll-uploads") if err := os.MkdirAll(dir, 0755); err != nil { return nil, fmt.Errorf("create upload dir: %w", err) } return &Store{dir: dir}, nil } // Save streams r into a new file in the store and returns its key. func (s *Store) Save(r io.Reader, ext string) (string, error) { key := "video-" + NewID() + ext path := filepath.Join(s.dir, key) f, err := os.Create(path) if err != nil { return "", fmt.Errorf("create file: %w", err) } defer f.Close() if _, err := io.Copy(f, r); err != nil { os.Remove(path) return "", fmt.Errorf("write file: %w", err) } return key, nil } // Path returns the absolute path for a key, if the file exists. func (s *Store) Path(key string) (string, bool) { path := filepath.Join(s.dir, key) if _, err := os.Stat(path); err != nil { return "", false } return path, true } // Allocate returns a path for a new key without creating the file. // Used by ExportHandler so ffmpeg can write directly to the store. func (s *Store) Allocate(key string) string { return filepath.Join(s.dir, key) } // Delete removes the file for a key. func (s *Store) Delete(key string) { os.Remove(filepath.Join(s.dir, key)) } func NewID() string { b := make([]byte, 16) rand.Read(b) return hex.EncodeToString(b) }