diag(android): log raw SOAP response body for first 3 UPnP polls
android / Build + lint + test (push) Failing after 1m22s
android / Build + lint + test (push) Failing after 1m22s
Add optional onRawResponse callback to SoapClient; loggingSoapClient factory emits the first 6 GetPositionInfo/GetTransportInfo bodies (3 poll cycles) at WARN so release logs capture them. Wire into transportFor so every AVTransportClient for a new UPnP session logs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.fabledsword.minstrel.player.output.upnp
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.HttpUrl
|
||||
@@ -8,6 +9,7 @@ import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import org.xmlpull.v1.XmlPullParserFactory
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Minimal SOAP/UPnP envelope builder + POST. Hand-rolled rather than
|
||||
@@ -25,6 +27,7 @@ import org.xmlpull.v1.XmlPullParserFactory
|
||||
*/
|
||||
class SoapClient(
|
||||
private val okHttp: OkHttpClient,
|
||||
private val onRawResponse: ((action: String, body: String) -> Unit)? = null,
|
||||
) {
|
||||
suspend fun call(
|
||||
controlUrl: HttpUrl,
|
||||
@@ -41,6 +44,7 @@ class SoapClient(
|
||||
.build()
|
||||
okHttp.newCall(request).execute().use { response ->
|
||||
val body = response.body?.string().orEmpty()
|
||||
onRawResponse?.invoke(action, body)
|
||||
if (!response.isSuccessful) {
|
||||
throw SoapFaultException(faultCodeOf(body), faultDescriptionOf(body))
|
||||
}
|
||||
@@ -132,6 +136,28 @@ class SoapClient(
|
||||
class SoapFaultException(val code: String, val description: String) :
|
||||
Exception("SOAP fault $code: $description")
|
||||
|
||||
private const val MAX_DIAGNOSTIC_RESPONSES = 6 // 3 polls x 2 action types
|
||||
private const val MAX_BODY_LOG_CHARS = 2048
|
||||
|
||||
/**
|
||||
* Returns a [SoapClient] that logs the raw response body for the first
|
||||
* [MAX_DIAGNOSTIC_RESPONSES] GetPositionInfo / GetTransportInfo calls (i.e.
|
||||
* the first 3 poll cycles of a UPnP session). After that the callback is a
|
||||
* no-op so there is no persistent log spam. Logged at WARN so release builds
|
||||
* capture it without a separate log-level override.
|
||||
*/
|
||||
internal fun loggingSoapClient(okHttp: OkHttpClient, label: String): SoapClient {
|
||||
val counter = AtomicInteger(0)
|
||||
return SoapClient(okHttp) { action, body ->
|
||||
if (action != "GetPositionInfo" && action != "GetTransportInfo") return@SoapClient
|
||||
val n = counter.incrementAndGet()
|
||||
if (n <= MAX_DIAGNOSTIC_RESPONSES) {
|
||||
Timber.w("UPnP %s response #%d (%s): %s",
|
||||
label, n, action, body.take(MAX_BODY_LOG_CHARS))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** XML-escapes a string value for embedding as text content inside a SOAP envelope. */
|
||||
internal fun xmlEscape(v: String): String = v
|
||||
.replace("&", "&")
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ class UpnpDiscoveryController @Inject constructor(
|
||||
*/
|
||||
fun transportFor(routeId: String): AVTransportClient? {
|
||||
val route = routesInternal.value.firstOrNull { it.id == routeId } ?: return null
|
||||
return AVTransportClient(SoapClient(okHttp), route.avTransportControlUrl)
|
||||
return AVTransportClient(loggingSoapClient(okHttp, route.name), route.avTransportControlUrl)
|
||||
}
|
||||
|
||||
private suspend fun handleDiscovery(locationUrl: String) {
|
||||
|
||||
Reference in New Issue
Block a user