mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 00:26:29 +00:00
Applied automatic fixes using golangci-lint --fix which resolved: - All remaining wsl_v5 whitespace issues (28 issues) - All whitespace formatting issues (1 issue) - Improved code formatting consistency across the entire codebase All tests passing and functionality preserved.
391 lines
12 KiB
Go
391 lines
12 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/gesellix/bose-soundtouch/pkg/client"
|
|
"github.com/gesellix/bose-soundtouch/pkg/models"
|
|
)
|
|
|
|
// ServiceAvailabilityChecker provides service availability validation for CLI commands
|
|
type ServiceAvailabilityChecker struct {
|
|
client *client.Client
|
|
serviceAvailability *models.ServiceAvailability
|
|
skipAvailabilityCheck bool
|
|
cached bool
|
|
}
|
|
|
|
// NewServiceAvailabilityChecker creates a new service availability checker
|
|
func NewServiceAvailabilityChecker(client *client.Client) *ServiceAvailabilityChecker {
|
|
skipCheck := os.Getenv("SOUNDTOUCH_SKIP_AVAILABILITY_CHECK") == "true" ||
|
|
os.Getenv("SOUNDTOUCH_SKIP_AVAILABILITY_CHECK") == "1"
|
|
|
|
return &ServiceAvailabilityChecker{
|
|
client: client,
|
|
skipAvailabilityCheck: skipCheck,
|
|
cached: false,
|
|
}
|
|
}
|
|
|
|
// loadServiceAvailability loads service availability data (cached after first call)
|
|
func (sac *ServiceAvailabilityChecker) loadServiceAvailability() {
|
|
if sac.cached {
|
|
return
|
|
}
|
|
|
|
if sac.skipAvailabilityCheck {
|
|
// Create a mock availability that allows everything
|
|
sac.serviceAvailability = &models.ServiceAvailability{}
|
|
sac.cached = true
|
|
|
|
return
|
|
}
|
|
|
|
serviceAvailability, err := sac.client.GetServiceAvailability()
|
|
if err != nil {
|
|
// If availability check fails, warn but don't fail the command
|
|
PrintWarning(fmt.Sprintf("Could not check service availability: %v", err))
|
|
|
|
if !sac.skipAvailabilityCheck {
|
|
PrintWarning("Command will proceed without availability validation")
|
|
PrintWarning("Set SOUNDTOUCH_SKIP_AVAILABILITY_CHECK=true to disable these checks")
|
|
}
|
|
// Create empty availability to prevent further errors
|
|
sac.serviceAvailability = &models.ServiceAvailability{}
|
|
sac.cached = true
|
|
|
|
return
|
|
}
|
|
|
|
sac.serviceAvailability = serviceAvailability
|
|
sac.cached = true
|
|
}
|
|
|
|
// CheckServiceAvailable validates if a service is available and provides user feedback
|
|
func (sac *ServiceAvailabilityChecker) CheckServiceAvailable(serviceType models.ServiceType, actionDescription string) bool {
|
|
if sac.skipAvailabilityCheck {
|
|
return true
|
|
}
|
|
|
|
sac.loadServiceAvailability()
|
|
|
|
// If we couldn't load availability data, allow the operation
|
|
if sac.serviceAvailability == nil || sac.serviceAvailability.Services == nil {
|
|
return true
|
|
}
|
|
|
|
if sac.serviceAvailability.IsServiceAvailable(serviceType) {
|
|
return true
|
|
}
|
|
|
|
// Service is not available - provide helpful feedback
|
|
serviceName := formatServiceTypeForDisplay(serviceType)
|
|
PrintError(fmt.Sprintf("Cannot %s: %s service is not available", actionDescription, serviceName))
|
|
|
|
// Get specific reason if available
|
|
service := sac.serviceAvailability.GetServiceByType(serviceType)
|
|
if service != nil && service.Reason != "" {
|
|
PrintError(fmt.Sprintf("Reason: %s", service.Reason))
|
|
}
|
|
|
|
// Provide troubleshooting hints
|
|
sac.provideTroubleshootingHints(serviceType)
|
|
|
|
// Suggest alternatives
|
|
sac.suggestAlternatives(serviceType, actionDescription)
|
|
|
|
PrintWarning("To bypass this check, set SOUNDTOUCH_SKIP_AVAILABILITY_CHECK=true")
|
|
|
|
return false
|
|
}
|
|
|
|
// CheckSourceAvailable validates if a source string corresponds to an available service
|
|
func (sac *ServiceAvailabilityChecker) CheckSourceAvailable(source, actionDescription string) bool {
|
|
if sac.skipAvailabilityCheck {
|
|
return true
|
|
}
|
|
|
|
serviceType := sourceToServiceType(source)
|
|
if serviceType == "" {
|
|
// Unknown source type, allow it (might be a valid source not in our list)
|
|
return true
|
|
}
|
|
|
|
return sac.CheckServiceAvailable(serviceType, actionDescription)
|
|
}
|
|
|
|
// ValidateSpotifyAvailable checks Spotify availability for Spotify-specific operations
|
|
func (sac *ServiceAvailabilityChecker) ValidateSpotifyAvailable(actionDescription string) bool {
|
|
return sac.CheckServiceAvailable(models.ServiceTypeSpotify, actionDescription)
|
|
}
|
|
|
|
// ValidateBluetoothAvailable checks Bluetooth availability for Bluetooth operations
|
|
func (sac *ServiceAvailabilityChecker) ValidateBluetoothAvailable(actionDescription string) bool {
|
|
return sac.CheckServiceAvailable(models.ServiceTypeBluetooth, actionDescription)
|
|
}
|
|
|
|
// ValidateTuneInAvailable checks TuneIn availability for radio operations
|
|
func (sac *ServiceAvailabilityChecker) ValidateTuneInAvailable(actionDescription string) bool {
|
|
return sac.CheckServiceAvailable(models.ServiceTypeTuneIn, actionDescription)
|
|
}
|
|
|
|
// ValidatePandoraAvailable checks Pandora availability for Pandora operations
|
|
func (sac *ServiceAvailabilityChecker) ValidatePandoraAvailable(actionDescription string) bool {
|
|
return sac.CheckServiceAvailable(models.ServiceTypePandora, actionDescription)
|
|
}
|
|
|
|
// GetAvailableStreamingServices returns a list of available streaming services for user feedback
|
|
func (sac *ServiceAvailabilityChecker) GetAvailableStreamingServices() []string {
|
|
if sac.skipAvailabilityCheck {
|
|
return []string{"All services (availability check disabled)"}
|
|
}
|
|
|
|
sac.loadServiceAvailability()
|
|
|
|
if sac.serviceAvailability == nil || sac.serviceAvailability.Services == nil {
|
|
return []string{"Unable to determine available services"}
|
|
}
|
|
|
|
streamingServices := sac.serviceAvailability.GetStreamingServices()
|
|
|
|
var available []string
|
|
|
|
for _, service := range streamingServices {
|
|
if service.IsAvailable {
|
|
available = append(available, formatServiceTypeForDisplay(models.ServiceType(service.Type)))
|
|
}
|
|
}
|
|
|
|
if len(available) == 0 {
|
|
return []string{"No streaming services currently available"}
|
|
}
|
|
|
|
return available
|
|
}
|
|
|
|
// GetAvailableLocalServices returns a list of available local input services
|
|
func (sac *ServiceAvailabilityChecker) GetAvailableLocalServices() []string {
|
|
if sac.skipAvailabilityCheck {
|
|
return []string{"All services (availability check disabled)"}
|
|
}
|
|
|
|
sac.loadServiceAvailability()
|
|
|
|
if sac.serviceAvailability == nil || sac.serviceAvailability.Services == nil {
|
|
return []string{"Unable to determine available services"}
|
|
}
|
|
|
|
localServices := sac.serviceAvailability.GetLocalServices()
|
|
|
|
var available []string
|
|
|
|
for _, service := range localServices {
|
|
if service.IsAvailable {
|
|
available = append(available, formatServiceTypeForDisplay(models.ServiceType(service.Type)))
|
|
}
|
|
}
|
|
|
|
if len(available) == 0 {
|
|
return []string{"No local input services currently available"}
|
|
}
|
|
|
|
return available
|
|
}
|
|
|
|
// provideTroubleshootingHints provides specific troubleshooting advice based on service type
|
|
func (sac *ServiceAvailabilityChecker) provideTroubleshootingHints(serviceType models.ServiceType) {
|
|
switch serviceType {
|
|
case models.ServiceTypeBluetooth:
|
|
PrintWarning("💡 Bluetooth troubleshooting:")
|
|
PrintWarning(" • Check if your device supports Bluetooth audio input")
|
|
PrintWarning(" • Ensure Bluetooth is enabled on the SoundTouch device")
|
|
PrintWarning(" • Try restarting the device")
|
|
|
|
case models.ServiceTypeSpotify:
|
|
PrintWarning("💡 Spotify troubleshooting:")
|
|
PrintWarning(" • Ensure you have a Spotify Premium account")
|
|
PrintWarning(" • Check if you're logged in to Spotify on the device")
|
|
PrintWarning(" • Verify your network connection")
|
|
|
|
case models.ServiceTypeAirPlay:
|
|
PrintWarning("💡 AirPlay troubleshooting:")
|
|
PrintWarning(" • Ensure your Apple device and SoundTouch are on the same network")
|
|
PrintWarning(" • Check that AirPlay is enabled in device settings")
|
|
PrintWarning(" • Verify network connectivity")
|
|
|
|
case models.ServiceTypeAlexa:
|
|
PrintWarning("💡 Alexa troubleshooting:")
|
|
PrintWarning(" • Check if Amazon Alexa is properly configured")
|
|
PrintWarning(" • Ensure the device is connected to your Amazon account")
|
|
PrintWarning(" • Verify internet connectivity")
|
|
|
|
case models.ServiceTypeTuneIn:
|
|
PrintWarning("💡 TuneIn troubleshooting:")
|
|
PrintWarning(" • Check internet connectivity")
|
|
PrintWarning(" • Verify the device can access external streaming services")
|
|
|
|
case models.ServiceTypePandora:
|
|
PrintWarning("💡 Pandora troubleshooting:")
|
|
PrintWarning(" • Ensure you have a valid Pandora account")
|
|
PrintWarning(" • Check if you're logged in to Pandora on the device")
|
|
PrintWarning(" • Verify internet connectivity")
|
|
}
|
|
}
|
|
|
|
// suggestAlternatives suggests alternative services when the requested one is unavailable
|
|
func (sac *ServiceAvailabilityChecker) suggestAlternatives(serviceType models.ServiceType, _ string) {
|
|
if sac.serviceAvailability == nil {
|
|
return
|
|
}
|
|
|
|
switch serviceType {
|
|
case models.ServiceTypeSpotify:
|
|
if sac.serviceAvailability.HasTuneIn() {
|
|
PrintWarning("💡 Alternative: TuneIn Radio is available for music streaming")
|
|
}
|
|
|
|
if sac.serviceAvailability.HasPandora() {
|
|
PrintWarning("💡 Alternative: Pandora is available for music streaming")
|
|
}
|
|
|
|
case models.ServiceTypeBluetooth:
|
|
if sac.serviceAvailability.HasAirPlay() {
|
|
PrintWarning("💡 Alternative: AirPlay is available for wireless audio")
|
|
}
|
|
|
|
if sac.serviceAvailability.HasLocalMusic() {
|
|
PrintWarning("💡 Alternative: Local Music Library is available")
|
|
}
|
|
|
|
case models.ServiceTypeTuneIn:
|
|
if sac.serviceAvailability.HasSpotify() {
|
|
PrintWarning("💡 Alternative: Spotify is available for music streaming")
|
|
}
|
|
|
|
if sac.serviceAvailability.HasPandora() {
|
|
PrintWarning("💡 Alternative: Pandora is available for music streaming")
|
|
}
|
|
}
|
|
|
|
// Show all available streaming services as suggestions
|
|
available := sac.GetAvailableStreamingServices()
|
|
if len(available) > 0 && available[0] != "No streaming services currently available" {
|
|
PrintWarning(fmt.Sprintf("💡 Available streaming services: %s", strings.Join(available, ", ")))
|
|
}
|
|
}
|
|
|
|
// sourceToServiceType maps source strings to service types
|
|
func sourceToServiceType(source string) models.ServiceType {
|
|
switch strings.ToUpper(source) {
|
|
case "SPOTIFY":
|
|
return models.ServiceTypeSpotify
|
|
case "BLUETOOTH":
|
|
return models.ServiceTypeBluetooth
|
|
case "AIRPLAY":
|
|
return models.ServiceTypeAirPlay
|
|
case "ALEXA":
|
|
return models.ServiceTypeAlexa
|
|
case "AMAZON":
|
|
return models.ServiceTypeAmazon
|
|
case "PANDORA":
|
|
return models.ServiceTypePandora
|
|
case "TUNEIN":
|
|
return models.ServiceTypeTuneIn
|
|
case "DEEZER":
|
|
return models.ServiceTypeDeezer
|
|
case "IHEART", "IHEARTRADIO":
|
|
return models.ServiceTypeIHeart
|
|
case "LOCAL_INTERNET_RADIO":
|
|
return models.ServiceTypeLocalInternetRadio
|
|
case "LOCAL_MUSIC":
|
|
return models.ServiceTypeLocalMusic
|
|
case "BMX":
|
|
return models.ServiceTypeBMX
|
|
case "NOTIFICATION":
|
|
return models.ServiceTypeNotification
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// formatServiceTypeForDisplay formats service types for user-friendly display
|
|
func formatServiceTypeForDisplay(serviceType models.ServiceType) string {
|
|
switch serviceType {
|
|
case models.ServiceTypeSpotify:
|
|
return "Spotify"
|
|
case models.ServiceTypeBluetooth:
|
|
return "Bluetooth"
|
|
case models.ServiceTypeAirPlay:
|
|
return "AirPlay"
|
|
case models.ServiceTypeAlexa:
|
|
return "Amazon Alexa"
|
|
case models.ServiceTypeAmazon:
|
|
return "Amazon Music"
|
|
case models.ServiceTypePandora:
|
|
return "Pandora"
|
|
case models.ServiceTypeTuneIn:
|
|
return "TuneIn Radio"
|
|
case models.ServiceTypeDeezer:
|
|
return "Deezer"
|
|
case models.ServiceTypeIHeart:
|
|
return "iHeartRadio"
|
|
case models.ServiceTypeLocalInternetRadio:
|
|
return "Internet Radio"
|
|
case models.ServiceTypeLocalMusic:
|
|
return "Local Music Library"
|
|
case models.ServiceTypeBMX:
|
|
return "BMX"
|
|
case models.ServiceTypeNotification:
|
|
return "Notifications"
|
|
default:
|
|
return string(serviceType)
|
|
}
|
|
}
|
|
|
|
// PrintServiceAvailabilitySummary prints a summary of available services
|
|
func (sac *ServiceAvailabilityChecker) PrintServiceAvailabilitySummary() {
|
|
if sac.skipAvailabilityCheck {
|
|
PrintWarning("Service availability checking is disabled")
|
|
return
|
|
}
|
|
|
|
sac.loadServiceAvailability()
|
|
|
|
if sac.serviceAvailability == nil || sac.serviceAvailability.Services == nil {
|
|
PrintWarning("Unable to determine service availability")
|
|
return
|
|
}
|
|
|
|
fmt.Printf("📊 Service Availability Summary:\n")
|
|
fmt.Printf(" Total services: %d\n", sac.serviceAvailability.GetServiceCount())
|
|
fmt.Printf(" Available: %d\n", sac.serviceAvailability.GetAvailableServiceCount())
|
|
fmt.Printf(" Unavailable: %d\n", sac.serviceAvailability.GetUnavailableServiceCount())
|
|
|
|
// Show quick status for popular services
|
|
fmt.Printf(" Popular services:\n")
|
|
|
|
popularChecks := []struct {
|
|
check func() bool
|
|
name string
|
|
}{
|
|
{sac.serviceAvailability.HasSpotify, "Spotify"},
|
|
{sac.serviceAvailability.HasBluetooth, "Bluetooth"},
|
|
{sac.serviceAvailability.HasAirPlay, "AirPlay"},
|
|
{sac.serviceAvailability.HasTuneIn, "TuneIn Radio"},
|
|
{sac.serviceAvailability.HasPandora, "Pandora"},
|
|
}
|
|
|
|
for _, check := range popularChecks {
|
|
status := "❌"
|
|
if check.check() {
|
|
status = "✅"
|
|
}
|
|
|
|
fmt.Printf(" %s %s\n", status, check.name)
|
|
}
|
|
|
|
fmt.Printf("💡 Use 'soundtouch-cli sources list' to see configured sources\n")
|
|
}
|