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
This commit is contained in:
schroda
2023-12-09 01:17:25 +01:00
committed by GitHub
parent df57070b70
commit fb545947ec
3 changed files with 65 additions and 48 deletions

View File

@@ -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<WebUIUpdateStatus> {
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 }
}
}
}
}

View File

@@ -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,

View File

@@ -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<WebUIUpdateStatus>(extraBufferCapacity = 1, onBufferOverflow = DROP_OLDEST)
@OptIn(FlowPreview::class)
private val statusFlow = MutableSharedFlow<WebUIUpdateStatus>()
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
}
}