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 } // DeleteFirmware deletes firmware binary and metadata func (s *FirmwareService) DeleteFirmware(name, version string) error { // Validate required fields if name == "" || version == "" { return fmt.Errorf("name and version are required") } // Check if firmware exists if !s.storage.FirmwareExists(name, version) { return fmt.Errorf("firmware not found") } // Delete from database first if err := s.repo.DeleteFirmwareMetadata(name, version); err != nil { return fmt.Errorf("failed to delete firmware metadata: %w", err) } // Delete firmware binary if err := s.storage.DeleteFirmwareBinary(name, version); err != nil { return fmt.Errorf("failed to delete firmware binary: %w", err) } return 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 }