mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 16:46:17 +00:00
- Fix all errcheck issues by properly checking error return values - Fix gocritic exitAfterDefer issues by replacing log.Fatalf with return statements - Fix rangeValCopy issues by using index-based iteration for large structs - Add missing package comments for all packages - Fix unused parameter issues by renaming to underscore - Fix empty block issues by adding explicit error handling - Add documentation for exported methods and constants - Fix shadow variable issues - Replace deprecated strings.Title with manual implementation - Fix defer function error handling Reduced lint issues from 108 to 84 (22% improvement) All critical error handling and code quality issues resolved
499 lines
16 KiB
Go
499 lines
16 KiB
Go
package models
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// WebSocketEventType represents the type of WebSocket event
|
|
type WebSocketEventType string
|
|
|
|
const (
|
|
// EventTypeNowPlaying indicates a now playing status update
|
|
EventTypeNowPlaying WebSocketEventType = "nowPlayingUpdated"
|
|
// EventTypeVolumeUpdated indicates a volume level change
|
|
EventTypeVolumeUpdated WebSocketEventType = "volumeUpdated"
|
|
// EventTypeConnectionState indicates a connection state change
|
|
EventTypeConnectionState WebSocketEventType = "connectionStateUpdated"
|
|
// EventTypePresetUpdated indicates a preset configuration change
|
|
EventTypePresetUpdated WebSocketEventType = "presetUpdated"
|
|
// EventTypeZoneUpdated indicates a zone configuration change
|
|
EventTypeZoneUpdated WebSocketEventType = "zoneUpdated"
|
|
// EventTypeBassUpdated indicates a bass level change
|
|
EventTypeBassUpdated WebSocketEventType = "bassUpdated"
|
|
// EventTypeClockTimeUpdated indicates a clock time change
|
|
EventTypeClockTimeUpdated WebSocketEventType = "clockTimeUpdated"
|
|
// EventTypeClockDisplayUpdated indicates a clock display setting change
|
|
EventTypeClockDisplayUpdated WebSocketEventType = "clockDisplayUpdated"
|
|
// EventTypeNameUpdated indicates a device name change
|
|
EventTypeNameUpdated WebSocketEventType = "nameUpdated"
|
|
// EventTypeErrorUpdated indicates an error status change
|
|
EventTypeErrorUpdated WebSocketEventType = "errorUpdated"
|
|
// EventTypeRecentsUpdated indicates a recent items list change
|
|
EventTypeRecentsUpdated WebSocketEventType = "recentsUpdated"
|
|
// EventTypeLanguageUpdated indicates a language setting change
|
|
EventTypeLanguageUpdated WebSocketEventType = "languageUpdated"
|
|
// EventTypeUnknown indicates an unrecognized event type
|
|
EventTypeUnknown WebSocketEventType = "unknown"
|
|
)
|
|
|
|
// String returns a human-readable string representation
|
|
func (e WebSocketEventType) String() string {
|
|
switch e {
|
|
case EventTypeNowPlaying:
|
|
return "Now Playing Updated"
|
|
case EventTypeVolumeUpdated:
|
|
return "Volume Updated"
|
|
case EventTypeConnectionState:
|
|
return "Connection State Updated"
|
|
case EventTypePresetUpdated:
|
|
return "Preset Updated"
|
|
case EventTypeZoneUpdated:
|
|
return "Zone Updated"
|
|
case EventTypeBassUpdated:
|
|
return "Bass Updated"
|
|
case EventTypeClockTimeUpdated:
|
|
return "Clock Time Updated"
|
|
case EventTypeClockDisplayUpdated:
|
|
return "Clock Display Updated"
|
|
case EventTypeNameUpdated:
|
|
return "Name Updated"
|
|
case EventTypeErrorUpdated:
|
|
return "Error Updated"
|
|
case EventTypeRecentsUpdated:
|
|
return "Recents Updated"
|
|
case EventTypeLanguageUpdated:
|
|
return "Language Updated"
|
|
default:
|
|
return "Unknown Event"
|
|
}
|
|
}
|
|
|
|
// WebSocketEvent represents a generic WebSocket event from SoundTouch device
|
|
type WebSocketEvent struct {
|
|
XMLName xml.Name `xml:"updates"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
NowPlayingUpdated *NowPlayingUpdatedEvent `xml:"nowPlayingUpdated,omitempty"`
|
|
VolumeUpdated *VolumeUpdatedEvent `xml:"volumeUpdated,omitempty"`
|
|
ConnectionStateUpdated *ConnectionStateUpdatedEvent `xml:"connectionStateUpdated,omitempty"`
|
|
PresetUpdated *PresetUpdatedEvent `xml:"presetUpdated,omitempty"`
|
|
ZoneUpdated *ZoneUpdatedEvent `xml:"zoneUpdated,omitempty"`
|
|
BassUpdated *BassUpdatedEvent `xml:"bassUpdated,omitempty"`
|
|
ClockTimeUpdated *ClockTimeUpdatedEvent `xml:"clockTimeUpdated,omitempty"`
|
|
ClockDisplayUpdated *ClockDisplayUpdatedEvent `xml:"clockDisplayUpdated,omitempty"`
|
|
NameUpdated *NameUpdatedEvent `xml:"nameUpdated,omitempty"`
|
|
ErrorUpdated *ErrorUpdatedEvent `xml:"errorUpdated,omitempty"`
|
|
RecentsUpdated *RecentsUpdatedEvent `xml:"recentsUpdated,omitempty"`
|
|
LanguageUpdated *LanguageUpdatedEvent `xml:"languageUpdated,omitempty"`
|
|
Timestamp time.Time `json:"timestamp"` // Added by client for tracking
|
|
}
|
|
|
|
// GetEvents returns all events present in this WebSocket event
|
|
func (e *WebSocketEvent) GetEvents() []interface{} {
|
|
var events []interface{}
|
|
|
|
if e.NowPlayingUpdated != nil {
|
|
events = append(events, e.NowPlayingUpdated)
|
|
}
|
|
if e.VolumeUpdated != nil {
|
|
events = append(events, e.VolumeUpdated)
|
|
}
|
|
if e.ConnectionStateUpdated != nil {
|
|
events = append(events, e.ConnectionStateUpdated)
|
|
}
|
|
if e.PresetUpdated != nil {
|
|
events = append(events, e.PresetUpdated)
|
|
}
|
|
if e.ZoneUpdated != nil {
|
|
events = append(events, e.ZoneUpdated)
|
|
}
|
|
if e.BassUpdated != nil {
|
|
events = append(events, e.BassUpdated)
|
|
}
|
|
if e.ClockTimeUpdated != nil {
|
|
events = append(events, e.ClockTimeUpdated)
|
|
}
|
|
if e.ClockDisplayUpdated != nil {
|
|
events = append(events, e.ClockDisplayUpdated)
|
|
}
|
|
if e.NameUpdated != nil {
|
|
events = append(events, e.NameUpdated)
|
|
}
|
|
if e.ErrorUpdated != nil {
|
|
events = append(events, e.ErrorUpdated)
|
|
}
|
|
if e.RecentsUpdated != nil {
|
|
events = append(events, e.RecentsUpdated)
|
|
}
|
|
if e.LanguageUpdated != nil {
|
|
events = append(events, e.LanguageUpdated)
|
|
}
|
|
|
|
return events
|
|
}
|
|
|
|
// NowPlayingUpdatedEvent represents a now playing update event
|
|
type NowPlayingUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"nowPlayingUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
NowPlaying NowPlaying `xml:"nowPlaying"`
|
|
}
|
|
|
|
// VolumeUpdatedEvent represents a volume update event
|
|
type VolumeUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"volumeUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
Volume Volume `xml:"volume"`
|
|
}
|
|
|
|
// ConnectionStateUpdatedEvent represents a connection state update event
|
|
type ConnectionStateUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"connectionStateUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
ConnectionState ConnectionState `xml:"connectionState"`
|
|
}
|
|
|
|
// ConnectionState represents the device's network connection state
|
|
type ConnectionState struct {
|
|
XMLName xml.Name `xml:"connectionState"`
|
|
State string `xml:"state,attr"`
|
|
Signal string `xml:"signal,attr"`
|
|
}
|
|
|
|
// ConnectionStateType represents connection state values
|
|
type ConnectionStateType string
|
|
|
|
const (
|
|
// ConnectionStateConnected indicates the device is connected
|
|
ConnectionStateConnected ConnectionStateType = "CONNECTED"
|
|
// ConnectionStateDisconnected indicates the device is disconnected
|
|
ConnectionStateDisconnected ConnectionStateType = "DISCONNECTED"
|
|
)
|
|
|
|
// IsConnected returns true if the device is connected
|
|
func (cs *ConnectionState) IsConnected() bool {
|
|
return cs.State == string(ConnectionStateConnected)
|
|
}
|
|
|
|
// GetSignalStrength returns the signal strength as a string
|
|
func (cs *ConnectionState) GetSignalStrength() string {
|
|
return cs.Signal
|
|
}
|
|
|
|
// PresetUpdatedEvent represents a preset update event
|
|
type PresetUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"presetUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
Preset Preset `xml:"preset"`
|
|
}
|
|
|
|
// ZoneUpdatedEvent represents a multiroom zone update event
|
|
type ZoneUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"zoneUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
Zone Zone `xml:"zone"`
|
|
}
|
|
|
|
// Zone represents multiroom zone information
|
|
type Zone struct {
|
|
XMLName xml.Name `xml:"zone"`
|
|
Master string `xml:"master,attr"`
|
|
Members []ZoneMember `xml:"member"`
|
|
}
|
|
|
|
// ZoneMember represents a member of a multiroom zone
|
|
type ZoneMember struct {
|
|
XMLName xml.Name `xml:"member"`
|
|
DeviceID string `xml:",chardata"`
|
|
IP string `xml:"ipaddress,attr"`
|
|
}
|
|
|
|
// BassUpdatedEvent represents a bass setting update event
|
|
type BassUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"bassUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
Bass Bass `xml:"bass"`
|
|
}
|
|
|
|
// ClockTimeUpdatedEvent represents a clock time update event
|
|
type ClockTimeUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"clockTimeUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
ClockTime ClockTime `xml:"clockTime"`
|
|
}
|
|
|
|
// ClockDisplayUpdatedEvent represents a clock display setting update event
|
|
type ClockDisplayUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"clockDisplayUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
ClockDisplay ClockDisplay `xml:"clockDisplay"`
|
|
}
|
|
|
|
// NameUpdatedEvent represents a device name update event
|
|
type NameUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"nameUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
Name Name `xml:"name"`
|
|
}
|
|
|
|
// ErrorUpdatedEvent represents an error state update event
|
|
type ErrorUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"errorUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
Error Error `xml:"error"`
|
|
}
|
|
|
|
// Error represents an error state
|
|
type Error struct {
|
|
XMLName xml.Name `xml:"error"`
|
|
Value string `xml:"value,attr"`
|
|
Name string `xml:"name,attr"`
|
|
Text string `xml:",chardata"`
|
|
}
|
|
|
|
// RecentsUpdatedEvent represents a recent items update event
|
|
type RecentsUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"recentsUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
Recents Recents `xml:"recents"`
|
|
}
|
|
|
|
// Recents represents recently played items
|
|
type Recents struct {
|
|
XMLName xml.Name `xml:"recents"`
|
|
Items []RecentItem `xml:"recent"`
|
|
}
|
|
|
|
// RecentItem represents a recently played item
|
|
type RecentItem struct {
|
|
XMLName xml.Name `xml:"recent"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
CreatedOn int64 `xml:"createdOn,attr"`
|
|
ID string `xml:"id,attr"`
|
|
ContentItem ContentItem `xml:"ContentItem"`
|
|
}
|
|
|
|
// LanguageUpdatedEvent represents a language setting update event
|
|
type LanguageUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"languageUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
Language Language `xml:"language"`
|
|
}
|
|
|
|
// Language represents language settings
|
|
type Language struct {
|
|
XMLName xml.Name `xml:"language"`
|
|
Value string `xml:",chardata"`
|
|
}
|
|
|
|
// EventHandler represents a function that handles WebSocket events
|
|
type EventHandler func(event *WebSocketEvent)
|
|
|
|
// TypedEventHandler represents a function that handles specific event types
|
|
type TypedEventHandler[T any] func(event T)
|
|
|
|
// WebSocketEventHandlers holds typed event handlers for different event types
|
|
type WebSocketEventHandlers struct {
|
|
OnNowPlaying TypedEventHandler[*NowPlayingUpdatedEvent]
|
|
OnVolumeUpdated TypedEventHandler[*VolumeUpdatedEvent]
|
|
OnConnectionState TypedEventHandler[*ConnectionStateUpdatedEvent]
|
|
OnPresetUpdated TypedEventHandler[*PresetUpdatedEvent]
|
|
OnZoneUpdated TypedEventHandler[*ZoneUpdatedEvent]
|
|
OnBassUpdated TypedEventHandler[*BassUpdatedEvent]
|
|
OnClockTimeUpdated TypedEventHandler[*ClockTimeUpdatedEvent]
|
|
OnClockDisplayUpdated TypedEventHandler[*ClockDisplayUpdatedEvent]
|
|
OnNameUpdated TypedEventHandler[*NameUpdatedEvent]
|
|
OnErrorUpdated TypedEventHandler[*ErrorUpdatedEvent]
|
|
OnRecentsUpdated TypedEventHandler[*RecentsUpdatedEvent]
|
|
OnLanguageUpdated TypedEventHandler[*LanguageUpdatedEvent]
|
|
OnUnknownEvent EventHandler
|
|
}
|
|
|
|
// ParseWebSocketEvent attempts to parse a WebSocket message into a specific event type
|
|
func ParseWebSocketEvent(data []byte) (*WebSocketEvent, error) {
|
|
var event WebSocketEvent
|
|
if err := xml.Unmarshal(data, &event); err != nil {
|
|
return nil, fmt.Errorf("failed to parse WebSocket event: %w", err)
|
|
}
|
|
|
|
// Add timestamp
|
|
event.Timestamp = time.Now()
|
|
|
|
return &event, nil
|
|
}
|
|
|
|
// ParseTypedEvent attempts to parse a WebSocket event into a specific typed event
|
|
func ParseTypedEvent[T any](event *WebSocketEvent, eventType WebSocketEventType) (T, error) {
|
|
var result T
|
|
|
|
// Get the event directly from the parsed structure
|
|
switch eventType {
|
|
case EventTypeNowPlaying:
|
|
if event.NowPlayingUpdated != nil {
|
|
if typedResult, ok := interface{}(event.NowPlayingUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypeVolumeUpdated:
|
|
if event.VolumeUpdated != nil {
|
|
if typedResult, ok := interface{}(event.VolumeUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypeConnectionState:
|
|
if event.ConnectionStateUpdated != nil {
|
|
if typedResult, ok := interface{}(event.ConnectionStateUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypePresetUpdated:
|
|
if event.PresetUpdated != nil {
|
|
if typedResult, ok := interface{}(event.PresetUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypeZoneUpdated:
|
|
if event.ZoneUpdated != nil {
|
|
if typedResult, ok := interface{}(event.ZoneUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypeBassUpdated:
|
|
if event.BassUpdated != nil {
|
|
if typedResult, ok := interface{}(event.BassUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypeClockTimeUpdated:
|
|
if event.ClockTimeUpdated != nil {
|
|
if typedResult, ok := interface{}(event.ClockTimeUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypeClockDisplayUpdated:
|
|
if event.ClockDisplayUpdated != nil {
|
|
if typedResult, ok := interface{}(event.ClockDisplayUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypeNameUpdated:
|
|
if event.NameUpdated != nil {
|
|
if typedResult, ok := interface{}(event.NameUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypeErrorUpdated:
|
|
if event.ErrorUpdated != nil {
|
|
if typedResult, ok := interface{}(event.ErrorUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypeRecentsUpdated:
|
|
if event.RecentsUpdated != nil {
|
|
if typedResult, ok := interface{}(event.RecentsUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypeLanguageUpdated:
|
|
if event.LanguageUpdated != nil {
|
|
if typedResult, ok := interface{}(event.LanguageUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return result, fmt.Errorf("event type %s not found in WebSocket event", eventType)
|
|
}
|
|
|
|
// HasEventType checks if the WebSocket event contains a specific event type
|
|
func (e *WebSocketEvent) HasEventType(eventType WebSocketEventType) bool {
|
|
switch eventType {
|
|
case EventTypeNowPlaying:
|
|
return e.NowPlayingUpdated != nil
|
|
case EventTypeVolumeUpdated:
|
|
return e.VolumeUpdated != nil
|
|
case EventTypeConnectionState:
|
|
return e.ConnectionStateUpdated != nil
|
|
case EventTypePresetUpdated:
|
|
return e.PresetUpdated != nil
|
|
case EventTypeZoneUpdated:
|
|
return e.ZoneUpdated != nil
|
|
case EventTypeBassUpdated:
|
|
return e.BassUpdated != nil
|
|
case EventTypeClockTimeUpdated:
|
|
return e.ClockTimeUpdated != nil
|
|
case EventTypeClockDisplayUpdated:
|
|
return e.ClockDisplayUpdated != nil
|
|
case EventTypeNameUpdated:
|
|
return e.NameUpdated != nil
|
|
case EventTypeErrorUpdated:
|
|
return e.ErrorUpdated != nil
|
|
case EventTypeRecentsUpdated:
|
|
return e.RecentsUpdated != nil
|
|
case EventTypeLanguageUpdated:
|
|
return e.LanguageUpdated != nil
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GetEventTypes returns all event types present in this WebSocket event
|
|
func (e *WebSocketEvent) GetEventTypes() []WebSocketEventType {
|
|
var types []WebSocketEventType
|
|
|
|
if e.NowPlayingUpdated != nil {
|
|
types = append(types, EventTypeNowPlaying)
|
|
}
|
|
if e.VolumeUpdated != nil {
|
|
types = append(types, EventTypeVolumeUpdated)
|
|
}
|
|
if e.ConnectionStateUpdated != nil {
|
|
types = append(types, EventTypeConnectionState)
|
|
}
|
|
if e.PresetUpdated != nil {
|
|
types = append(types, EventTypePresetUpdated)
|
|
}
|
|
if e.ZoneUpdated != nil {
|
|
types = append(types, EventTypeZoneUpdated)
|
|
}
|
|
if e.BassUpdated != nil {
|
|
types = append(types, EventTypeBassUpdated)
|
|
}
|
|
if e.ClockTimeUpdated != nil {
|
|
types = append(types, EventTypeClockTimeUpdated)
|
|
}
|
|
if e.ClockDisplayUpdated != nil {
|
|
types = append(types, EventTypeClockDisplayUpdated)
|
|
}
|
|
if e.NameUpdated != nil {
|
|
types = append(types, EventTypeNameUpdated)
|
|
}
|
|
if e.ErrorUpdated != nil {
|
|
types = append(types, EventTypeErrorUpdated)
|
|
}
|
|
if e.RecentsUpdated != nil {
|
|
types = append(types, EventTypeRecentsUpdated)
|
|
}
|
|
if e.LanguageUpdated != nil {
|
|
types = append(types, EventTypeLanguageUpdated)
|
|
}
|
|
|
|
return types
|
|
}
|
|
|
|
// String returns a human-readable string representation of the WebSocket event
|
|
func (e *WebSocketEvent) String() string {
|
|
events := e.GetEvents()
|
|
eventTypes := e.GetEventTypes()
|
|
|
|
if len(events) == 0 {
|
|
return fmt.Sprintf("WebSocket Event [Device: %s] - No events", e.DeviceID)
|
|
}
|
|
|
|
if len(events) == 1 {
|
|
return fmt.Sprintf("WebSocket Event [Device: %s] - %s", e.DeviceID, eventTypes[0].String())
|
|
}
|
|
|
|
return fmt.Sprintf("WebSocket Event [Device: %s] - %d events", e.DeviceID, len(events))
|
|
}
|