package storage import ( "context" "fmt" "io" "os" "path/filepath" "git.nakama.town/fmartingr/hako/internal/model" "github.com/google/uuid" ) // LocalStorage implements Storage interface using local filesystem type LocalStorage struct { basePath string } // NewLocalStorage creates a new LocalStorage instance func NewLocalStorage(basePath string) *LocalStorage { return &LocalStorage{ basePath: basePath, } } // Save stores a file at basePath/linkID/filename // Returns the relative storage path func (s *LocalStorage) Save(ctx context.Context, linkID string, filename string, reader io.Reader) (string, error) { // Get relative path for link directory linkDirRelPath := model.GetPathForLink(linkID) linkDir := filepath.Join(s.basePath, linkDirRelPath) if err := os.MkdirAll(linkDir, 0755); err != nil { return "", fmt.Errorf("failed to create directory: %w", err) } // Generate a unique filename to avoid collisions ext := filepath.Ext(filename) randomName := uuid.New().String() + ext // Get relative path for the file relPath := model.GetPathForLinkFile(linkID, randomName) fullPath := filepath.Join(s.basePath, relPath) // Create the file file, err := os.Create(fullPath) if err != nil { return "", fmt.Errorf("failed to create file: %w", err) } defer func() { _ = file.Close() }() // Copy data from reader to file if _, err := io.Copy(file, reader); err != nil { return "", fmt.Errorf("failed to write file: %w", err) } return relPath, nil } // Get retrieves a file from storage func (s *LocalStorage) Get(ctx context.Context, path string) (io.ReadCloser, error) { fullPath := filepath.Join(s.basePath, path) file, err := os.Open(fullPath) if err != nil { return nil, fmt.Errorf("failed to open file: %w", err) } return file, nil } // Delete removes a file from storage func (s *LocalStorage) Delete(ctx context.Context, path string) error { fullPath := filepath.Join(s.basePath, path) if err := os.Remove(fullPath); err != nil { return fmt.Errorf("failed to delete file: %w", err) } return nil } // Exists checks if a file exists in storage func (s *LocalStorage) Exists(ctx context.Context, path string) (bool, error) { fullPath := filepath.Join(s.basePath, path) _, err := os.Stat(fullPath) if err != nil { if os.IsNotExist(err) { return false, nil } return false, fmt.Errorf("failed to check file existence: %w", err) } return true, nil } // DeleteDirectory removes an entire link directory and all its contents func (s *LocalStorage) DeleteDirectory(ctx context.Context, linkID string) error { linkDirRelPath := model.GetPathForLink(linkID) linkDir := filepath.Join(s.basePath, linkDirRelPath) if err := os.RemoveAll(linkDir); err != nil { return fmt.Errorf("failed to delete link directory: %w", err) } return nil }