From fb545947ecbada33261bc84eac3b9b98ca0ef49a Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Sat, 9 Dec 2023 01:17:25 +0100 Subject: [PATCH] Feature/gql improve webui update status (#783) * Remove "updateAvailable" from webui update info Doesn't add anything * Extract status creation into function * Optionally emit status immediately Otherwise, some emissions can get lost due to the 1s sample period * Rename "STOPPED" state to "IDLE" * Reset webui update status Currently, the update status never gets reset. Thus, it will be "FINISHED" or "ERROR" until the next update gets triggered. Due to this, the client won't know that the update result was already handled and will handle it again the next time it gets the update status. To prevent this, the client has to be able to tell the server that it has handled the update result and that it can be resetted --- .../graphql/mutations/InfoMutation.kt | 30 ++++--- .../graphql/types/WebUIUpdateType.kt | 3 +- .../server/util/WebInterfaceManager.kt | 80 +++++++++++-------- 3 files changed, 65 insertions(+), 48 deletions(-) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/InfoMutation.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/InfoMutation.kt index f28a8068..f9c3b454 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/InfoMutation.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/InfoMutation.kt @@ -4,11 +4,9 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.withTimeout import suwayomi.tachidesk.graphql.types.UpdateState.DOWNLOADING import suwayomi.tachidesk.graphql.types.UpdateState.ERROR -import suwayomi.tachidesk.graphql.types.UpdateState.STOPPED -import suwayomi.tachidesk.graphql.types.WebUIUpdateInfo +import suwayomi.tachidesk.graphql.types.UpdateState.IDLE import suwayomi.tachidesk.graphql.types.WebUIUpdateStatus import suwayomi.tachidesk.server.JavalinSetup.future -import suwayomi.tachidesk.server.serverConfig import suwayomi.tachidesk.server.util.WebInterfaceManager import java.util.concurrent.CompletableFuture import kotlin.time.Duration.Companion.seconds @@ -37,16 +35,7 @@ class InfoMutation { return@withTimeout WebUIUpdatePayload( input.clientMutationId, - WebUIUpdateStatus( - info = - WebUIUpdateInfo( - channel = serverConfig.webUIChannel.value, - tag = version, - updateAvailable, - ), - state = if (didUpdateCheckFail) ERROR else STOPPED, - progress = 0, - ), + WebInterfaceManager.getStatus(version, if (didUpdateCheckFail) ERROR else IDLE), ) } try { @@ -62,4 +51,19 @@ class InfoMutation { } } } + + fun resetWebUIUpdateStatus(): CompletableFuture { + return future { + withTimeout(30.seconds) { + val isUpdateFinished = WebInterfaceManager.status.value.state != DOWNLOADING + if (!isUpdateFinished) { + throw Exception("Status reset is not allowed during status \"$DOWNLOADING\"") + } + + WebInterfaceManager.resetStatus() + + WebInterfaceManager.status.first { it.state == IDLE } + } + } + } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/WebUIUpdateType.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/WebUIUpdateType.kt index 56d01820..766a3170 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/WebUIUpdateType.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/WebUIUpdateType.kt @@ -14,11 +14,10 @@ data class WebUIUpdateCheck( data class WebUIUpdateInfo( val channel: String, val tag: String, - val updateAvailable: Boolean, ) enum class UpdateState { - STOPPED, + IDLE, DOWNLOADING, FINISHED, ERROR, diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/util/WebInterfaceManager.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/util/WebInterfaceManager.kt index 8abd9710..c00dbe74 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/util/WebInterfaceManager.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/util/WebInterfaceManager.kt @@ -41,7 +41,7 @@ import suwayomi.tachidesk.graphql.types.UpdateState import suwayomi.tachidesk.graphql.types.UpdateState.DOWNLOADING import suwayomi.tachidesk.graphql.types.UpdateState.ERROR import suwayomi.tachidesk.graphql.types.UpdateState.FINISHED -import suwayomi.tachidesk.graphql.types.UpdateState.STOPPED +import suwayomi.tachidesk.graphql.types.UpdateState.IDLE import suwayomi.tachidesk.graphql.types.WebUIUpdateInfo import suwayomi.tachidesk.graphql.types.WebUIUpdateStatus import suwayomi.tachidesk.server.ApplicationDirs @@ -137,25 +137,22 @@ object WebInterfaceManager { private val notifyFlow = MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = DROP_OLDEST) - @OptIn(FlowPreview::class) + private val statusFlow = MutableSharedFlow() val status = - notifyFlow.sample(1.seconds) - .stateIn( - scope, - SharingStarted.Eagerly, - WebUIUpdateStatus( - info = - WebUIUpdateInfo( - channel = serverConfig.webUIChannel.value, - tag = "", - updateAvailable = false, - ), - state = STOPPED, - progress = 0, - ), - ) + statusFlow.stateIn( + scope, + SharingStarted.Eagerly, + getStatus(), + ) init { + scope.launch { + @OptIn(FlowPreview::class) + notifyFlow.sample(1.seconds).collect { + statusFlow.emit(it) + } + } + serverConfig.subscribeTo( combine(serverConfig.webUIUpdateCheckInterval, serverConfig.webUIFlavor) { interval, flavor -> Pair( @@ -168,7 +165,7 @@ object WebInterfaceManager { ) } - suspend fun getAboutInfo(): AboutWebUI { + fun getAboutInfo(): AboutWebUI { val currentVersion = getLocalVersion() val failedToGetVersion = currentVersion === "r-1" @@ -182,6 +179,26 @@ object WebInterfaceManager { ) } + fun getStatus( + version: String = "", + state: UpdateState = IDLE, + progress: Int = 0, + ): WebUIUpdateStatus { + return WebUIUpdateStatus( + info = + WebUIUpdateInfo( + channel = serverConfig.webUIChannel.value, + tag = version, + ), + state, + progress, + ) + } + + fun resetStatus() { + emitStatus("", IDLE, 0, immediate = true) + } + private var serveWebUI: () -> Unit = {} fun setServeWebUI(serveWebUI: () -> Unit) { @@ -535,20 +552,17 @@ object WebInterfaceManager { version: String, state: UpdateState, progress: Int, + immediate: Boolean = false, ) { scope.launch { - notifyFlow.emit( - WebUIUpdateStatus( - info = - WebUIUpdateInfo( - channel = serverConfig.webUIChannel.value, - tag = version, - updateAvailable = true, - ), - state, - progress, - ), - ) + val status = getStatus(version, state, progress) + + if (immediate) { + statusFlow.emit(status) + return@launch + } + + notifyFlow.emit(status) } } @@ -559,7 +573,7 @@ object WebInterfaceManager { } suspend fun downloadVersion(version: String) { - emitStatus(version, DOWNLOADING, 0) + emitStatus(version, DOWNLOADING, 0, immediate = true) try { val webUIZip = "${WebUIFlavor.WEBUI.baseFileName}-$version.zip" @@ -586,11 +600,11 @@ object WebInterfaceManager { extractDownload(webUIZipPath, applicationDirs.webUIRoot) log.info { "Extracting WebUI zip Done." } - emitStatus(version, FINISHED, 100) + emitStatus(version, FINISHED, 100, immediate = true) serveWebUI() } catch (e: Exception) { - emitStatus(version, ERROR, 0) + emitStatus(version, ERROR, 0, immediate = true) throw e } }