Handle server properly with downloader

This commit is contained in:
Syer10
2022-04-01 23:51:57 -04:00
parent 79ff944bad
commit 7649665f67
3 changed files with 107 additions and 90 deletions

View File

@@ -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<Unit>()
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 {

View File

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

View File

@@ -105,7 +105,7 @@ actual class SettingsServerHostViewModel @Inject constructor(
fun restartServer() {
if (serverSettingChanged.value) {
serverService.restartServer()
serverService.startServer()
}
}