217 lines
6.3 KiB
Go
217 lines
6.3 KiB
Go
// Package storage handles file storage for packages and assets
|
|
package storage
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// Storage manages file storage on the local filesystem
|
|
type Storage struct {
|
|
basePath string
|
|
}
|
|
|
|
// New creates a new Storage instance
|
|
func New(basePath string) (*Storage, error) {
|
|
// Create base directories if they don't exist
|
|
dirs := []string{
|
|
filepath.Join(basePath, "packages"),
|
|
filepath.Join(basePath, "assets"),
|
|
filepath.Join(basePath, "temp"),
|
|
}
|
|
|
|
for _, dir := range dirs {
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return nil, fmt.Errorf("failed to create directory %s: %w", dir, err)
|
|
}
|
|
}
|
|
|
|
return &Storage{basePath: basePath}, nil
|
|
}
|
|
|
|
// BasePath returns the base storage path
|
|
func (s *Storage) BasePath() string {
|
|
return s.basePath
|
|
}
|
|
|
|
// PackagesPath returns the path to the packages directory
|
|
func (s *Storage) PackagesPath() string {
|
|
return filepath.Join(s.basePath, "packages")
|
|
}
|
|
|
|
// AssetsPath returns the path to the assets directory
|
|
func (s *Storage) AssetsPath() string {
|
|
return filepath.Join(s.basePath, "assets")
|
|
}
|
|
|
|
// TempPath returns the path to the temp directory
|
|
func (s *Storage) TempPath() string {
|
|
return filepath.Join(s.basePath, "temp")
|
|
}
|
|
|
|
// PackagePath returns the path for a specific package version
|
|
func (s *Storage) PackagePath(developerID, appID string, versionCode int) string {
|
|
return filepath.Join(s.basePath, "packages", developerID, appID,
|
|
fmt.Sprintf("%d", versionCode), "package.mosis")
|
|
}
|
|
|
|
// PackageDir returns the directory for a specific package version
|
|
func (s *Storage) PackageDir(developerID, appID string, versionCode int) string {
|
|
return filepath.Join(s.basePath, "packages", developerID, appID,
|
|
fmt.Sprintf("%d", versionCode))
|
|
}
|
|
|
|
// TempPackagePath returns a temp path for uploading
|
|
func (s *Storage) TempPackagePath(uploadID string) string {
|
|
return filepath.Join(s.basePath, "temp", uploadID, "package.mosis")
|
|
}
|
|
|
|
// IconPath returns the path for an app icon of a specific size
|
|
func (s *Storage) IconPath(appID string, size int) string {
|
|
return filepath.Join(s.basePath, "assets", appID, fmt.Sprintf("icon-%d.png", size))
|
|
}
|
|
|
|
// ScreenshotPath returns the path for an app screenshot
|
|
func (s *Storage) ScreenshotPath(appID string, index int) string {
|
|
return filepath.Join(s.basePath, "assets", appID, "screenshots", fmt.Sprintf("%d.png", index))
|
|
}
|
|
|
|
// SaveTempFile saves a file to temp storage
|
|
func (s *Storage) SaveTempFile(uploadID string, r io.Reader) (int64, error) {
|
|
path := s.TempPackagePath(uploadID)
|
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
|
return 0, fmt.Errorf("failed to create temp directory: %w", err)
|
|
}
|
|
|
|
f, err := os.Create(path)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to create temp file: %w", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
size, err := io.Copy(f, r)
|
|
if err != nil {
|
|
os.Remove(path)
|
|
return 0, fmt.Errorf("failed to write temp file: %w", err)
|
|
}
|
|
|
|
return size, nil
|
|
}
|
|
|
|
// MoveTempToFinal moves a temp file to its final location
|
|
func (s *Storage) MoveTempToFinal(uploadID, developerID, appID string, versionCode int) error {
|
|
tempPath := s.TempPackagePath(uploadID)
|
|
finalPath := s.PackagePath(developerID, appID, versionCode)
|
|
|
|
// Create final directory
|
|
if err := os.MkdirAll(filepath.Dir(finalPath), 0755); err != nil {
|
|
return fmt.Errorf("failed to create package directory: %w", err)
|
|
}
|
|
|
|
// Move file
|
|
if err := os.Rename(tempPath, finalPath); err != nil {
|
|
// If rename fails (cross-device), copy and delete
|
|
if err := copyFile(tempPath, finalPath); err != nil {
|
|
return fmt.Errorf("failed to move temp file: %w", err)
|
|
}
|
|
os.Remove(tempPath)
|
|
}
|
|
|
|
// Clean up temp directory
|
|
os.Remove(filepath.Dir(tempPath))
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeleteTemp removes temp files for an upload
|
|
func (s *Storage) DeleteTemp(uploadID string) error {
|
|
tempDir := filepath.Join(s.basePath, "temp", uploadID)
|
|
return os.RemoveAll(tempDir)
|
|
}
|
|
|
|
// SaveIcon saves an icon file
|
|
func (s *Storage) SaveIcon(appID string, size int, data []byte) error {
|
|
path := s.IconPath(appID, size)
|
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
|
return fmt.Errorf("failed to create assets directory: %w", err)
|
|
}
|
|
|
|
return os.WriteFile(path, data, 0644)
|
|
}
|
|
|
|
// SaveScreenshot saves a screenshot
|
|
func (s *Storage) SaveScreenshot(appID string, index int, data []byte) error {
|
|
path := s.ScreenshotPath(appID, index)
|
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
|
return fmt.Errorf("failed to create screenshots directory: %w", err)
|
|
}
|
|
|
|
return os.WriteFile(path, data, 0644)
|
|
}
|
|
|
|
// OpenPackage opens a package file for reading
|
|
func (s *Storage) OpenPackage(developerID, appID string, versionCode int) (*os.File, error) {
|
|
path := s.PackagePath(developerID, appID, versionCode)
|
|
return os.Open(path)
|
|
}
|
|
|
|
// PackageExists checks if a package file exists
|
|
func (s *Storage) PackageExists(developerID, appID string, versionCode int) bool {
|
|
path := s.PackagePath(developerID, appID, versionCode)
|
|
_, err := os.Stat(path)
|
|
return err == nil
|
|
}
|
|
|
|
// GetPackageSize returns the size of a package file
|
|
func (s *Storage) GetPackageSize(developerID, appID string, versionCode int) (int64, error) {
|
|
path := s.PackagePath(developerID, appID, versionCode)
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return info.Size(), nil
|
|
}
|
|
|
|
// DeletePackage removes a package and its directory
|
|
func (s *Storage) DeletePackage(developerID, appID string, versionCode int) error {
|
|
dir := s.PackageDir(developerID, appID, versionCode)
|
|
return os.RemoveAll(dir)
|
|
}
|
|
|
|
// DeleteAppAssets removes all assets for an app
|
|
func (s *Storage) DeleteAppAssets(appID string) error {
|
|
dir := filepath.Join(s.basePath, "assets", appID)
|
|
return os.RemoveAll(dir)
|
|
}
|
|
|
|
// GetPackagePath resolves a package URL (stored in DB) to a filesystem path
|
|
// Package URLs are stored as relative paths like "packages/{developerID}/{appID}/{versionCode}/package.mosis"
|
|
func (s *Storage) GetPackagePath(packageURL string) string {
|
|
// If it's already an absolute path, return as-is
|
|
if filepath.IsAbs(packageURL) {
|
|
return packageURL
|
|
}
|
|
// Otherwise, join with base path
|
|
return filepath.Join(s.basePath, packageURL)
|
|
}
|
|
|
|
// copyFile copies a file from src to dst
|
|
func copyFile(src, dst string) error {
|
|
in, err := os.Open(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer in.Close()
|
|
|
|
out, err := os.Create(dst)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer out.Close()
|
|
|
|
_, err = io.Copy(out, in)
|
|
return err
|
|
}
|