diff --git a/data/src/desktopMain/kotlin/ca/gosyer/jui/data/server/ServerService.kt b/data/src/desktopMain/kotlin/ca/gosyer/jui/data/server/ServerService.kt index 2c1280d9..9d20604d 100644 --- a/data/src/desktopMain/kotlin/ca/gosyer/jui/data/server/ServerService.kt +++ b/data/src/desktopMain/kotlin/ca/gosyer/jui/data/server/ServerService.kt @@ -11,15 +11,17 @@ import ca.gosyer.jui.core.io.userDataDir import ca.gosyer.jui.core.lang.withIOContext import ca.gosyer.jui.data.build.BuildKonfig import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.mapLatest -import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import me.tatarka.inject.annotations.Inject import okio.FileSystem import okio.Path @@ -41,7 +43,8 @@ import kotlin.io.path.isExecutable class ServerService @Inject constructor( private val serverHostPreferences: ServerHostPreferences ) { - private val restartServerFlow = MutableSharedFlow() + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + private val host = serverHostPreferences.host().stateIn(GlobalScope) private val _initialized = MutableStateFlow( if (host.value) { @@ -57,10 +60,6 @@ class ServerService @Inject constructor( _initialized.value = ServerResult.UNUSED } - fun restartServer() { - GlobalScope.launch { restartServerFlow.emit(Unit) } - } - @Throws(IOException::class) private suspend fun copyJar(jarFile: Path) { javaClass.getResourceAsStream("/Tachidesk.jar")?.source() @@ -97,6 +96,91 @@ class ServerService @Inject constructor( .firstOrNull() } + private suspend fun runService() { + process?.destroy() + process?.waitFor() + _initialized.value = if (host.value) { + ServerResult.STARTING + } else { + ServerResult.UNUSED + return + } + val handler = CoroutineExceptionHandler { _, throwable -> + log.error(throwable) { "Error launching Tachidesk.jar" } + if (_initialized.value == ServerResult.STARTING || _initialized.value == ServerResult.STARTED) { + _initialized.value = ServerResult.FAILED + } + } + withContext(handler) { + val jarFile = userDataDir / "Tachidesk.jar" + if (!FileSystem.SYSTEM.exists(jarFile)) { + log.info { "Copying server to resources" } + withIOContext { copyJar(jarFile) } + } else { + try { + val jarVersion = withIOContext { + JarInputStream(FileSystem.SYSTEM.source(jarFile).buffer().inputStream()).use { jar -> + jar.manifest?.mainAttributes?.getValue(Attributes.Name.IMPLEMENTATION_VERSION)?.toIntOrNull() + } + } + + if (jarVersion != BuildKonfig.SERVER_CODE) { + log.info { "Updating server file from resources" } + withIOContext { copyJar(jarFile) } + } + } catch (e: IOException) { + log.error(e) { + "Error accessing server jar, cannot update server, ${BuildKonfig.NAME} may not work properly" + } + } + } + + val javaPath = getRuntimeJava() ?: getPossibleJava() ?: "java" + log.info { "Starting server with $javaPath" } + val properties = serverHostPreferences.properties() + log.info { "Using server properties:\n" + properties.joinToString(separator = "\n") } + + withIOContext { + val reader: Reader + process = ProcessBuilder(javaPath, *properties, "-jar", jarFile.toString()) + .redirectErrorStream(true) + .start() + .also { + reader = it.inputStream.reader() + } + log.info { "Server started successfully" } + val log = logging("Server") + reader.forEachLine { + if (_initialized.value == ServerResult.STARTING) { + when { + it.contains("Javalin started") -> + _initialized.value = ServerResult.STARTED + it.contains("Javalin has stopped") -> + _initialized.value = ServerResult.FAILED + } + } + log.info { it } + } + if (_initialized.value == ServerResult.STARTING) { + _initialized.value = ServerResult.FAILED + } + log.info { "Server closed" } + val exitVal = process?.waitFor() + log.info { "Process exitValue: $exitVal" } + process = null + } + } + } + + fun startServer() { + scope.coroutineContext.cancelChildren() + host + .mapLatest { + runService() + } + .launchIn(scope) + } + init { Runtime.getRuntime().addShutdownHook( thread(start = false) { @@ -104,83 +188,6 @@ class ServerService @Inject constructor( process = null } ) - - merge(restartServerFlow, host).mapLatest { - process?.destroy() - process?.waitFor() - _initialized.value = if (host.value) { - ServerResult.STARTING - } else { - ServerResult.UNUSED - return@mapLatest - } - val handler = CoroutineExceptionHandler { _, throwable -> - log.error(throwable) { "Error launching Tachidesk.jar" } - if (_initialized.value == ServerResult.STARTING || _initialized.value == ServerResult.STARTED) { - _initialized.value = ServerResult.FAILED - } - } - GlobalScope.launch(handler) { - val jarFile = userDataDir / "Tachidesk.jar" - if (!FileSystem.SYSTEM.exists(jarFile)) { - log.info { "Copying server to resources" } - withIOContext { copyJar(jarFile) } - } else { - try { - val jarVersion = withIOContext { - JarInputStream(FileSystem.SYSTEM.source(jarFile).buffer().inputStream()).use { jar -> - jar.manifest?.mainAttributes?.getValue(Attributes.Name.IMPLEMENTATION_VERSION)?.toIntOrNull() - } - } - - if (jarVersion != BuildKonfig.SERVER_CODE) { - log.info { "Updating server file from resources" } - withIOContext { copyJar(jarFile) } - } - } catch (e: IOException) { - log.error(e) { - "Error accessing server jar, cannot update server, ${BuildKonfig.NAME} may not work properly" - } - } - } - - val javaPath = getRuntimeJava() ?: getPossibleJava() ?: "java" - log.info { "Starting server with $javaPath" } - val properties = serverHostPreferences.properties() - log.info { "Using server properties:\n" + properties.joinToString(separator = "\n") } - - withIOContext { - val reader: Reader - process = ProcessBuilder(javaPath, *properties, "-jar", jarFile.toString()) - .redirectErrorStream(true) - .start() - .also { - reader = it.inputStream.reader() - } - log.info { "Server started successfully" } - val log = logging("Server") - reader.useLines { lines -> - lines.forEach { - if (_initialized.value == ServerResult.STARTING) { - if (it.contains("Javalin started")) { - _initialized.value = ServerResult.STARTED - } else if (it.contains("Javalin has stopped")) { - _initialized.value = ServerResult.FAILED - } - } - log.info { it } - } - } - if (_initialized.value == ServerResult.STARTING) { - _initialized.value = ServerResult.FAILED - } - log.info { "Server closed" } - val exitVal = process?.waitFor() - log.info { "Process exitValue: $exitVal" } - process = null - } - } - }.launchIn(GlobalScope) } enum class ServerResult { diff --git a/desktop/src/main/kotlin/ca/gosyer/jui/desktop/main.kt b/desktop/src/main/kotlin/ca/gosyer/jui/desktop/main.kt index ecf8f8b7..b42e9b0e 100644 --- a/desktop/src/main/kotlin/ca/gosyer/jui/desktop/main.kt +++ b/desktop/src/main/kotlin/ca/gosyer/jui/desktop/main.kt @@ -18,7 +18,6 @@ import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.type -import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window @@ -41,6 +40,7 @@ import ca.gosyer.jui.ui.util.compose.WindowGet import ca.gosyer.jui.uicore.components.LoadingScreen import ca.gosyer.jui.uicore.prefs.asStateIn import ca.gosyer.jui.uicore.resources.stringResource +import ca.gosyer.jui.uicore.resources.toPainter import com.github.weisj.darklaf.LafManager import com.github.weisj.darklaf.theme.DarculaTheme import com.github.weisj.darklaf.theme.IntelliJTheme @@ -51,7 +51,9 @@ import com.vanpra.composematerialdialogs.title import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.jetbrains.skiko.SystemTheme import org.jetbrains.skiko.currentSystemTheme import java.util.Locale @@ -70,9 +72,17 @@ suspend fun main() { val uiComponent = appComponent.uiComponent dataComponent.migrations.runMigrations() appComponent.appMigrations.runMigrations() - dataComponent.downloadService.init() - // dataComponent.libraryUpdateService.init() + val serverService = dataComponent.serverService + serverService.startServer() + serverService.initialized + .filter { it == ServerResult.STARTED || it == ServerResult.UNUSED } + .onEach { + dataComponent.downloadService.init() + // dataComponent.libraryUpdateService.init() + } + .launchIn(GlobalScope) + val uiPreferences = dataComponent.uiPreferences val uiHooks = uiComponent.getHooks() @@ -133,7 +143,7 @@ suspend fun main() { placement = placement ) - val icon = painterResource("icon.png") + val icon = MR.images.icon.toPainter() Tray(icon) diff --git a/presentation/src/desktopMain/kotlin/ca/gosyer/jui/ui/settings/DesktopSettingsServerScreen.kt b/presentation/src/desktopMain/kotlin/ca/gosyer/jui/ui/settings/DesktopSettingsServerScreen.kt index e75b3b09..59722157 100644 --- a/presentation/src/desktopMain/kotlin/ca/gosyer/jui/ui/settings/DesktopSettingsServerScreen.kt +++ b/presentation/src/desktopMain/kotlin/ca/gosyer/jui/ui/settings/DesktopSettingsServerScreen.kt @@ -105,7 +105,7 @@ actual class SettingsServerHostViewModel @Inject constructor( fun restartServer() { if (serverSettingChanged.value) { - serverService.restartServer() + serverService.startServer() } }