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
|
package com.fabledsword.minstrel.player.output.upnp
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
@@ -8,6 +9,7 @@ import okhttp3.Request
|
|||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import org.xmlpull.v1.XmlPullParser
|
import org.xmlpull.v1.XmlPullParser
|
||||||
import org.xmlpull.v1.XmlPullParserFactory
|
import org.xmlpull.v1.XmlPullParserFactory
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimal SOAP/UPnP envelope builder + POST. Hand-rolled rather than
|
* Minimal SOAP/UPnP envelope builder + POST. Hand-rolled rather than
|
||||||
@@ -25,6 +27,7 @@ import org.xmlpull.v1.XmlPullParserFactory
|
|||||||
*/
|
*/
|
||||||
class SoapClient(
|
class SoapClient(
|
||||||
private val okHttp: OkHttpClient,
|
private val okHttp: OkHttpClient,
|
||||||
|
private val onRawResponse: ((action: String, body: String) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
suspend fun call(
|
suspend fun call(
|
||||||
controlUrl: HttpUrl,
|
controlUrl: HttpUrl,
|
||||||
@@ -41,6 +44,7 @@ class SoapClient(
|
|||||||
.build()
|
.build()
|
||||||
okHttp.newCall(request).execute().use { response ->
|
okHttp.newCall(request).execute().use { response ->
|
||||||
val body = response.body?.string().orEmpty()
|
val body = response.body?.string().orEmpty()
|
||||||
|
onRawResponse?.invoke(action, body)
|
||||||
if (!response.isSuccessful) {
|
if (!response.isSuccessful) {
|
||||||
throw SoapFaultException(faultCodeOf(body), faultDescriptionOf(body))
|
throw SoapFaultException(faultCodeOf(body), faultDescriptionOf(body))
|
||||||
}
|
}
|
||||||
@@ -132,6 +136,28 @@ class SoapClient(
|
|||||||
class SoapFaultException(val code: String, val description: String) :
|
class SoapFaultException(val code: String, val description: String) :
|
||||||
Exception("SOAP fault $code: $description")
|
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. */
|
/** XML-escapes a string value for embedding as text content inside a SOAP envelope. */
|
||||||
internal fun xmlEscape(v: String): String = v
|
internal fun xmlEscape(v: String): String = v
|
||||||
.replace("&", "&")
|
.replace("&", "&")
|
||||||
|
|||||||
+1
-1
@@ -90,7 +90,7 @@ class UpnpDiscoveryController @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
fun transportFor(routeId: String): AVTransportClient? {
|
fun transportFor(routeId: String): AVTransportClient? {
|
||||||
val route = routesInternal.value.firstOrNull { it.id == routeId } ?: return null
|
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) {
|
private suspend fun handleDiscovery(locationUrl: String) {
|
||||||
|
|||||||
Reference in New Issue
Block a user