From 02802fab97d091aeab8232c6e3f073434e675f39 Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Wed, 19 May 2021 16:36:17 +0430 Subject: [PATCH] Application mutex --- server/build.gradle.kts | 8 ++- .../ir/armor/tachidesk/server/JavalinSetup.kt | 2 +- .../ir/armor/tachidesk/server/ServerSetup.kt | 7 +- .../{internal => impl_internal}/About.kt | 2 +- .../ir/armor/tachidesk/server/util/AppExit.kt | 13 ++++ .../armor/tachidesk/server/util/AppMutex.kt | 71 +++++++++++++++++++ .../armor/tachidesk/server/util/SystemTray.kt | 5 +- 7 files changed, 99 insertions(+), 9 deletions(-) rename server/src/main/kotlin/ir/armor/tachidesk/server/{internal => impl_internal}/About.kt (92%) create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/server/util/AppExit.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/server/util/AppMutex.kt diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 661bd93..7124b93 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -12,7 +12,9 @@ plugins { } repositories { - mavenCentral() + maven { + url = uri("https://repo1.maven.org/maven2/") + } maven { url = uri("https://jitpack.io") } @@ -53,7 +55,9 @@ dependencies { // api implementation("io.javalin:javalin:3.13.6") - implementation("com.fasterxml.jackson.core:jackson-databind:2.12.3") + // jackson version is tied to javalin, ref: `io.javalin.core.util.OptionalDependency` + implementation("com.fasterxml.jackson.core:jackson-databind:2.10.3") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.10.3") // Exposed ORM val exposedVersion = "0.31.1" diff --git a/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt b/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt index b9344eb..344ebc3 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt @@ -33,7 +33,7 @@ import ir.armor.tachidesk.impl.Source.getSourceList import ir.armor.tachidesk.impl.backup.BackupFlags import ir.armor.tachidesk.impl.backup.legacy.LegacyBackupExport.createLegacyBackup import ir.armor.tachidesk.impl.backup.legacy.LegacyBackupImport.restoreLegacyBackup -import ir.armor.tachidesk.server.internal.About.getAbout +import ir.armor.tachidesk.server.impl_internal.About.getAbout import ir.armor.tachidesk.server.util.openInBrowser import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers diff --git a/server/src/main/kotlin/ir/armor/tachidesk/server/ServerSetup.kt b/server/src/main/kotlin/ir/armor/tachidesk/server/ServerSetup.kt index ec48e65..09d5939 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/server/ServerSetup.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/server/ServerSetup.kt @@ -10,6 +10,7 @@ package ir.armor.tachidesk.server import ch.qos.logback.classic.Level import eu.kanade.tachiyomi.App import ir.armor.tachidesk.model.database.databaseUp +import ir.armor.tachidesk.server.util.AppMutex.handleAppMutex import ir.armor.tachidesk.server.util.systemTray import mu.KotlinLogging import org.kodein.di.DI @@ -36,7 +37,7 @@ class ApplicationDirs( val serverConfig: ServerConfig by lazy { GlobalConfigManager.module() } -val systemTray by lazy { systemTray() } +val systemTrayInstance by lazy { systemTray() } val androidCompat by lazy { AndroidCompat() } @@ -66,6 +67,8 @@ fun applicationSetup() { ServerConfig.register(GlobalConfigManager.config) ) + handleAppMutex() + // Load config API DI.global.addImport(ConfigKodeinModule().create()) // Load Android compatibility dependencies @@ -97,7 +100,7 @@ fun applicationSetup() { // create system tray if (serverConfig.systemTrayEnabled) { try { - systemTray + systemTrayInstance } catch (e: Throwable) { // cover both java.lang.Exception and java.lang.Error e.printStackTrace() } diff --git a/server/src/main/kotlin/ir/armor/tachidesk/server/internal/About.kt b/server/src/main/kotlin/ir/armor/tachidesk/server/impl_internal/About.kt similarity index 92% rename from server/src/main/kotlin/ir/armor/tachidesk/server/internal/About.kt rename to server/src/main/kotlin/ir/armor/tachidesk/server/impl_internal/About.kt index a99cc2a..b991aae 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/server/internal/About.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/server/impl_internal/About.kt @@ -1,4 +1,4 @@ -package ir.armor.tachidesk.server.internal +package ir.armor.tachidesk.server.impl_internal /* * Copyright (C) Contributors to the Suwayomi project diff --git a/server/src/main/kotlin/ir/armor/tachidesk/server/util/AppExit.kt b/server/src/main/kotlin/ir/armor/tachidesk/server/util/AppExit.kt new file mode 100644 index 0000000..4493ac6 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/server/util/AppExit.kt @@ -0,0 +1,13 @@ +package ir.armor.tachidesk.server.util + +import kotlin.system.exitProcess + +enum class ExitCode(val code: Int) { + Success(0), + MutexCheckFailedTachideskRunning(1), + MutexCheckFailedAnotherAppRunning(2); +} + +fun shutdownApp(exitCode: ExitCode) { + exitProcess(exitCode.code) +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/server/util/AppMutex.kt b/server/src/main/kotlin/ir/armor/tachidesk/server/util/AppMutex.kt new file mode 100644 index 0000000..948f721 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/server/util/AppMutex.kt @@ -0,0 +1,71 @@ +package ir.armor.tachidesk.server.util + +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * 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 io.javalin.plugin.json.JavalinJackson +import ir.armor.tachidesk.server.impl_internal.AboutDataClass +import ir.armor.tachidesk.server.serverConfig +import ir.armor.tachidesk.server.util.AppMutex.AppMutexStat.Clear +import ir.armor.tachidesk.server.util.AppMutex.AppMutexStat.OtherApplicationRunning +import ir.armor.tachidesk.server.util.AppMutex.AppMutexStat.TachideskInstanceRunning +import mu.KotlinLogging +import okhttp3.OkHttpClient +import okhttp3.Request.Builder +import java.io.IOException +import java.util.concurrent.TimeUnit + +object AppMutex { + private val logger = KotlinLogging.logger {} + + private enum class AppMutexStat(val stat: Int) { + Clear(0), + TachideskInstanceRunning(1), + OtherApplicationRunning(2) + } + + private val appIP = if (serverConfig.ip == "0.0.0.0") "127.0.0.1" else serverConfig.ip + + private fun checkAppMutex(): AppMutexStat { + val client = OkHttpClient.Builder() + .connectTimeout(200, TimeUnit.MILLISECONDS) + .build() + + val request = Builder() + .url("http://$appIP:${serverConfig.port}/api/v1/about/") + .build() + + val response = try { + client.newCall(request).execute().use { response -> response.body!!.string() } + } catch (e: IOException) { + return AppMutexStat.Clear + } + + return try { + JavalinJackson.fromJson(response, AboutDataClass::class.java) + AppMutexStat.TachideskInstanceRunning + } catch (e: IOException) { + AppMutexStat.OtherApplicationRunning + } + } + + fun handleAppMutex() { + when (checkAppMutex()) { + Clear -> { + logger.info("Mutex status is clear, Resuming startup.") + } + TachideskInstanceRunning -> { + logger.info("Another instance of Tachidesk is running on $appIP:${serverConfig.port}, Aborting.") + shutdownApp(ExitCode.MutexCheckFailedTachideskRunning) + } + OtherApplicationRunning -> { + logger.error("A non Tachidesk application is running on $appIP:${serverConfig.port}, Aborting.") + shutdownApp(ExitCode.MutexCheckFailedAnotherAppRunning) + } + } + } +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/server/util/SystemTray.kt b/server/src/main/kotlin/ir/armor/tachidesk/server/util/SystemTray.kt index 3e00571..499c492 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/server/util/SystemTray.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/server/util/SystemTray.kt @@ -15,7 +15,7 @@ import dorkbox.util.Desktop import ir.armor.tachidesk.server.BuildConfig import ir.armor.tachidesk.server.ServerConfig import ir.armor.tachidesk.server.serverConfig -import kotlin.system.exitProcess +import ir.armor.tachidesk.server.util.ExitCode.Success fun openInBrowser() { try { @@ -53,8 +53,7 @@ fun systemTray(): SystemTray? { mainMenu.add( MenuItem("Quit") { - systemTray.shutdown() - exitProcess(0) + shutdownApp(Success) } )