191 lines
5.1 KiB
Go
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
|
|
}
|