mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-09-07 15:07:17 +00:00
fix(player): satisfy projection lint
This commit is contained in:
committed by
Tobias Gesellchen
parent
60788bfa90
commit
b07375d23d
@@ -236,10 +236,12 @@ func (ws *WebSocketClient) connectWithConfig(config *WebSocketConfig) error {
|
||||
defer ws.connectMu.Unlock()
|
||||
|
||||
ws.mu.RLock()
|
||||
|
||||
if ws.connected {
|
||||
ws.mu.RUnlock()
|
||||
return fmt.Errorf("already connected")
|
||||
}
|
||||
|
||||
ctx := ws.ctx
|
||||
dialContext := ws.dialContext
|
||||
ws.mu.RUnlock()
|
||||
@@ -288,20 +290,25 @@ func (ws *WebSocketClient) connectWithConfig(config *WebSocketConfig) error {
|
||||
ws.mu.Lock()
|
||||
if err := ctx.Err(); err != nil {
|
||||
ws.mu.Unlock()
|
||||
|
||||
_ = conn.Close()
|
||||
|
||||
return fmt.Errorf("WebSocket client closed during connect: %w", err)
|
||||
}
|
||||
|
||||
if ws.connected {
|
||||
ws.mu.Unlock()
|
||||
|
||||
_ = conn.Close()
|
||||
|
||||
return fmt.Errorf("already connected")
|
||||
}
|
||||
|
||||
connection, transportHandler, transportGeneration := ws.activateConnectionLocked(conn)
|
||||
ws.mu.Unlock()
|
||||
|
||||
notifyTransportState(transportHandler, true, transportGeneration)
|
||||
|
||||
go ws.readLoop(config, connection)
|
||||
go ws.pingLoop(config, connection)
|
||||
|
||||
@@ -349,14 +356,17 @@ func (ws *WebSocketClient) Disconnect() error {
|
||||
|
||||
ws.mu.Lock()
|
||||
wasConnected := ws.connected
|
||||
|
||||
ws.reconnect = false
|
||||
if ws.connection != nil {
|
||||
ws.connection.cancel()
|
||||
ws.connection = nil
|
||||
}
|
||||
|
||||
conn := ws.conn
|
||||
ws.conn = nil
|
||||
ws.connected = false
|
||||
|
||||
var (
|
||||
transportHandler func(bool, uint64)
|
||||
transportGeneration uint64
|
||||
@@ -370,13 +380,16 @@ func (ws *WebSocketClient) Disconnect() error {
|
||||
|
||||
if conn != nil {
|
||||
err := conn.Close()
|
||||
|
||||
ws.logger.Printf("Disconnected")
|
||||
|
||||
notifyTransportState(transportHandler, false, transportGeneration)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
notifyTransportState(transportHandler, false, transportGeneration)
|
||||
|
||||
if !wasConnected {
|
||||
return fmt.Errorf("not connected")
|
||||
}
|
||||
@@ -391,14 +404,17 @@ func (ws *WebSocketClient) Close() error {
|
||||
|
||||
ws.mu.Lock()
|
||||
wasConnected := ws.connected
|
||||
|
||||
ws.reconnect = false
|
||||
if ws.connection != nil {
|
||||
ws.connection.cancel()
|
||||
ws.connection = nil
|
||||
}
|
||||
|
||||
conn := ws.conn
|
||||
ws.conn = nil
|
||||
ws.connected = false
|
||||
|
||||
var (
|
||||
transportHandler func(bool, uint64)
|
||||
transportGeneration uint64
|
||||
@@ -417,6 +433,7 @@ func (ws *WebSocketClient) Close() error {
|
||||
}
|
||||
|
||||
err := conn.Close()
|
||||
|
||||
ws.logger.Printf("Disconnected")
|
||||
notifyTransportState(transportHandler, false, transportGeneration)
|
||||
|
||||
@@ -465,7 +482,9 @@ func (ws *WebSocketClient) readLoop(config *WebSocketConfig, connection *webSock
|
||||
transportGeneration := ws.transportGeneration
|
||||
reconnect := ws.reconnect
|
||||
ws.mu.Unlock()
|
||||
|
||||
_ = connection.conn.Close()
|
||||
|
||||
notifyTransportState(transportHandler, false, transportGeneration)
|
||||
|
||||
// Attempt reconnection if enabled
|
||||
|
||||
@@ -263,14 +263,16 @@ func newStereoPairView(group *models.Group, byDeviceID map[string][]deviceProjec
|
||||
|
||||
func projectedDeviceInfo(controlID string, info *models.DeviceInfo, pair *stereoPairView) *models.DeviceInfo {
|
||||
if info == nil {
|
||||
return info
|
||||
return nil
|
||||
}
|
||||
|
||||
address := projectedIPAddress(controlID, info, "")
|
||||
|
||||
name := info.Name
|
||||
if pair != nil && pair.Name != "" {
|
||||
name = pair.Name
|
||||
}
|
||||
|
||||
if address == info.IPAddress && name == info.Name {
|
||||
return info
|
||||
}
|
||||
@@ -301,9 +303,11 @@ func projectedConnectivity(status *webtypes.DeviceStatus) webtypes.Connectivity
|
||||
if status == nil {
|
||||
return webtypes.ConnectivityOffline
|
||||
}
|
||||
|
||||
if status.Connectivity != "" {
|
||||
return status.Connectivity
|
||||
}
|
||||
|
||||
if status.IsConnected {
|
||||
return webtypes.ConnectivityOnline
|
||||
}
|
||||
|
||||
@@ -66,10 +66,15 @@ func NewDiscoveryService(discoveryInterface string, configuredHosts ...string) *
|
||||
// mDNS/UPnP. If the host is already known, the existing entry's
|
||||
// LastSeen is bumped and the function returns without re-fetching.
|
||||
func (app *WebApp) AddDeviceByHost(host string, port int, source string) {
|
||||
app.addDeviceByHost(host, port, source)
|
||||
app.addDeviceByHost(context.Background(), host, port, source)
|
||||
}
|
||||
|
||||
func (app *WebApp) addDeviceByHost(host string, port int, source string) *webtypes.DeviceConnection {
|
||||
func (app *WebApp) addDeviceByHost(
|
||||
ctx context.Context,
|
||||
host string,
|
||||
port int,
|
||||
source string,
|
||||
) *webtypes.DeviceConnection {
|
||||
// Fast path: skip the network call if we already know this host.
|
||||
if app.TouchDevice(host) {
|
||||
return nil
|
||||
@@ -89,10 +94,11 @@ func (app *WebApp) addDeviceByHost(host string, port int, source string) *webtyp
|
||||
|
||||
// Keep the registry key stable for controls, but expose a canonical numeric
|
||||
// address separately for presentation and sorting.
|
||||
info.IPAddress = resolvedDeviceIPAddress(host, info)
|
||||
info.IPAddress = resolvedDeviceIPAddress(ctx, host, info)
|
||||
|
||||
conn := webtypes.NewDeviceConnection(c, info)
|
||||
conn.MarkHTTPSuccess(time.Now())
|
||||
|
||||
if !app.AddDevice(host, conn) {
|
||||
// Lost a race — another goroutine inserted the same host
|
||||
// between TouchDevice and AddDevice. AddDevice bumped LastSeen
|
||||
@@ -124,7 +130,7 @@ func (app *WebApp) addDeviceByHost(host string, port int, source string) *webtyp
|
||||
return conn
|
||||
}
|
||||
|
||||
func resolvedDeviceIPAddress(host string, info *models.DeviceInfo) string {
|
||||
func resolvedDeviceIPAddress(ctx context.Context, host string, info *models.DeviceInfo) string {
|
||||
if info != nil {
|
||||
if address := numericIPAddress(info.IPAddress); address != "" {
|
||||
return address
|
||||
@@ -142,7 +148,7 @@ func resolvedDeviceIPAddress(host string, info *models.DeviceInfo) string {
|
||||
return address
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
addresses, err := net.DefaultResolver.LookupNetIP(ctx, "ip4", bareHost)
|
||||
@@ -178,7 +184,7 @@ func numericIPAddress(address string) string {
|
||||
// need to distinguish "read failed" from "converged" (the bounded startup
|
||||
// retry) should call seedExtraDevices directly instead.
|
||||
func (app *WebApp) SeedExtraDevices() {
|
||||
if _, _, _, err := app.seedExtraDevices(); err != nil {
|
||||
if _, _, _, err := app.seedExtraDevices(context.Background()); err != nil {
|
||||
log.Printf("SeedExtraDevices: failed to read extra device hosts: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -203,7 +209,9 @@ type seededExtraDevice struct {
|
||||
// A non-nil error means the hook itself failed (e.g. a datastore glitch);
|
||||
// callers must treat that as "unknown state, don't prune, don't declare
|
||||
// ready" rather than as an empty desired set.
|
||||
func (app *WebApp) seedExtraDevices() (inserted []seededExtraDevice, removed int, desired map[string]struct{}, err error) {
|
||||
func (app *WebApp) seedExtraDevices(
|
||||
ctx context.Context,
|
||||
) (inserted []seededExtraDevice, removed int, desired map[string]struct{}, err error) {
|
||||
if app.ExtraDeviceHosts == nil {
|
||||
return nil, 0, nil, nil
|
||||
}
|
||||
@@ -231,7 +239,7 @@ func (app *WebApp) seedExtraDevices() (inserted []seededExtraDevice, removed int
|
||||
go func(h string) {
|
||||
defer wg.Done()
|
||||
|
||||
conn := app.addDeviceByHost(h, 8090, "service-store")
|
||||
conn := app.addDeviceByHost(ctx, h, 8090, "service-store")
|
||||
if conn == nil {
|
||||
return
|
||||
}
|
||||
@@ -266,7 +274,7 @@ func (app *WebApp) seedExtraDevices() (inserted []seededExtraDevice, removed int
|
||||
// while the service is starting.
|
||||
func (app *WebApp) SeedExtraDevicesUntilReady(ctx context.Context, retryInterval time.Duration) {
|
||||
retryUntilReady(ctx, retryInterval, func() bool {
|
||||
inserted, removed, desired, err := app.seedExtraDevices()
|
||||
inserted, removed, desired, err := app.seedExtraDevices(ctx)
|
||||
if err != nil {
|
||||
log.Printf("SeedExtraDevicesUntilReady: failed to read extra device hosts, will retry: %v", err)
|
||||
return false
|
||||
@@ -373,7 +381,9 @@ func (app *WebApp) DiscoverDevices(ctx context.Context, discoveryService *discov
|
||||
|
||||
// Re-sync from the external device source (embedded: the service datastore).
|
||||
// No-op when ExtraDeviceHosts is unset.
|
||||
app.SeedExtraDevices()
|
||||
if _, _, _, err := app.seedExtraDevices(ctx); err != nil {
|
||||
log.Printf("DiscoverDevices: failed to read extra device hosts: %v", err)
|
||||
}
|
||||
|
||||
// Own mDNS/UPnP sweep — standalone only. The embedded build passes a nil
|
||||
// discovery service and relies entirely on the host service's discovery.
|
||||
@@ -394,7 +404,7 @@ func (app *WebApp) DiscoverDevices(ctx context.Context, discoveryService *discov
|
||||
log.Printf("Found %d devices", len(devices))
|
||||
|
||||
for _, device := range devices {
|
||||
app.AddDeviceByHost(device.Host, device.Port, classifySource(device.DiscoveryMethod))
|
||||
app.addDeviceByHost(ctx, device.Host, device.Port, classifySource(device.DiscoveryMethod))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,13 +107,13 @@ func TestResolvedDeviceIPAddressSeparatesHostnameFromReportedAddress(t *testing.
|
||||
{Type: "SCM", IPAddress: "192.0.2.42"},
|
||||
}}
|
||||
|
||||
if got := resolvedDeviceIPAddress("kitchen.local", info); got != "192.0.2.42" {
|
||||
if got := resolvedDeviceIPAddress(context.Background(), "kitchen.local", info); got != "192.0.2.42" {
|
||||
t.Fatalf("resolved address = %q, want reported speaker address", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolvedDeviceIPAddressPreservesLiteralIP(t *testing.T) {
|
||||
if got := resolvedDeviceIPAddress("192.0.2.20", &models.DeviceInfo{}); got != "192.0.2.20" {
|
||||
if got := resolvedDeviceIPAddress(context.Background(), "192.0.2.20", &models.DeviceInfo{}); got != "192.0.2.20" {
|
||||
t.Fatalf("resolved literal address = %q, want unchanged literal", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,6 +465,7 @@ func (app *WebApp) HandleDeleteDevice(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
app.BroadcastDeviceList()
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@@ -1057,6 +1058,7 @@ func (app *WebApp) HandleGetZone(w http.ResponseWriter, r *http.Request) {
|
||||
masterIP := app.findIPByHwID(zone.Master)
|
||||
|
||||
masterName := ""
|
||||
|
||||
if conn, ok := app.GetDevice(masterIP); ok {
|
||||
if info := conn.Info(); info != nil {
|
||||
masterName = info.Name
|
||||
@@ -1073,6 +1075,7 @@ func (app *WebApp) HandleGetZone(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
for _, m := range zone.Members {
|
||||
name := ""
|
||||
|
||||
if conn, ok := app.GetDevice(m.IP); ok {
|
||||
if info := conn.Info(); info != nil {
|
||||
name = info.Name
|
||||
|
||||
@@ -492,6 +492,7 @@ func (app *WebApp) ConnectDeviceWebSocket(deviceID string, conn *webtypes.Device
|
||||
if conn.Client == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !conn.TryStartWebSocketLoop() {
|
||||
return
|
||||
}
|
||||
@@ -605,6 +606,7 @@ func (app *WebApp) ConnectDeviceWebSocket(deviceID string, conn *webtypes.Device
|
||||
if !published {
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed to connect WebSocket for device %s: %v (retrying in %s)", sanitizeLog(deviceID), err, backoff)
|
||||
|
||||
@@ -627,10 +629,6 @@ func (app *WebApp) ConnectDeviceWebSocket(deviceID string, conn *webtypes.Device
|
||||
// disconnected would otherwise stay stale until the next WS event.
|
||||
go app.UpdateDeviceStatus(deviceID, conn)
|
||||
|
||||
// Reset backoff after a successful connect so the next failure
|
||||
// starts at the lowest cadence again.
|
||||
backoff = initialBackoff
|
||||
|
||||
<-conn.Done()
|
||||
|
||||
return
|
||||
@@ -737,6 +735,7 @@ func (app *WebApp) updateDeviceStatus(_ string, conn *webtypes.DeviceConnection,
|
||||
if stereoCapable && groupBaseline == nil {
|
||||
groupGeneration = conn.BeginGroupRefresh()
|
||||
}
|
||||
|
||||
nameGeneration := conn.BeginNameRefresh()
|
||||
|
||||
// Phase 1: slow network fetches. Local vars only, no shared state
|
||||
|
||||
@@ -126,6 +126,7 @@ type DeviceStatus struct {
|
||||
// evidence but cannot override a current direct-path success.
|
||||
type Connectivity string
|
||||
|
||||
// Player connectivity states derived from direct and speaker-reported evidence.
|
||||
const (
|
||||
ConnectivityOnline Connectivity = "online"
|
||||
ConnectivityStale Connectivity = "stale"
|
||||
@@ -179,6 +180,7 @@ func NewDeviceConnection(c *client.Client, info *models.DeviceInfo) *DeviceConne
|
||||
IsConnected: false,
|
||||
LastActivity: time.Now(),
|
||||
})
|
||||
|
||||
if info != nil {
|
||||
conn.storeDeviceName(info.Name)
|
||||
}
|
||||
@@ -290,6 +292,7 @@ func (c *DeviceConnection) SetWebSocket(ws *client.WebSocketClient) bool {
|
||||
select {
|
||||
case <-c.done:
|
||||
c.webSocketMu.Unlock()
|
||||
|
||||
if ws != nil {
|
||||
_ = ws.Close()
|
||||
}
|
||||
@@ -415,6 +418,7 @@ func (c *DeviceConnection) BeginHTTPPoll() uint64 {
|
||||
if c.pollEventGen == nil {
|
||||
c.pollEventGen = make(map[uint64]uint64)
|
||||
}
|
||||
|
||||
c.pollEventGen[c.nextPollGeneration] = c.speakerEventGen
|
||||
|
||||
return c.nextPollGeneration
|
||||
@@ -431,6 +435,7 @@ func (c *DeviceConnection) ApplySpeakerEventAt(at time.Time, mut func(*DeviceSta
|
||||
if mut != nil {
|
||||
mut(status)
|
||||
}
|
||||
|
||||
c.applyConnectivityLocked(status, at)
|
||||
})
|
||||
}
|
||||
@@ -481,10 +486,12 @@ func (c *DeviceConnection) ObserveEventStreamTransport(
|
||||
}
|
||||
|
||||
c.lastTransportGeneration = generation
|
||||
|
||||
c.eventStreamConnected = connected
|
||||
if connected {
|
||||
c.recordDirectSuccessLocked(at)
|
||||
}
|
||||
|
||||
c.UpdateStatus(func(status *DeviceStatus) {
|
||||
c.applyConnectivityLocked(status, at)
|
||||
})
|
||||
@@ -502,6 +509,7 @@ func (c *DeviceConnection) ObserveEventStream(connected bool, at time.Time) {
|
||||
if connected {
|
||||
c.recordDirectSuccessLocked(at)
|
||||
}
|
||||
|
||||
c.UpdateStatus(func(status *DeviceStatus) {
|
||||
c.applyConnectivityLocked(status, at)
|
||||
})
|
||||
@@ -550,7 +558,9 @@ func (c *DeviceConnection) CompleteHTTPPoll(
|
||||
if merge != nil && knownGeneration && pollEventGeneration == c.speakerEventGen {
|
||||
merge(status)
|
||||
}
|
||||
|
||||
c.applyConnectivityLocked(status, at)
|
||||
|
||||
if success {
|
||||
status.LastActivity = at
|
||||
}
|
||||
@@ -601,6 +611,7 @@ func withinConnectivityGrace(at, success time.Time) bool {
|
||||
if success.IsZero() {
|
||||
return false
|
||||
}
|
||||
|
||||
if at.Before(success) {
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user