Feature/graphql web UI (#649)

* Add "server" to "checkForUpdate" logic names

* Use "webUIRoot" as default path for "getLocalVersion"

* Use local version as default version for "isUpdateAvailable"

* Return the version with the webUI update check

* Update WebinterfaceManager to be async

* Add query, mutation and subscription for webUI update

* Catch error and return default error value for missing local WebUI version
This commit is contained in:
schroda
2023-08-10 02:46:48 +02:00
committed by GitHub
parent 684bb1875c
commit 74ff112e7a
7 changed files with 308 additions and 61 deletions

View File

@@ -0,0 +1,62 @@
package suwayomi.tachidesk.graphql.mutations
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withTimeout
import suwayomi.tachidesk.graphql.types.UpdateState.DOWNLOADING
import suwayomi.tachidesk.graphql.types.UpdateState.FINISHED
import suwayomi.tachidesk.graphql.types.UpdateState.STOPPED
import suwayomi.tachidesk.graphql.types.WebUIUpdateInfo
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
class InfoMutation {
data class WebUIUpdateInput(
val clientMutationId: String? = null
)
data class WebUIUpdatePayload(
val clientMutationId: String?,
val updateStatus: WebUIUpdateStatus
)
fun updateWebUI(input: WebUIUpdateInput): CompletableFuture<WebUIUpdatePayload> {
return future {
withTimeout(30.seconds) {
if (WebInterfaceManager.status.value.state === DOWNLOADING) {
return@withTimeout WebUIUpdatePayload(input.clientMutationId, WebInterfaceManager.status.value)
}
val (version, updateAvailable) = WebInterfaceManager.isUpdateAvailable()
if (!updateAvailable) {
return@withTimeout WebUIUpdatePayload(
input.clientMutationId,
WebUIUpdateStatus(
info = WebUIUpdateInfo(
channel = serverConfig.webUIChannel,
tag = version,
updateAvailable
),
state = STOPPED,
progress = 0
)
)
}
try {
WebInterfaceManager.startDownloadInScope(version)
} catch (e: Exception) {
// ignore since we use the status anyway
}
WebUIUpdatePayload(
input.clientMutationId,
updateStatus = WebInterfaceManager.status.first { it.state == DOWNLOADING }
)
}
}
}
}

View File

@@ -1,8 +1,12 @@
package suwayomi.tachidesk.graphql.queries
import suwayomi.tachidesk.global.impl.AppUpdate
import suwayomi.tachidesk.graphql.types.WebUIUpdateInfo
import suwayomi.tachidesk.graphql.types.WebUIUpdateStatus
import suwayomi.tachidesk.server.BuildConfig
import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.serverConfig
import suwayomi.tachidesk.server.util.WebInterfaceManager
import java.util.concurrent.CompletableFuture
class InfoQuery {
@@ -28,17 +32,17 @@ class InfoQuery {
)
}
data class CheckForUpdatesPayload(
data class CheckForServerUpdatesPayload(
/** [channel] mirrors [suwayomi.tachidesk.server.BuildConfig.BUILD_TYPE] */
val channel: String,
val tag: String,
val url: String
)
fun checkForUpdates(): CompletableFuture<List<CheckForUpdatesPayload>> {
fun checkForServerUpdates(): CompletableFuture<List<CheckForServerUpdatesPayload>> {
return future {
AppUpdate.checkUpdate().map {
CheckForUpdatesPayload(
CheckForServerUpdatesPayload(
channel = it.channel,
tag = it.tag,
url = it.url
@@ -46,4 +50,19 @@ class InfoQuery {
}
}
}
fun checkForWebUIUpdate(): CompletableFuture<WebUIUpdateInfo> {
return future {
val (version, updateAvailable) = WebInterfaceManager.isUpdateAvailable()
WebUIUpdateInfo(
channel = serverConfig.webUIChannel,
tag = version,
updateAvailable
)
}
}
fun getWebUIUpdateStatus(): WebUIUpdateStatus {
return WebInterfaceManager.status.value
}
}

View File

@@ -18,6 +18,7 @@ import suwayomi.tachidesk.graphql.mutations.CategoryMutation
import suwayomi.tachidesk.graphql.mutations.ChapterMutation
import suwayomi.tachidesk.graphql.mutations.DownloadMutation
import suwayomi.tachidesk.graphql.mutations.ExtensionMutation
import suwayomi.tachidesk.graphql.mutations.InfoMutation
import suwayomi.tachidesk.graphql.mutations.MangaMutation
import suwayomi.tachidesk.graphql.mutations.MetaMutation
import suwayomi.tachidesk.graphql.mutations.SourceMutation
@@ -37,6 +38,7 @@ import suwayomi.tachidesk.graphql.server.primitives.GraphQLCursor
import suwayomi.tachidesk.graphql.server.primitives.GraphQLLongAsString
import suwayomi.tachidesk.graphql.server.primitives.GraphQLUpload
import suwayomi.tachidesk.graphql.subscriptions.DownloadSubscription
import suwayomi.tachidesk.graphql.subscriptions.InfoSubscription
import suwayomi.tachidesk.graphql.subscriptions.UpdateSubscription
import kotlin.reflect.KClass
import kotlin.reflect.KType
@@ -74,6 +76,7 @@ val schema = toSchema(
TopLevelObject(ChapterMutation()),
TopLevelObject(DownloadMutation()),
TopLevelObject(ExtensionMutation()),
TopLevelObject(InfoMutation()),
TopLevelObject(MangaMutation()),
TopLevelObject(MetaMutation()),
TopLevelObject(SourceMutation()),
@@ -81,6 +84,7 @@ val schema = toSchema(
),
subscriptions = listOf(
TopLevelObject(DownloadSubscription()),
TopLevelObject(InfoSubscription()),
TopLevelObject(UpdateSubscription())
)
)

View File

@@ -0,0 +1,11 @@
package suwayomi.tachidesk.graphql.subscriptions
import kotlinx.coroutines.flow.Flow
import suwayomi.tachidesk.graphql.types.WebUIUpdateStatus
import suwayomi.tachidesk.server.util.WebInterfaceManager
class InfoSubscription {
fun webUIUpdateStatusChange(): Flow<WebUIUpdateStatus> {
return WebInterfaceManager.status
}
}

View File

@@ -0,0 +1,20 @@
package suwayomi.tachidesk.graphql.types
data class WebUIUpdateInfo(
val channel: String,
val tag: String,
val updateAvailable: Boolean
)
enum class UpdateState {
STOPPED,
DOWNLOADING,
FINISHED,
ERROR
}
data class WebUIUpdateStatus(
val info: WebUIUpdateInfo,
val state: UpdateState,
val progress: Int
)

View File

@@ -19,6 +19,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.future.future
import kotlinx.coroutines.runBlocking
import mu.KotlinLogging
import org.kodein.di.DI
import org.kodein.di.conf.global
@@ -47,7 +48,9 @@ object JavalinSetup {
fun javalinSetup() {
val app = Javalin.create { config ->
if (serverConfig.webUIEnabled) {
WebInterfaceManager.setupWebUI()
runBlocking {
WebInterfaceManager.setupWebUI()
}
logger.info { "Serving web static files for ${serverConfig.webUIFlavor}" }
config.addStaticFiles(applicationDirs.webUIRoot, Location.EXTERNAL)

View File

@@ -7,21 +7,44 @@ package suwayomi.tachidesk.server.util
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.await
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.BufferOverflow.DROP_OLDEST
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import mu.KLogger
import mu.KotlinLogging
import net.lingala.zip4j.ZipFile
import org.json.JSONArray
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
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.WebUIUpdateInfo
import suwayomi.tachidesk.graphql.types.WebUIUpdateStatus
import suwayomi.tachidesk.server.ApplicationDirs
import suwayomi.tachidesk.server.BuildConfig
import suwayomi.tachidesk.server.serverConfig
import suwayomi.tachidesk.util.HAScheduler
import uy.kohesive.injekt.injectLazy
import java.io.File
import java.io.InputStream
import java.net.HttpURLConnection
@@ -31,6 +54,7 @@ import java.security.MessageDigest
import java.util.Date
import java.util.prefs.Preferences
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.seconds
private val applicationDirs by DI.global.instance<ApplicationDirs>()
private val tmpDir = System.getProperty("java.io.tmpdir")
@@ -51,7 +75,12 @@ enum class WebUIChannel {
}
}
enum class WebUI(val repoUrl: String, val versionMappingUrl: String, val latestReleaseInfoUrl: String, val baseFileName: String) {
enum class WebUI(
val repoUrl: String,
val versionMappingUrl: String,
val latestReleaseInfoUrl: String,
val baseFileName: String
) {
WEBUI(
"https://github.com/Suwayomi/Tachidesk-WebUI-preview",
"https://raw.githubusercontent.com/Suwayomi/Tachidesk-WebUI/master/versionToServerVersionMapping.json",
@@ -64,12 +93,34 @@ const val DEFAULT_WEB_UI = "WebUI"
object WebInterfaceManager {
private val logger = KotlinLogging.logger {}
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
private const val webUIPreviewVersion = "PREVIEW"
private const val lastWebUIUpdateCheckKey = "lastWebUIUpdateCheckKey"
private val preferences = Preferences.userNodeForPackage(WebInterfaceManager::class.java)
private val preferences = Preferences.userNodeForPackage(WebInterfaceManager::class.java)
private var currentUpdateTaskId: String = ""
private val json: Json by injectLazy()
private val network: NetworkHelper by injectLazy()
private val notifyFlow =
MutableSharedFlow<WebUIUpdateStatus>(extraBufferCapacity = 1, onBufferOverflow = DROP_OLDEST)
val status = notifyFlow.sample(1.seconds)
.stateIn(
scope,
SharingStarted.Eagerly,
WebUIUpdateStatus(
info = WebUIUpdateInfo(
channel = serverConfig.webUIChannel,
tag = "",
updateAvailable = false
),
state = STOPPED,
progress = 0
)
)
init {
scheduleWebUIUpdateCheck()
}
@@ -90,25 +141,36 @@ object WebInterfaceManager {
val lastAutomatedUpdate = preferences.getLong(lastWebUIUpdateCheckKey, System.currentTimeMillis())
val task = {
logger.debug { "Checking for webUI update (channel= ${serverConfig.webUIChannel}, interval= ${serverConfig.webUIUpdateCheckInterval}h, lastAutomatedUpdate= ${Date(lastAutomatedUpdate)})" }
checkForUpdate()
logger.debug {
"Checking for webUI update (channel= ${serverConfig.webUIChannel}, interval= ${serverConfig.webUIUpdateCheckInterval}h, lastAutomatedUpdate= ${
Date(
lastAutomatedUpdate
)
})"
}
runBlocking {
checkForUpdate()
}
}
val wasPreviousUpdateCheckTriggered = (System.currentTimeMillis() - lastAutomatedUpdate) < updateInterval.inWholeMilliseconds
val wasPreviousUpdateCheckTriggered =
(System.currentTimeMillis() - lastAutomatedUpdate) < updateInterval.inWholeMilliseconds
if (!wasPreviousUpdateCheckTriggered) {
task()
}
currentUpdateTaskId = HAScheduler.scheduleCron(task, "0 */${updateInterval.inWholeHours} * * *", "webUI-update-checker")
currentUpdateTaskId =
HAScheduler.scheduleCron(task, "0 */${updateInterval.inWholeHours} * * *", "webUI-update-checker")
}
fun setupWebUI() {
suspend fun setupWebUI() {
if (serverConfig.webUIFlavor == "Custom") {
return
}
if (doesLocalWebUIExist(applicationDirs.webUIRoot)) {
val currentVersion = getLocalVersion(applicationDirs.webUIRoot)
val currentVersion = getLocalVersion()
logger.info { "setupWebUI: found webUI files - flavor= ${serverConfig.webUIFlavor}, version= $currentVersion" }
@@ -124,7 +186,7 @@ object WebInterfaceManager {
// check if the bundled webUI version is a newer version than the current used version
// this could be the case in case no compatible webUI version is available and a newer server version was installed
val shouldUpdateToBundledVersion =
serverConfig.webUIFlavor == DEFAULT_WEB_UI && extractVersion(getLocalVersion(applicationDirs.webUIRoot)) < extractVersion(
serverConfig.webUIFlavor == DEFAULT_WEB_UI && extractVersion(getLocalVersion()) < extractVersion(
BuildConfig.WEBUI_TAG
)
if (shouldUpdateToBundledVersion) {
@@ -147,7 +209,7 @@ object WebInterfaceManager {
/**
* Tries to download the latest compatible version for the selected webUI and falls back to the default webUI in case of errors.
*/
private fun doInitialSetup() {
private suspend fun doInitialSetup() {
val isLocalWebUIValid = isLocalWebUIValid(applicationDirs.webUIRoot)
/**
@@ -155,7 +217,7 @@ object WebInterfaceManager {
*
* In case the download failed but the local webUI is valid the download is considered a success to prevent the fallback logic
*/
val doDownload: (getVersion: () -> String) -> Boolean = { getVersion ->
val doDownload: suspend (getVersion: suspend () -> String) -> Boolean = { getVersion ->
try {
downloadVersion(getVersion())
true
@@ -202,7 +264,8 @@ object WebInterfaceManager {
}
private fun extractBundledWebUI() {
val resourceWebUI: InputStream = BuildConfig::class.java.getResourceAsStream("/WebUI.zip") ?: throw BundledWebUIMissing()
val resourceWebUI: InputStream =
BuildConfig::class.java.getResourceAsStream("/WebUI.zip") ?: throw BundledWebUIMissing()
logger.info { "extractBundledWebUI: Using the bundled WebUI zip..." }
@@ -219,11 +282,11 @@ object WebInterfaceManager {
extractDownload(webUIZipPath, applicationDirs.webUIRoot)
}
private fun checkForUpdate() {
private suspend fun checkForUpdate() {
preferences.putLong(lastWebUIUpdateCheckKey, System.currentTimeMillis())
val localVersion = getLocalVersion(applicationDirs.webUIRoot)
val localVersion = getLocalVersion()
if (!isUpdateAvailable(localVersion)) {
if (!isUpdateAvailable(localVersion).second) {
logger.debug { "checkForUpdate(${serverConfig.webUIFlavor}, $localVersion): local version is the latest one" }
return
}
@@ -243,8 +306,12 @@ object WebInterfaceManager {
return "$downloadSpecificVersionBaseUrl/$version"
}
private fun getLocalVersion(path: String): String {
return File("$path/revision").readText().trim()
private fun getLocalVersion(path: String = applicationDirs.webUIRoot): String {
return try {
File("$path/revision").readText().trim()
} catch (e: Exception) {
"r-1"
}
}
private fun doesLocalWebUIExist(path: String): Boolean {
@@ -253,7 +320,7 @@ object WebInterfaceManager {
return webUIRevisionFile.exists()
}
private fun isLocalWebUIValid(path: String): Boolean {
private suspend fun isLocalWebUIValid(path: String): Boolean {
if (!doesLocalWebUIExist(path)) {
return false
}
@@ -287,7 +354,12 @@ object WebInterfaceManager {
return digest.toHex()
}
private fun <T> executeWithRetry(log: KLogger, execute: () -> T, maxRetries: Int = 3, retryCount: Int = 0): T {
private suspend fun <T> executeWithRetry(
log: KLogger,
execute: suspend () -> T,
maxRetries: Int = 3,
retryCount: Int = 0
): T {
try {
return execute()
} catch (e: Exception) {
@@ -301,11 +373,10 @@ object WebInterfaceManager {
}
}
private fun fetchMD5SumFor(version: String): String {
private suspend fun fetchMD5SumFor(version: String): String {
return try {
executeWithRetry(KotlinLogging.logger("${logger.name} fetchMD5SumFor($version)"), {
val url = "${getDownloadUrlFor(version)}/md5sum"
URL(url).readText().trim()
network.client.newCall(GET("${getDownloadUrlFor(version)}/md5sum")).await().body.string().trim()
})
} catch (e: Exception) {
""
@@ -317,18 +388,26 @@ object WebInterfaceManager {
return versionString.substring(1).toInt()
}
private fun fetchPreviewVersion(): String {
private suspend fun fetchPreviewVersion(): String {
return executeWithRetry(KotlinLogging.logger("${logger.name} fetchPreviewVersion"), {
val releaseInfoJson = URL(WebUI.WEBUI.latestReleaseInfoUrl).readText()
Json.decodeFromString<JsonObject>(releaseInfoJson)["tag_name"]?.jsonPrimitive?.content ?: throw Exception("Failed to get the preview version tag")
val releaseInfoJson = network.client.newCall(GET(WebUI.WEBUI.latestReleaseInfoUrl)).await().body.string()
Json.decodeFromString<JsonObject>(releaseInfoJson)["tag_name"]?.jsonPrimitive?.content
?: throw Exception("Failed to get the preview version tag")
})
}
private fun fetchServerMappingFile(): JSONArray {
return executeWithRetry(KotlinLogging.logger("$logger fetchServerMappingFile"), { JSONArray(URL(WebUI.WEBUI.versionMappingUrl).readText()) })
private suspend fun fetchServerMappingFile(): JsonArray {
return executeWithRetry(
KotlinLogging.logger("$logger fetchServerMappingFile"),
{
json.parseToJsonElement(
network.client.newCall(GET(WebUI.WEBUI.versionMappingUrl)).await().body.string()
).jsonArray
}
)
}
private fun getLatestCompatibleVersion(): String {
private suspend fun getLatestCompatibleVersion(): String {
if (WebUIChannel.doesConfigChannelEqual(WebUIChannel.BUNDLED)) {
logger.debug { "getLatestCompatibleVersion: Channel is \"${WebUIChannel.BUNDLED}\", do not check for update" }
return BuildConfig.WEBUI_TAG
@@ -339,13 +418,14 @@ object WebInterfaceManager {
logger.debug { "getLatestCompatibleVersion: webUIChannel= ${serverConfig.webUIChannel}, currentServerVersion= ${BuildConfig.REVISION}, mappingFile= $webUIToServerVersionMappings" }
for (i in 0 until webUIToServerVersionMappings.length()) {
val webUIToServerVersionEntry = webUIToServerVersionMappings.getJSONObject(i)
var webUIVersion = webUIToServerVersionEntry.getString("uiVersion")
val minServerVersionString = webUIToServerVersionEntry.getString("serverVersion")
for (i in 0 until webUIToServerVersionMappings.size) {
val webUIToServerVersionEntry = webUIToServerVersionMappings[i].jsonObject
var webUIVersion = webUIToServerVersionEntry["uiVersion"].toString()
val minServerVersionString = webUIToServerVersionEntry["serverVersion"]?.jsonPrimitive?.content ?: throw Exception("Invalid mappingFile")
val minServerVersionNumber = extractVersion(minServerVersionString)
val ignorePreviewVersion = !WebUIChannel.doesConfigChannelEqual(WebUIChannel.PREVIEW) && webUIVersion == webUIPreviewVersion
val ignorePreviewVersion =
!WebUIChannel.doesConfigChannelEqual(WebUIChannel.PREVIEW) && webUIVersion == webUIPreviewVersion
if (ignorePreviewVersion) {
continue
} else {
@@ -361,24 +441,68 @@ object WebInterfaceManager {
throw Exception("No compatible webUI version found")
}
fun downloadVersion(version: String) {
val webUIZip = "${WebUI.WEBUI.baseFileName}-$version.zip"
val webUIZipPath = "$tmpDir/$webUIZip"
val webUIZipURL = "${getDownloadUrlFor(version)}/$webUIZip"
val log = KotlinLogging.logger("${logger.name} downloadVersion(version= $version, flavor= ${serverConfig.webUIFlavor})")
log.info { "Downloading WebUI zip from the Internet..." }
executeWithRetry(log, { downloadVersionZipFile(webUIZipURL, webUIZipPath) })
File(applicationDirs.webUIRoot).deleteRecursively()
// extract webUI zip
log.info { "Extracting WebUI zip..." }
extractDownload(webUIZipPath, applicationDirs.webUIRoot)
log.info { "Extracting WebUI zip Done." }
private fun emitStatus(version: String, state: UpdateState, progress: Int) {
scope.launch {
notifyFlow.emit(
WebUIUpdateStatus(
info = WebUIUpdateInfo(
channel = serverConfig.webUIChannel,
tag = version,
updateAvailable = true
),
state,
progress
)
)
}
}
private fun downloadVersionZipFile(url: String, filePath: String) {
fun startDownloadInScope(version: String) {
scope.launch {
downloadVersion(version)
}
}
suspend fun downloadVersion(version: String) {
emitStatus(version, DOWNLOADING, 0)
try {
val webUIZip = "${WebUI.WEBUI.baseFileName}-$version.zip"
val webUIZipPath = "$tmpDir/$webUIZip"
val webUIZipURL = "${getDownloadUrlFor(version)}/$webUIZip"
val log =
KotlinLogging.logger("${logger.name} downloadVersion(version= $version, flavor= ${serverConfig.webUIFlavor})")
log.info { "Downloading WebUI zip from the Internet..." }
executeWithRetry(log, {
downloadVersionZipFile(webUIZipURL, webUIZipPath) { progress ->
emitStatus(
version,
DOWNLOADING,
progress
)
}
})
File(applicationDirs.webUIRoot).deleteRecursively()
// extract webUI zip
log.info { "Extracting WebUI zip..." }
extractDownload(webUIZipPath, applicationDirs.webUIRoot)
log.info { "Extracting WebUI zip Done." }
emitStatus(version, FINISHED, 100)
} catch (e: Exception) {
emitStatus(version, ERROR, 0)
throw e
}
}
private suspend fun downloadVersionZipFile(
url: String,
filePath: String,
updateProgress: (progress: Int) -> Unit
) {
val zipFile = File(filePath)
zipFile.delete()
@@ -402,11 +526,13 @@ object WebInterfaceManager {
}
totalCount += count
val percentage =
(totalCount.toFloat() / contentLength * 100).toInt().toString().padStart(2, '0')
print("\b\b$percentage")
val percentage = (totalCount.toFloat() / contentLength * 100).toInt()
val percentageStr = percentage.toString().padStart(2, '0')
print("\b\b$percentageStr")
webUIZipFileOut.write(data, 0, count)
updateProgress(percentage)
}
println()
logger.info { "downloadVersionZipFile: Downloading WebUI Done." }
@@ -418,7 +544,7 @@ object WebInterfaceManager {
}
}
private fun isDownloadValid(zipFileName: String, zipFilePath: String): Boolean {
private suspend fun isDownloadValid(zipFileName: String, zipFilePath: String): Boolean {
val tempUnzippedWebUIFolderPath = zipFileName.replace(".zip", "")
extractDownload(zipFilePath, tempUnzippedWebUIFolderPath)
@@ -435,13 +561,15 @@ object WebInterfaceManager {
ZipFile(zipFilePath).use { it.extractAll(targetPath) }
}
fun isUpdateAvailable(currentVersion: String): Boolean {
suspend fun isUpdateAvailable(currentVersion: String = getLocalVersion()): Pair<String, Boolean> {
return try {
val latestCompatibleVersion = getLatestCompatibleVersion()
latestCompatibleVersion != currentVersion
val isUpdateAvailable = latestCompatibleVersion != currentVersion
Pair(latestCompatibleVersion, isUpdateAvailable)
} catch (e: Exception) {
logger.warn(e) { "isUpdateAvailable: check failed due to" }
false
Pair("", false)
}
}
}