Fix some IO related crashes (#1008)

## Description
- Catch network exceptions when querying to populate the nav drawer
- Perform screensaver work on IO thread
- Fix possible crash if screensaver service runs while regular activity
is destroyed
- Fix genre images & backdrop images not loading

This PR also adds an Jellyfin SDK `ApiClient` wrapper to push all
network calls onto the IO thread. And tries to use a more strict
network-on-main policy for debug builds.

### Related issues
Fixes #994
Fixes #1001
Should fix #1003

### Testing
Emulator with simulated exception throws

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Ray 2026-02-26 15:48:39 -05:00 committed by GitHub
parent 37d52ef57e
commit 9b995bf6ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 147 additions and 42 deletions

View file

@ -0,0 +1,88 @@
package com.github.damontecres.wholphin.util
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.ApiClientFactory
import org.jellyfin.sdk.api.client.HttpClientOptions
import org.jellyfin.sdk.api.client.HttpMethod
import org.jellyfin.sdk.api.client.RawResponse
import org.jellyfin.sdk.api.okhttp.OkHttpFactory
import org.jellyfin.sdk.api.sockets.SocketApi
import org.jellyfin.sdk.api.sockets.SocketConnection
import org.jellyfin.sdk.api.sockets.SocketConnectionFactory
import org.jellyfin.sdk.model.ClientInfo
import org.jellyfin.sdk.model.DeviceInfo
import kotlin.coroutines.CoroutineContext
/**
* Wraps [ApiClient.request] with the given [CoroutineContext]
*/
class CoroutineContextApiClient(
private val client: ApiClient,
private val coroutineContext: CoroutineContext = Dispatchers.IO,
) : ApiClient() {
override val baseUrl: String?
get() = client.baseUrl
override val accessToken: String?
get() = client.accessToken
override val clientInfo: ClientInfo
get() = client.clientInfo
override val deviceInfo: DeviceInfo
get() = client.deviceInfo
override val httpClientOptions: HttpClientOptions
get() = client.httpClientOptions
override val webSocket: SocketApi
get() = client.webSocket
override fun update(
baseUrl: String?,
accessToken: String?,
clientInfo: ClientInfo,
deviceInfo: DeviceInfo,
) {
client.update(baseUrl, accessToken, clientInfo, deviceInfo)
}
override suspend fun request(
method: HttpMethod,
pathTemplate: String,
pathParameters: Map<String, Any?>,
queryParameters: Map<String, Any?>,
requestBody: Any?,
): RawResponse =
withContext(coroutineContext) {
client.request(
method,
pathTemplate,
pathParameters,
queryParameters,
requestBody,
)
}
}
class CoroutineContextApiClientFactory(
private val factory: OkHttpFactory,
private val coroutineContext: CoroutineContext = Dispatchers.IO,
) : ApiClientFactory,
SocketConnectionFactory {
override fun create(
baseUrl: String?,
accessToken: String?,
clientInfo: ClientInfo,
deviceInfo: DeviceInfo,
httpClientOptions: HttpClientOptions,
socketConnectionFactory: SocketConnectionFactory,
): ApiClient =
CoroutineContextApiClient(
factory.create(baseUrl, accessToken, clientInfo, deviceInfo, httpClientOptions, socketConnectionFactory),
coroutineContext,
)
override fun create(
clientOptions: HttpClientOptions,
scope: CoroutineScope,
): SocketConnection = factory.create(clientOptions, scope)
}