From 916da91ccb63b035e8f31e290c781ac93cc489b1 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 5 Jun 2022 09:51:37 -0500 Subject: [PATCH] Fix:Android auto check if server connection available and use first available server config, add ping api check with 3 second timeout #212 #167 --- .../audiobookshelf/app/media/MediaManager.kt | 87 ++++++++++++++----- .../audiobookshelf/app/server/ApiHandler.kt | 48 +++++++--- 2 files changed, 99 insertions(+), 36 deletions(-) diff --git a/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt b/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt index ef1c2952..36e53b61 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt @@ -8,6 +8,10 @@ import com.audiobookshelf.app.device.DeviceManager import com.audiobookshelf.app.server.ApiHandler import java.util.* import io.paperdb.Paper +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.runBlocking +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine class MediaManager(var apiHandler: ApiHandler, var ctx: Context) { val tag = "MediaManager" @@ -137,6 +141,48 @@ class MediaManager(var apiHandler: ApiHandler, var ctx: Context) { } } + suspend fun checkServerConnection(config:ServerConnectionConfig) : Boolean { + var successfulPing = false + suspendCoroutine { cont -> + apiHandler.pingServer(config) { + Log.d(tag, "checkServerConnection: Checked server conn for ${config.address} result = $it") + successfulPing = it + cont.resume(it) + } + } + return successfulPing + } + + fun checkSetValidServerConnectionConfig(cb: (Boolean) -> Unit) = runBlocking { + if (!apiHandler.isOnline()) cb(false) + else { + coroutineScope { + var hasValidConn = false + + // First check if the current selected config is pingable + DeviceManager.serverConnectionConfig?.let { + hasValidConn = checkServerConnection(it) + Log.d(tag, "checkSetValidServerConnectionConfig: Current config ${DeviceManager.serverAddress} is pingable? $hasValidConn") + } + + if (!hasValidConn) { + // Loop through available configs and check if can connect + for (config: ServerConnectionConfig in DeviceManager.deviceData.serverConnectionConfigs) { + val result = checkServerConnection(config) + + if (result) { + hasValidConn = true + DeviceManager.serverConnectionConfig = config + break + } + } + } + + cb(hasValidConn) + } + } + } + // TODO: Load currently listening category for local items fun loadLocalCategory():List { val localBooks = DeviceManager.dbManager.getLocalLibraryItems("book") @@ -158,35 +204,32 @@ class MediaManager(var apiHandler: ApiHandler, var ctx: Context) { val localCategories = loadLocalCategory() cats.addAll(localCategories) - // Connected to server and has internet - load other cats - if (apiHandler.isOnline() && (DeviceManager.isConnectedToServer || DeviceManager.hasLastServerConnectionConfig)) { - if (!DeviceManager.isConnectedToServer) { - DeviceManager.serverConnectionConfig = DeviceManager.deviceData.getLastServerConnectionConfig() - Log.d(tag, "Not connected to server, set last server \"${DeviceManager.serverAddress}\"") - } + // Check if any valid server connection if not use locally downloaded books + checkSetValidServerConnectionConfig { isConnected -> + if (isConnected) { + serverConfigIdUsed = DeviceManager.serverConnectionConfigId - serverConfigIdUsed = DeviceManager.serverConnectionConfigId + loadLibraries { libraries -> + val library = libraries[0] + Log.d(tag, "Loading categories for library ${library.name} - ${library.id} - ${library.mediaType}") - loadLibraries { libraries -> - val library = libraries[0] - Log.d(tag, "Loading categories for library ${library.name} - ${library.id} - ${library.mediaType}") + loadLibraryCategories(library.id) { libraryCategories -> - loadLibraryCategories(library.id) { libraryCategories -> - - // Only using book or podcast library categories for now - libraryCategories.forEach { - // Log.d(tag, "Found library category ${it.label} with type ${it.type}") - if (it.type == library.mediaType) { - // Log.d(tag, "Using library category ${it.id}") - cats.add(it) + // Only using book or podcast library categories for now + libraryCategories.forEach { + // Log.d(tag, "Found library category ${it.label} with type ${it.type}") + if (it.type == library.mediaType) { + // Log.d(tag, "Using library category ${it.id}") + cats.add(it) + } } - } - cb(cats) + cb(cats) + } } + } else { // Not connected/no internet sent downloaded cats only + cb(cats) } - } else { // Not connected/no internet sent downloaded cats only - cb(cats) } } diff --git a/android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt b/android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt index 0f0a93b9..1bf04c4c 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt @@ -19,11 +19,13 @@ import okhttp3.MediaType.Companion.toMediaType import okhttp3.RequestBody.Companion.toRequestBody import org.json.JSONObject import java.io.IOException +import java.util.concurrent.TimeUnit class ApiHandler(var ctx:Context) { val tag = "ApiHandler" - private var client = OkHttpClient() + private var defaultClient = OkHttpClient() + private var pingClient = OkHttpClient.Builder().callTimeout(3, TimeUnit.SECONDS).build() var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature()) var storageSharedPreferences: SharedPreferences? = null @@ -33,11 +35,14 @@ class ApiHandler(var ctx:Context) { data class MediaProgressSyncResponsePayload(val numServerProgressUpdates:Int, val localProgressUpdates:List) data class LocalMediaProgressSyncResultsPayload(var numLocalMediaProgressForServer:Int, var numServerProgressUpdates:Int, var numLocalProgressUpdates:Int) - fun getRequest(endpoint:String, cb: (JSObject) -> Unit) { + fun getRequest(endpoint:String, httpClient:OkHttpClient?, config:ServerConnectionConfig?, cb: (JSObject) -> Unit) { + val address = config?.address ?: DeviceManager.serverAddress + val token = config?.token ?: DeviceManager.token + val request = Request.Builder() - .url("${DeviceManager.serverAddress}$endpoint").addHeader("Authorization", "Bearer ${DeviceManager.token}") + .url("${address}$endpoint").addHeader("Authorization", "Bearer $token") .build() - makeRequest(request, cb) + makeRequest(request, httpClient, cb) } fun postRequest(endpoint:String, payload: JSObject, cb: (JSObject) -> Unit) { @@ -46,7 +51,7 @@ class ApiHandler(var ctx:Context) { val request = Request.Builder().post(requestBody) .url("${DeviceManager.serverAddress}$endpoint").addHeader("Authorization", "Bearer ${DeviceManager.token}") .build() - makeRequest(request, cb) + makeRequest(request, null, cb) } fun patchRequest(endpoint:String, payload: JSObject, cb: (JSObject) -> Unit) { @@ -55,7 +60,7 @@ class ApiHandler(var ctx:Context) { val request = Request.Builder().patch(requestBody) .url("${DeviceManager.serverAddress}$endpoint").addHeader("Authorization", "Bearer ${DeviceManager.token}") .build() - makeRequest(request, cb) + makeRequest(request, null, cb) } fun isOnline(): Boolean { @@ -76,7 +81,8 @@ class ApiHandler(var ctx:Context) { return false } - fun makeRequest(request:Request, cb: (JSObject) -> Unit) { + fun makeRequest(request:Request, httpClient:OkHttpClient?, cb: (JSObject) -> Unit) { + val client = httpClient ?: defaultClient client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { Log.d(tag, "FAILURE TO CONNECT") @@ -114,7 +120,7 @@ class ApiHandler(var ctx:Context) { fun getLibraries(cb: (List) -> Unit) { val mapper = jacksonMapper - getRequest("/api/libraries") { + getRequest("/api/libraries", null,null) { val libraries = mutableListOf() if (it.has("value")) { val array = it.getJSONArray("value") @@ -128,7 +134,7 @@ class ApiHandler(var ctx:Context) { } fun getLibraryItem(libraryItemId:String, cb: (LibraryItem) -> Unit) { - getRequest("/api/items/$libraryItemId?expanded=1") { + getRequest("/api/items/$libraryItemId?expanded=1", null, null) { val libraryItem = jacksonMapper.readValue(it.toString()) cb(libraryItem) } @@ -137,14 +143,14 @@ class ApiHandler(var ctx:Context) { fun getLibraryItemWithProgress(libraryItemId:String, episodeId:String?, cb: (LibraryItem) -> Unit) { var requestUrl = "/api/items/$libraryItemId?expanded=1&include=progress" if (!episodeId.isNullOrEmpty()) requestUrl += "&episode=$episodeId" - getRequest(requestUrl) { + getRequest(requestUrl, null, null) { val libraryItem = jacksonMapper.readValue(it.toString()) cb(libraryItem) } } fun getLibraryItems(libraryId:String, cb: (List) -> Unit) { - getRequest("/api/libraries/$libraryId/items?limit=100&minified=1") { + getRequest("/api/libraries/$libraryId/items?limit=100&minified=1", null, null) { val items = mutableListOf() if (it.has("results")) { val array = it.getJSONArray("results") @@ -158,7 +164,7 @@ class ApiHandler(var ctx:Context) { } fun getLibraryCategories(libraryId:String, cb: (List) -> Unit) { - getRequest("/api/libraries/$libraryId/personalized") { + getRequest("/api/libraries/$libraryId/personalized", null, null) { val items = mutableListOf() if (it.has("value")) { val array = it.getJSONArray("value") @@ -265,7 +271,7 @@ class ApiHandler(var ctx:Context) { fun getMediaProgress(libraryItemId:String, episodeId:String?, cb: (MediaProgress) -> Unit) { val endpoint = if(episodeId.isNullOrEmpty()) "/api/me/progress/$libraryItemId" else "/api/me/progress/$libraryItemId/$episodeId" - getRequest(endpoint) { + getRequest(endpoint, null, null) { val progress = jacksonMapper.readValue(it.toString()) cb(progress) } @@ -273,7 +279,7 @@ class ApiHandler(var ctx:Context) { fun getPlaybackSession(playbackSessionId:String, cb: (PlaybackSession?) -> Unit) { val endpoint = "/api/session/$playbackSessionId" - getRequest(endpoint) { + getRequest(endpoint, null, null) { val err = it.getString("error") if (!err.isNullOrEmpty()) { cb(null) @@ -282,4 +288,18 @@ class ApiHandler(var ctx:Context) { } } } + + fun pingServer(config:ServerConnectionConfig, cb: (Boolean) -> Unit) { + Log.d(tag, "pingServer: Pinging ${config.address}") + getRequest("/ping", pingClient, config) { + val success = it.getString("success") + if (success.isNullOrEmpty()) { + Log.d(tag, "pingServer: Ping ${config.address} Failed") + cb(false) + } else { + Log.d(tag, "pingServer: Ping ${config.address} Successful") + cb(true) + } + } + } }