Files
spore-registry/internal/service/firmware.go
2025-10-21 22:43:18 +02:00

191 lines
5.1 KiB
Go

package service
import (
"fmt"
"io"
"os"
"sort"
"strconv"
"strings"
"spore-registry/internal/models"
"spore-registry/internal/repository"
"spore-registry/internal/storage"
)
// FirmwareService handles business logic for firmware operations
type FirmwareService struct {
repo *repository.FirmwareRepository
storage *storage.FileStorage
}
// NewFirmwareService creates a new firmware service
func NewFirmwareService(repo *repository.FirmwareRepository, storage *storage.FileStorage) *FirmwareService {
return &FirmwareService{
repo: repo,
storage: storage,
}
}
// UploadFirmware uploads firmware binary and metadata
func (s *FirmwareService) UploadFirmware(metadata models.FirmwareMetadata, data io.Reader) (*models.UploadResponse, error) {
// Validate required fields
if metadata.Name == "" || metadata.Version == "" {
return nil, fmt.Errorf("name and version are required")
}
// Store firmware binary
filePath, size, err := s.storage.StoreFirmwareBinary(metadata.Name, metadata.Version, data)
if err != nil {
return nil, fmt.Errorf("failed to store firmware binary: %w", err)
}
// Store metadata in database
if err := s.repo.StoreFirmwareMetadata(metadata, size); err != nil {
// Try to clean up the file if database operation fails
os.Remove(filePath)
return nil, fmt.Errorf("failed to store firmware metadata: %w", err)
}
return &models.UploadResponse{
Success: true,
Name: metadata.Name,
Version: metadata.Version,
Size: size,
}, nil
}
// ListFirmware retrieves firmware records with optional filtering
func (s *FirmwareService) ListFirmware(name, version string) ([]models.GroupedFirmware, error) {
records, err := s.repo.GetFirmwareRecords(name, version)
if err != nil {
return nil, fmt.Errorf("failed to retrieve firmware records: %w", err)
}
grouped := s.groupFirmwareByName(records)
return grouped, nil
}
// UpdateFirmwareMetadata updates firmware metadata
func (s *FirmwareService) UpdateFirmwareMetadata(name, version string, metadata models.FirmwareMetadata) (*models.UploadResponse, error) {
// Validate that the name and version match the URL
if metadata.Name != name || metadata.Version != version {
return nil, fmt.Errorf("name and version in URL must match metadata")
}
// Check if firmware exists
records, err := s.repo.GetFirmwareRecords(name, version)
if err != nil {
return nil, fmt.Errorf("failed to check if firmware exists: %w", err)
}
if len(records) == 0 {
return nil, fmt.Errorf("firmware not found")
}
// Get existing firmware size (keep the same size since we're not updating the binary)
existingRecord := records[0]
size := existingRecord.Size
// Update metadata in database
if err := s.repo.StoreFirmwareMetadata(metadata, size); err != nil {
return nil, fmt.Errorf("failed to update firmware metadata: %w", err)
}
return &models.UploadResponse{
Success: true,
Name: metadata.Name,
Version: metadata.Version,
Size: size,
Message: "Metadata updated successfully",
}, nil
}
// GetFirmwarePath returns the file path for downloading firmware
func (s *FirmwareService) GetFirmwarePath(name, version string) (string, error) {
if !s.storage.FirmwareExists(name, version) {
return "", fmt.Errorf("firmware not found")
}
return s.storage.GetFirmwareBinaryPath(name, version), nil
}
// groupFirmwareByName groups firmware records by name
func (s *FirmwareService) groupFirmwareByName(records []models.FirmwareRecord) []models.GroupedFirmware {
nameMap := make(map[string][]models.FirmwareRecord)
// Group records by name
for _, record := range records {
nameMap[record.Name] = append(nameMap[record.Name], record)
}
// Convert to slice
var grouped []models.GroupedFirmware
for name, firmware := range nameMap {
// Sort firmware versions by version number (highest first)
sort.Slice(firmware, func(i, j int) bool {
return compareVersions(firmware[i].Version, firmware[j].Version) > 0
})
grouped = append(grouped, models.GroupedFirmware{
Name: name,
Firmware: firmware,
})
}
// Sort firmware groups by name (A to Z)
sort.Slice(grouped, func(i, j int) bool {
return grouped[i].Name < grouped[j].Name
})
return grouped
}
// compareVersions compares two semantic version strings
// Returns: -1 if v1 < v2, 0 if v1 == v2, 1 if v1 > v2
func compareVersions(v1, v2 string) int {
// Remove 'v' prefix if present
v1 = strings.TrimPrefix(v1, "v")
v2 = strings.TrimPrefix(v2, "v")
parts1 := strings.Split(v1, ".")
parts2 := strings.Split(v2, ".")
// Pad shorter version with zeros
maxLen := len(parts1)
if len(parts2) > maxLen {
maxLen = len(parts2)
}
for len(parts1) < maxLen {
parts1 = append(parts1, "0")
}
for len(parts2) < maxLen {
parts2 = append(parts2, "0")
}
// Compare each part
for i := 0; i < maxLen; i++ {
num1, err1 := strconv.Atoi(parts1[i])
num2, err2 := strconv.Atoi(parts2[i])
// If parsing fails, do string comparison
if err1 != nil || err2 != nil {
if parts1[i] < parts2[i] {
return -1
} else if parts1[i] > parts2[i] {
return 1
}
continue
}
if num1 < num2 {
return -1
} else if num1 > num2 {
return 1
}
}
return 0
}