mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-09 20:06:39 -04:00
Minecraft Logging
This commit is contained in:
parent
cbf2a4e7a8
commit
563ea993c6
@ -18,10 +18,13 @@
|
|||||||
package org.jackhuang.hmcl
|
package org.jackhuang.hmcl
|
||||||
|
|
||||||
import javafx.application.Application
|
import javafx.application.Application
|
||||||
|
import javafx.application.Platform
|
||||||
import javafx.stage.Stage
|
import javafx.stage.Stage
|
||||||
|
import javafx.stage.StageStyle
|
||||||
import org.jackhuang.hmcl.setting.Settings
|
import org.jackhuang.hmcl.setting.Settings
|
||||||
import org.jackhuang.hmcl.task.Scheduler
|
import org.jackhuang.hmcl.task.Scheduler
|
||||||
import org.jackhuang.hmcl.ui.Controllers
|
import org.jackhuang.hmcl.ui.Controllers
|
||||||
|
import org.jackhuang.hmcl.ui.runOnUiThread
|
||||||
import org.jackhuang.hmcl.util.DEFAULT_USER_AGENT
|
import org.jackhuang.hmcl.util.DEFAULT_USER_AGENT
|
||||||
import org.jackhuang.hmcl.util.LOG
|
import org.jackhuang.hmcl.util.LOG
|
||||||
import org.jackhuang.hmcl.util.OS
|
import org.jackhuang.hmcl.util.OS
|
||||||
@ -41,7 +44,10 @@ fun i18n(key: String): String {
|
|||||||
class Main : Application() {
|
class Main : Application() {
|
||||||
|
|
||||||
override fun start(stage: Stage) {
|
override fun start(stage: Stage) {
|
||||||
PRIMARY_STAGE = stage
|
// When launcher visibility is set to "hide and reopen" without [Platform.implicitExit] = false,
|
||||||
|
// Stage.show() cannot work again because JavaFX Toolkit have already shut down.
|
||||||
|
Platform.setImplicitExit(false)
|
||||||
|
|
||||||
Controllers.initialize(stage)
|
Controllers.initialize(stage)
|
||||||
|
|
||||||
stage.isResizable = false
|
stage.isResizable = false
|
||||||
@ -55,7 +61,6 @@ class Main : Application() {
|
|||||||
val NAME = "HMCL"
|
val NAME = "HMCL"
|
||||||
val TITLE = "$NAME $VERSION"
|
val TITLE = "$NAME $VERSION"
|
||||||
val APPDATA = getWorkingDirectory("hmcl")
|
val APPDATA = getWorkingDirectory("hmcl")
|
||||||
lateinit var PRIMARY_STAGE: Stage
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
@ -79,8 +84,9 @@ class Main : Application() {
|
|||||||
|
|
||||||
fun getMinecraftDirectory(): File = getWorkingDirectory("minecraft")
|
fun getMinecraftDirectory(): File = getWorkingDirectory("minecraft")
|
||||||
|
|
||||||
fun stop() {
|
fun stop() = runOnUiThread {
|
||||||
PRIMARY_STAGE.close()
|
Controllers.stage.close()
|
||||||
|
Platform.exit()
|
||||||
Scheduler.shutdown()
|
Scheduler.shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,19 +17,27 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.game
|
package org.jackhuang.hmcl.game
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.Main
|
||||||
|
import org.jackhuang.hmcl.auth.AuthInfo
|
||||||
import org.jackhuang.hmcl.auth.AuthenticationException
|
import org.jackhuang.hmcl.auth.AuthenticationException
|
||||||
import org.jackhuang.hmcl.launch.DefaultLauncher
|
import org.jackhuang.hmcl.launch.DefaultLauncher
|
||||||
|
import org.jackhuang.hmcl.launch.ProcessListener
|
||||||
import org.jackhuang.hmcl.mod.CurseForgeModpackCompletionTask
|
import org.jackhuang.hmcl.mod.CurseForgeModpackCompletionTask
|
||||||
|
import org.jackhuang.hmcl.setting.LauncherVisibility
|
||||||
import org.jackhuang.hmcl.setting.Settings
|
import org.jackhuang.hmcl.setting.Settings
|
||||||
import org.jackhuang.hmcl.task.*
|
import org.jackhuang.hmcl.task.*
|
||||||
import org.jackhuang.hmcl.ui.Controllers
|
import org.jackhuang.hmcl.ui.Controllers
|
||||||
import org.jackhuang.hmcl.ui.DialogController
|
import org.jackhuang.hmcl.ui.DialogController
|
||||||
import org.jackhuang.hmcl.ui.LaunchingStepsPane
|
import org.jackhuang.hmcl.ui.LaunchingStepsPane
|
||||||
import org.jackhuang.hmcl.ui.runOnUiThread
|
import org.jackhuang.hmcl.ui.runOnUiThread
|
||||||
|
import org.jackhuang.hmcl.util.JavaProcess
|
||||||
|
import org.jackhuang.hmcl.util.Log4jLevel
|
||||||
|
import java.util.concurrent.ConcurrentSkipListSet
|
||||||
|
|
||||||
|
|
||||||
object LauncherHelper {
|
object LauncherHelper {
|
||||||
val launchingStepsPane = LaunchingStepsPane()
|
private val launchingStepsPane = LaunchingStepsPane()
|
||||||
|
val PROCESS = ConcurrentSkipListSet<JavaProcess>()
|
||||||
|
|
||||||
fun launch() {
|
fun launch() {
|
||||||
val profile = Settings.selectedProfile
|
val profile = Settings.selectedProfile
|
||||||
@ -37,6 +45,7 @@ object LauncherHelper {
|
|||||||
val dependency = profile.dependency
|
val dependency = profile.dependency
|
||||||
val account = Settings.selectedAccount ?: throw IllegalStateException("No account here")
|
val account = Settings.selectedAccount ?: throw IllegalStateException("No account here")
|
||||||
val version = repository.getVersion(profile.selectedVersion)
|
val version = repository.getVersion(profile.selectedVersion)
|
||||||
|
val setting = profile.getVersionSetting(profile.selectedVersion)
|
||||||
var finished = 0
|
var finished = 0
|
||||||
|
|
||||||
Controllers.dialog(launchingStepsPane)
|
Controllers.dialog(launchingStepsPane)
|
||||||
@ -61,13 +70,17 @@ object LauncherHelper {
|
|||||||
it["launcher"] = HMCLGameLauncher(
|
it["launcher"] = HMCLGameLauncher(
|
||||||
repository = repository,
|
repository = repository,
|
||||||
versionId = profile.selectedVersion,
|
versionId = profile.selectedVersion,
|
||||||
options = profile.getVersionSetting(profile.selectedVersion).toLaunchOptions(profile.gameDir),
|
options = setting.toLaunchOptions(profile.gameDir),
|
||||||
|
listener = HMCLProcessListener(it["account"], setting.launcherVisibility),
|
||||||
account = it["account"]
|
account = it["account"]
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.then { it.get<DefaultLauncher>("launcher").launchAsync() }
|
.then { it.get<DefaultLauncher>("launcher").launchAsync() }
|
||||||
|
.then(task {
|
||||||
|
if (setting.launcherVisibility == LauncherVisibility.CLOSE)
|
||||||
|
Main.stop()
|
||||||
|
})
|
||||||
|
|
||||||
.then(task(Scheduler.JAVAFX) { emitStatus(LoadingState.DONE) })
|
|
||||||
.executor()
|
.executor()
|
||||||
.apply {
|
.apply {
|
||||||
taskListener = object : TaskListener {
|
taskListener = object : TaskListener {
|
||||||
@ -79,19 +92,87 @@ object LauncherHelper {
|
|||||||
override fun onTerminate() {
|
override fun onTerminate() {
|
||||||
runOnUiThread { Controllers.closeDialog() }
|
runOnUiThread { Controllers.closeDialog() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun end() {
|
|
||||||
runOnUiThread { Controllers.closeDialog() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun emitStatus(state: LoadingState) {
|
fun emitStatus(state: LoadingState) {
|
||||||
|
if (state == LoadingState.DONE) {
|
||||||
|
Controllers.closeDialog()
|
||||||
|
}
|
||||||
|
|
||||||
launchingStepsPane.lblCurrentState.text = state.toString()
|
launchingStepsPane.lblCurrentState.text = state.toString()
|
||||||
launchingStepsPane.lblSteps.text = "${state.ordinal + 1} / ${LoadingState.values().size}"
|
launchingStepsPane.lblSteps.text = "${state.ordinal + 1} / ${LoadingState.values().size}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The managed process listener.
|
||||||
|
* Guarantee that one [JavaProcess], one [HMCLProcessListener].
|
||||||
|
* Because every time we launched a game, we generates a new [HMCLProcessListener]
|
||||||
|
*/
|
||||||
|
class HMCLProcessListener(authInfo: AuthInfo?, private val launcherVisibility: LauncherVisibility) : ProcessListener {
|
||||||
|
val forbiddenTokens: List<Pair<String, String>> = if (authInfo == null) emptyList() else
|
||||||
|
listOf(
|
||||||
|
authInfo.authToken to "<access token>",
|
||||||
|
authInfo.userId to "<uuid>",
|
||||||
|
authInfo.username to "<player>"
|
||||||
|
)
|
||||||
|
private lateinit var process: JavaProcess
|
||||||
|
private var lwjgl = false
|
||||||
|
override fun setProcess(process: JavaProcess) {
|
||||||
|
this.process = process
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLog(log: String, level: Log4jLevel) {
|
||||||
|
if (level.lessOrEqual(Log4jLevel.ERROR))
|
||||||
|
System.err.print(log)
|
||||||
|
else
|
||||||
|
System.out.print(log)
|
||||||
|
|
||||||
|
if (!lwjgl && log.contains("LWJGL Version: ")) {
|
||||||
|
lwjgl = true
|
||||||
|
when (launcherVisibility) {
|
||||||
|
LauncherVisibility.HIDE_AND_REOPEN -> {
|
||||||
|
runOnUiThread {
|
||||||
|
Controllers.stage.hide()
|
||||||
|
emitStatus(LoadingState.DONE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LauncherVisibility.CLOSE -> {
|
||||||
|
throw Error("Never come to here")
|
||||||
|
}
|
||||||
|
LauncherVisibility.KEEP -> {
|
||||||
|
// No operations here.
|
||||||
|
}
|
||||||
|
LauncherVisibility.HIDE -> {
|
||||||
|
runOnUiThread {
|
||||||
|
Controllers.stage.close()
|
||||||
|
emitStatus(LoadingState.DONE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onExit(exitCode: Int, exitType: ProcessListener.ExitType) {
|
||||||
|
checkExit(launcherVisibility)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkExit(launcherVisibility: LauncherVisibility) {
|
||||||
|
when (launcherVisibility) {
|
||||||
|
LauncherVisibility.HIDE_AND_REOPEN -> runOnUiThread { Controllers.stage.show() }
|
||||||
|
LauncherVisibility.KEEP -> {}
|
||||||
|
LauncherVisibility.CLOSE -> {}
|
||||||
|
LauncherVisibility.HIDE -> Main.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopManagedProcess() {
|
||||||
|
PROCESS.forEach(JavaProcess::stop)
|
||||||
|
}
|
||||||
|
|
||||||
enum class LoadingState {
|
enum class LoadingState {
|
||||||
DEPENDENCIES,
|
DEPENDENCIES,
|
||||||
MODS,
|
MODS,
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
package org.jackhuang.hmcl.ui
|
package org.jackhuang.hmcl.ui
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXDialog
|
import com.jfoenix.controls.JFXDialog
|
||||||
import javafx.fxml.FXMLLoader
|
|
||||||
import javafx.scene.Node
|
import javafx.scene.Node
|
||||||
import javafx.scene.Scene
|
import javafx.scene.Scene
|
||||||
import javafx.scene.layout.Region
|
import javafx.scene.layout.Region
|
||||||
|
@ -96,7 +96,7 @@ class YggdrasilAccount private constructor(override val username: String): Accou
|
|||||||
isOnline = true
|
isOnline = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logIn1(ROUTE_REFRESH, RefreshRequest(clientToken = clientToken, accessToken = accessToken!!, selectedProfile = selectedProfile), proxy)
|
logIn1(ROUTE_REFRESH, RefreshRequest(clientToken = clientToken, accessToken = accessToken!!), proxy)
|
||||||
} else if (isNotBlank(password)) {
|
} else if (isNotBlank(password)) {
|
||||||
logIn1(ROUTE_AUTHENTICATE, AuthenticationRequest(username, password!!, clientToken), proxy)
|
logIn1(ROUTE_AUTHENTICATE, AuthenticationRequest(username, password!!, clientToken), proxy)
|
||||||
} else
|
} else
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.event
|
package org.jackhuang.hmcl.event
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.task.Scheduler
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class EventBus {
|
class EventBus {
|
||||||
@ -25,7 +26,7 @@ class EventBus {
|
|||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T : EventObject> channel(classOfT: Class<T>): EventManager<T> {
|
fun <T : EventObject> channel(classOfT: Class<T>): EventManager<T> {
|
||||||
if (!events.containsKey(classOfT))
|
if (!events.containsKey(classOfT))
|
||||||
events.put(classOfT, EventManager<T>())
|
events.put(classOfT, EventManager<T>(Scheduler.COMPUTATION))
|
||||||
return events[classOfT] as EventManager<T>
|
return events[classOfT] as EventManager<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,10 +17,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.event
|
package org.jackhuang.hmcl.event
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.task.Scheduler
|
||||||
import org.jackhuang.hmcl.util.SimpleMultimap
|
import org.jackhuang.hmcl.util.SimpleMultimap
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class EventManager<T : EventObject> {
|
class EventManager<T : EventObject>(val scheduler: Scheduler = Scheduler.IMMEDIATE) {
|
||||||
private val handlers = SimpleMultimap<EventPriority, (T) -> Unit>({ EnumMap(EventPriority::class.java) }, ::HashSet)
|
private val handlers = SimpleMultimap<EventPriority, (T) -> Unit>({ EnumMap(EventPriority::class.java) }, ::HashSet)
|
||||||
private val handlers2 = SimpleMultimap<EventPriority, () -> Unit>({ EnumMap(EventPriority::class.java) }, ::HashSet)
|
private val handlers2 = SimpleMultimap<EventPriority, () -> Unit>({ EnumMap(EventPriority::class.java) }, ::HashSet)
|
||||||
|
|
||||||
@ -43,11 +44,13 @@ class EventManager<T : EventObject> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun fireEvent(event: T) {
|
fun fireEvent(event: T) {
|
||||||
for (priority in EventPriority.values()) {
|
scheduler.schedule {
|
||||||
for (handler in handlers[priority])
|
for (priority in EventPriority.values()) {
|
||||||
handler(event)
|
for (handler in handlers[priority])
|
||||||
for (handler in handlers2[priority])
|
handler(event)
|
||||||
handler()
|
for (handler in handlers2[priority])
|
||||||
|
handler()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.event
|
package org.jackhuang.hmcl.event
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.util.JavaProcess
|
||||||
import java.util.EventObject
|
import java.util.EventObject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,4 +49,34 @@ class RefreshedVersionsEvent(source: Any) : EventObject(source)
|
|||||||
* @param version the version id.
|
* @param version the version id.
|
||||||
* @author huangyuhui
|
* @author huangyuhui
|
||||||
*/
|
*/
|
||||||
class LoadedOneVersionEvent(source: Any, val version: String) : EventObject(source)
|
class LoadedOneVersionEvent(source: Any, val version: String) : EventObject(source)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event gets fired when a JavaProcess exited abnormally and the exit code is not zero.
|
||||||
|
* <br></br>
|
||||||
|
* This event is fired on the [org.jackhuang.hmcl.api.HMCLApi.EVENT_BUS]
|
||||||
|
* @param source [org.jackhuang.hmcl.util.sys.JavaProcessMonitor]
|
||||||
|
* @param JavaProcess The process that exited abnormally.
|
||||||
|
* @author huangyuhui
|
||||||
|
*/
|
||||||
|
class JavaProcessExitedAbnormallyEvent(source: Any, val value: JavaProcess) : EventObject(source)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event gets fired when minecraft process exited successfully and the exit code is 0.
|
||||||
|
* <br></br>
|
||||||
|
* This event is fired on the [org.jackhuang.hmcl.api.HMCLApi.EVENT_BUS]
|
||||||
|
* @param source [org.jackhuang.hmcl.util.sys.JavaProcessMonitor]
|
||||||
|
* @param JavaProcess minecraft process
|
||||||
|
* @author huangyuhui
|
||||||
|
*/
|
||||||
|
class JavaProcessStoppedEvent(source: Any, val value: JavaProcess) : EventObject(source)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event gets fired when we launch the JVM and it got crashed.
|
||||||
|
* <br></br>
|
||||||
|
* This event is fired on the [org.jackhuang.hmcl.api.HMCLApi.EVENT_BUS]
|
||||||
|
* @param source [org.jackhuang.hmcl.util.sys.JavaProcessMonitor]
|
||||||
|
* @param JavaProcess the crashed process.
|
||||||
|
* @author huangyuhui
|
||||||
|
*/
|
||||||
|
class JVMLaunchFailedEvent(source: Any, val value: JavaProcess) : EventObject(source)
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher.
|
||||||
|
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.launch
|
||||||
|
|
||||||
|
fun parseCrashReport(lines: List<String>) {
|
||||||
|
var errorText: String? = null
|
||||||
|
for (line in lines) {
|
||||||
|
errorText = line.substringAfterLast("#@!@#")
|
||||||
|
if (errorText.isNotBlank())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorText != null && errorText.isNotBlank()) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -25,6 +25,7 @@ import java.io.File
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
import kotlin.coroutines.experimental.EmptyCoroutineContext.plus
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param versionId The version to be launched.
|
* @param versionId The version to be launched.
|
||||||
@ -279,14 +280,19 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun startMonitors(javaProcess: JavaProcess) {
|
private fun startMonitors(javaProcess: JavaProcess) {
|
||||||
thread(name = "stdout-pump", isDaemon = true, block = StreamPump(javaProcess.process.inputStream)::run)
|
javaProcess.relatedThreads += thread(name = "stdout-pump", isDaemon = true, block = StreamPump(javaProcess.process.inputStream)::run)
|
||||||
thread(name = "stderr-pump", isDaemon = true, block = StreamPump(javaProcess.process.errorStream)::run)
|
javaProcess.relatedThreads += thread(name = "stderr-pump", isDaemon = true, block = StreamPump(javaProcess.process.errorStream)::run)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startMonitors(javaProcess: JavaProcess, processListener: ProcessListener, isDaemon: Boolean = true) {
|
private fun startMonitors(javaProcess: JavaProcess, processListener: ProcessListener, isDaemon: Boolean = true) {
|
||||||
thread(name = "stdout-pump", isDaemon = isDaemon, block = StreamPump(javaProcess.process.inputStream, processListener::onLog)::run)
|
processListener.setProcess(javaProcess)
|
||||||
thread(name = "stderr-pump", isDaemon = isDaemon, block = StreamPump(javaProcess.process.errorStream, processListener::onErrorLog)::run)
|
val logHandler = Log4jHandler { line, level -> processListener.onLog(line, level); javaProcess.stdOutLines += line }.apply { start() }
|
||||||
thread(name = "exit-waiter", isDaemon = isDaemon, block = ExitWaiter(javaProcess.process, processListener::onExit)::run)
|
javaProcess.relatedThreads += logHandler
|
||||||
|
val stdout = thread(name = "stdout-pump", isDaemon = isDaemon, block = StreamPump(javaProcess.process.inputStream, { logHandler.newLine(it) } )::run)
|
||||||
|
javaProcess.relatedThreads += stdout
|
||||||
|
val stderr = thread(name = "stderr-pump", isDaemon = isDaemon, block = StreamPump(javaProcess.process.errorStream, { processListener.onLog(it + OS.LINE_SEPARATOR, Log4jLevel.ERROR); javaProcess.stdErrLines += it })::run)
|
||||||
|
javaProcess.relatedThreads += stderr
|
||||||
|
javaProcess.relatedThreads += thread(name = "exit-waiter", isDaemon = isDaemon, block = ExitWaiter(javaProcess, listOf(stdout, stderr), { exitCode, exitType -> logHandler.onStopped(); processListener.onExit(exitCode, exitType) })::run)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher.
|
||||||
|
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.launch
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.task.Scheduler
|
||||||
|
import org.jackhuang.hmcl.util.Log4jLevel
|
||||||
|
import org.jackhuang.hmcl.util.OS
|
||||||
|
import org.xml.sax.Attributes
|
||||||
|
import org.xml.sax.InputSource
|
||||||
|
import org.xml.sax.helpers.DefaultHandler
|
||||||
|
import org.xml.sax.helpers.XMLReaderFactory
|
||||||
|
import java.io.PipedInputStream
|
||||||
|
import java.io.PipedOutputStream
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is to parse log4j classic XML layout logging, since only vanilla Minecraft will enable this layout.
|
||||||
|
*/
|
||||||
|
internal class Log4jHandler(val callback: (String, Log4jLevel) -> Unit) : Thread() {
|
||||||
|
val reader = XMLReaderFactory.createXMLReader().apply {
|
||||||
|
contentHandler = Log4jHandlerImpl()
|
||||||
|
}
|
||||||
|
private val outputStream = PipedOutputStream()
|
||||||
|
private val inputStream = PipedInputStream(outputStream)
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
name = "log4j-handler"
|
||||||
|
newLine("<output>")
|
||||||
|
reader.parse(InputSource(inputStream))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onStopped() {
|
||||||
|
Scheduler.NEW_THREAD.schedule {
|
||||||
|
newLine("</output>")?.get()
|
||||||
|
outputStream.close()
|
||||||
|
join()
|
||||||
|
}!!.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should be called in [ProcessListener.onErrorLog] and [ProcessListener.onLog] manually.
|
||||||
|
*/
|
||||||
|
fun newLine(content: String) =
|
||||||
|
Scheduler.COMPUTATION.schedule {
|
||||||
|
outputStream.write((content + OS.LINE_SEPARATOR).replace("log4j:Event", "log4j_Event").replace("log4j:Message", "log4j_Message").replace("log4j:Throwable", "log4j_Throwable").toByteArray())
|
||||||
|
outputStream.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class Log4jHandlerImpl : DefaultHandler() {
|
||||||
|
private val df = SimpleDateFormat("HH:mm:ss")
|
||||||
|
|
||||||
|
var date = ""
|
||||||
|
var thread = ""
|
||||||
|
var logger = ""
|
||||||
|
var message: StringBuilder? = null
|
||||||
|
var l: Log4jLevel? = null
|
||||||
|
var readingMessage = false
|
||||||
|
|
||||||
|
override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) {
|
||||||
|
when (localName) {
|
||||||
|
"log4j_Event" -> {
|
||||||
|
message = StringBuilder()
|
||||||
|
val d = Date(attributes.getValue("timestamp").toLong())
|
||||||
|
date = df.format(d)
|
||||||
|
try {
|
||||||
|
l = Log4jLevel.valueOf(attributes.getValue("level"))
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
l = Log4jLevel.INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
thread = attributes.getValue("thread")
|
||||||
|
logger = attributes.getValue("logger")
|
||||||
|
if ("STDERR" == logger)
|
||||||
|
l = Log4jLevel.ERROR
|
||||||
|
}
|
||||||
|
"log4j_Message" -> readingMessage = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun endElement(uri: String?, localName: String?, qName: String?) {
|
||||||
|
when (localName) {
|
||||||
|
"log4j_Event" -> callback("[" + date + "] [" + thread + "/" + l!!.name + "] [" + logger + "] " + message.toString(), l!!)
|
||||||
|
"log4j_Message" -> readingMessage = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun characters(ch: CharArray?, start: Int, length: Int) {
|
||||||
|
val line = String(ch!!, start, length)
|
||||||
|
if (line.trim { it <= ' ' }.isEmpty()) return
|
||||||
|
if (readingMessage)
|
||||||
|
message!!.append(line).append(OS.LINE_SEPARATOR)
|
||||||
|
else
|
||||||
|
callback(line, Log4jLevel.guessLevel(line) ?: Log4jLevel.INFO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,25 +17,33 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.launch
|
package org.jackhuang.hmcl.launch
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.util.JavaProcess
|
||||||
|
import org.jackhuang.hmcl.util.Log4jLevel
|
||||||
|
|
||||||
interface ProcessListener {
|
interface ProcessListener {
|
||||||
|
/**
|
||||||
|
* When a game launched, this method will be called to get the new process.
|
||||||
|
* You should not override this method when your ProcessListener is shared with all processes.
|
||||||
|
*/
|
||||||
|
fun setProcess(process: JavaProcess) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when receiving a log from stdout
|
* Called when receiving a log from stdout
|
||||||
*
|
*
|
||||||
* @param log the log
|
* @param log the log
|
||||||
*/
|
*/
|
||||||
fun onLog(log: String)
|
fun onLog(log: String, level: Log4jLevel)
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when receiving a log from stderr.
|
|
||||||
*
|
|
||||||
* @param log the log
|
|
||||||
*/
|
|
||||||
fun onErrorLog(log: String)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the game process stops.
|
* Called when the game process stops.
|
||||||
*
|
*
|
||||||
* @param exitCode the exit code
|
* @param exitCode the exit code
|
||||||
*/
|
*/
|
||||||
fun onExit(exitCode: Int)
|
fun onExit(exitCode: Int, exitType: ExitType)
|
||||||
|
|
||||||
|
enum class ExitType {
|
||||||
|
JVM_ERROR,
|
||||||
|
APPLICATION_ERROR,
|
||||||
|
NORMAL
|
||||||
|
}
|
||||||
}
|
}
|
@ -26,46 +26,42 @@ interface Scheduler {
|
|||||||
fun schedule(block: () -> Unit): Future<*>? = schedule(Callable { block() })
|
fun schedule(block: () -> Unit): Future<*>? = schedule(Callable { block() })
|
||||||
fun schedule(block: Callable<Unit>): Future<*>?
|
fun schedule(block: Callable<Unit>): Future<*>?
|
||||||
|
|
||||||
companion object Schedulers {
|
private class SchedulerImpl(val executor: (Runnable) -> Unit) : Scheduler {
|
||||||
val JAVAFX: Scheduler = SchedulerImpl(Platform::runLater)
|
override fun schedule(block: Callable<Unit>): Future<*>? {
|
||||||
val SWING: Scheduler = SchedulerImpl(SwingUtilities::invokeLater)
|
val latch = CountDownLatch(1)
|
||||||
private class SchedulerImpl(val executor: (Runnable) -> Unit) : Scheduler {
|
val wrapper = AtomicReference<Exception>()
|
||||||
override fun schedule(block: Callable<Unit>): Future<*>? {
|
executor.invoke(Runnable {
|
||||||
val latch = CountDownLatch(1)
|
try {
|
||||||
val wrapper = AtomicReference<Exception>()
|
block.call()
|
||||||
executor.invoke(Runnable {
|
} catch (e: Exception) {
|
||||||
try {
|
wrapper.set(e)
|
||||||
block.call()
|
} finally {
|
||||||
} catch (e: Exception) {
|
latch.countDown()
|
||||||
wrapper.set(e)
|
|
||||||
} finally {
|
|
||||||
latch.countDown()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return object : Future<Unit> {
|
|
||||||
override fun get(timeout: Long, unit: TimeUnit) {
|
|
||||||
latch.await(timeout, unit)
|
|
||||||
val e = wrapper.get()
|
|
||||||
if (e != null) throw ExecutionException(e)
|
|
||||||
}
|
|
||||||
override fun get() {
|
|
||||||
latch.await()
|
|
||||||
val e = wrapper.get()
|
|
||||||
if (e != null) throw ExecutionException(e)
|
|
||||||
}
|
|
||||||
override fun isDone() = latch.count == 0L
|
|
||||||
override fun isCancelled() = false
|
|
||||||
override fun cancel(mayInterruptIfRunning: Boolean) = false
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
return object : Future<Unit> {
|
||||||
|
override fun get(timeout: Long, unit: TimeUnit) {
|
||||||
|
latch.await(timeout, unit)
|
||||||
|
val e = wrapper.get()
|
||||||
|
if (e != null) throw ExecutionException(e)
|
||||||
|
}
|
||||||
|
override fun get() {
|
||||||
|
latch.await()
|
||||||
|
val e = wrapper.get()
|
||||||
|
if (e != null) throw ExecutionException(e)
|
||||||
|
}
|
||||||
|
override fun isDone() = latch.count == 0L
|
||||||
|
override fun isCancelled() = false
|
||||||
|
override fun cancel(mayInterruptIfRunning: Boolean) = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val NEW_THREAD: Scheduler = object : Scheduler {
|
}
|
||||||
override fun schedule(block: Callable<Unit>) = CACHED_EXECUTOR.submit(block)
|
|
||||||
}
|
private class SchedulerExecutorService(val executorService: ExecutorService) : Scheduler {
|
||||||
val IO: Scheduler = object : Scheduler {
|
override fun schedule(block: Callable<Unit>) = executorService.submit(block)
|
||||||
override fun schedule(block: Callable<Unit>) = IO_EXECUTOR.submit(block)
|
}
|
||||||
}
|
|
||||||
val DEFAULT = NEW_THREAD
|
companion object Schedulers {
|
||||||
private val CACHED_EXECUTOR: ExecutorService by lazy {
|
private val CACHED_EXECUTOR: ExecutorService by lazy {
|
||||||
ThreadPoolExecutor(0, Integer.MAX_VALUE,
|
ThreadPoolExecutor(0, Integer.MAX_VALUE,
|
||||||
60L, TimeUnit.SECONDS,
|
60L, TimeUnit.SECONDS,
|
||||||
@ -80,9 +76,31 @@ interface Scheduler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val SINGLE_EXECUTOR: ExecutorService by lazy {
|
||||||
|
Executors.newSingleThreadExecutor { r: Runnable ->
|
||||||
|
val thread: Thread = Executors.defaultThreadFactory().newThread(r)
|
||||||
|
thread.isDaemon = true
|
||||||
|
thread
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val IMMEDIATE: Scheduler = object : Scheduler {
|
||||||
|
override fun schedule(block: Callable<Unit>): Future<*>? {
|
||||||
|
block.call()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val JAVAFX: Scheduler = SchedulerImpl(Platform::runLater)
|
||||||
|
val SWING: Scheduler = SchedulerImpl(SwingUtilities::invokeLater)
|
||||||
|
val NEW_THREAD: Scheduler = SchedulerExecutorService(CACHED_EXECUTOR)
|
||||||
|
val IO: Scheduler = SchedulerExecutorService(IO_EXECUTOR)
|
||||||
|
val COMPUTATION: Scheduler = SchedulerExecutorService(SINGLE_EXECUTOR)
|
||||||
|
val DEFAULT = NEW_THREAD
|
||||||
|
|
||||||
fun shutdown() {
|
fun shutdown() {
|
||||||
CACHED_EXECUTOR.shutdown()
|
CACHED_EXECUTOR.shutdown()
|
||||||
IO_EXECUTOR.shutdown()
|
IO_EXECUTOR.shutdown()
|
||||||
|
SINGLE_EXECUTOR.shutdown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -66,8 +66,6 @@ class TaskExecutor() {
|
|||||||
}
|
}
|
||||||
if (canceled || Thread.interrupted())
|
if (canceled || Thread.interrupted())
|
||||||
taskListener?.onTerminate()
|
taskListener?.onTerminate()
|
||||||
else
|
|
||||||
taskListener?.end()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,5 +24,4 @@ interface TaskListener : EventListener {
|
|||||||
fun onFinished(task: Task) {}
|
fun onFinished(task: Task) {}
|
||||||
fun onFailed(task: Task) {}
|
fun onFailed(task: Task) {}
|
||||||
fun onTerminate() {}
|
fun onTerminate() {}
|
||||||
fun end() {}
|
|
||||||
}
|
}
|
@ -17,17 +17,49 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.util
|
package org.jackhuang.hmcl.util
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.event.EVENT_BUS
|
||||||
|
import org.jackhuang.hmcl.event.JVMLaunchFailedEvent
|
||||||
|
import org.jackhuang.hmcl.event.JavaProcessExitedAbnormallyEvent
|
||||||
|
import org.jackhuang.hmcl.event.JavaProcessStoppedEvent
|
||||||
|
import org.jackhuang.hmcl.launch.ProcessListener
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param process the process to wait for
|
* @param process the process to wait for
|
||||||
* @param watcher the callback that will be called after process stops.
|
* @param watcher the callback that will be called after process stops.
|
||||||
*/
|
*/
|
||||||
internal class ExitWaiter(val process: Process, val watcher: (Int) -> Unit) : Runnable {
|
internal class ExitWaiter(val process: JavaProcess, val joins: Collection<Thread>, val watcher: (Int, ProcessListener.ExitType) -> Unit) : Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
try {
|
try {
|
||||||
process.waitFor()
|
process.process.waitFor()
|
||||||
watcher(process.exitValue())
|
|
||||||
|
joins.forEach { it.join() }
|
||||||
|
|
||||||
|
val exitCode = process.exitCode
|
||||||
|
val lines = LinkedList<String>()
|
||||||
|
lines.addAll(process.stdErrLines)
|
||||||
|
lines.addAll(process.stdOutLines)
|
||||||
|
val errorLines = lines.filter(::guessLogLineError)
|
||||||
|
val exitType: ProcessListener.ExitType
|
||||||
|
// LaunchWrapper will catch the exception logged and will exit normally.
|
||||||
|
if (exitCode != 0 || errorLines.containsOne("Unable to launch")) {
|
||||||
|
EVENT_BUS.fireEvent(JavaProcessExitedAbnormallyEvent(this, process))
|
||||||
|
exitType = ProcessListener.ExitType.APPLICATION_ERROR
|
||||||
|
} else if (exitCode != 0 && errorLines.containsOne(
|
||||||
|
"Could not create the Java Virtual Machine.",
|
||||||
|
"Error occurred during initialization of VM",
|
||||||
|
"A fatal exception has occurred. Program will exit.",
|
||||||
|
"Unable to launch")) {
|
||||||
|
EVENT_BUS.fireEvent(JVMLaunchFailedEvent(this, process))
|
||||||
|
exitType = ProcessListener.ExitType.JVM_ERROR
|
||||||
|
} else
|
||||||
|
exitType = ProcessListener.ExitType.NORMAL
|
||||||
|
|
||||||
|
EVENT_BUS.fireEvent(JavaProcessStoppedEvent(this, process))
|
||||||
|
|
||||||
|
watcher(exitCode, exitType)
|
||||||
} catch (e: InterruptedException) {
|
} catch (e: InterruptedException) {
|
||||||
watcher(1)
|
watcher(1, ProcessListener.ExitType.NORMAL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -17,13 +17,16 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.util
|
package org.jackhuang.hmcl.util
|
||||||
|
|
||||||
import java.util.*
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
|
||||||
class JavaProcess(
|
class JavaProcess(
|
||||||
val process: Process,
|
val process: Process,
|
||||||
val commands: List<String>
|
val commands: List<String>
|
||||||
) {
|
) {
|
||||||
val stdOutLines: List<String> = Collections.synchronizedList(LinkedList<String>())
|
val properties = mutableMapOf<String, Any>()
|
||||||
|
val stdOutLines: MutableCollection<String> = ConcurrentLinkedQueue<String>()
|
||||||
|
val stdErrLines: MutableCollection<String> = ConcurrentLinkedQueue<String>()
|
||||||
|
val relatedThreads = mutableListOf<Thread>()
|
||||||
val isRunning: Boolean = try {
|
val isRunning: Boolean = try {
|
||||||
process.exitValue()
|
process.exitValue()
|
||||||
true
|
true
|
||||||
@ -32,11 +35,10 @@ class JavaProcess(
|
|||||||
}
|
}
|
||||||
val exitCode: Int get() = process.exitValue()
|
val exitCode: Int get() = process.exitValue()
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString() = "JavaProcess[commands=$commands, isRunning=$isRunning]"
|
||||||
return "JavaProcess[commands=$commands, isRunning=$isRunning]"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
process.destroy()
|
process.destroy()
|
||||||
|
relatedThreads.forEach(Thread::interrupt)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -122,6 +122,14 @@ fun parseParams(beforeFunc: (Any?) -> String, params: Array<*>?, afterFunc: (Any
|
|||||||
return sb.toString()
|
return sb.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Collection<String>.containsOne(vararg matcher: String): Boolean {
|
||||||
|
for (a in this)
|
||||||
|
for (b in matcher)
|
||||||
|
if (a.toLowerCase().contains(b.toLowerCase()))
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
fun <T> Property<in T>.updateAsync(newValue: T, update: AtomicReference<T>) {
|
fun <T> Property<in T>.updateAsync(newValue: T, update: AtomicReference<T>) {
|
||||||
if (update.getAndSet(newValue) == null) {
|
if (update.getAndSet(newValue) == null) {
|
||||||
UI_THREAD_SCHEDULER {
|
UI_THREAD_SCHEDULER {
|
||||||
|
@ -24,6 +24,8 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.logging.*
|
import java.util.logging.*
|
||||||
import java.util.logging.Formatter
|
import java.util.logging.Formatter
|
||||||
|
import javafx.scene.paint.Color
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
val LOG = Logger.getLogger("HMCL").apply {
|
val LOG = Logger.getLogger("HMCL").apply {
|
||||||
level = Level.FINER
|
level = Level.FINER
|
||||||
@ -50,4 +52,90 @@ internal object DefaultFormatter : Formatter() {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author huangyuhui
|
||||||
|
*/
|
||||||
|
enum class Log4jLevel constructor(val level: Int, val color: Color) {
|
||||||
|
|
||||||
|
FATAL(1, Color.RED),
|
||||||
|
ERROR(2, Color.RED),
|
||||||
|
WARN(3, Color.ORANGE),
|
||||||
|
INFO(4, Color.BLACK),
|
||||||
|
DEBUG(5, Color.BLUE),
|
||||||
|
TRACE(6, Color.BLUE),
|
||||||
|
ALL(2147483647, Color.BLACK);
|
||||||
|
|
||||||
|
fun lessOrEqual(level: Log4jLevel): Boolean {
|
||||||
|
return this.level <= level.level
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
val MINECRAFT_LOGGER = Pattern.compile("\\[(?<timestamp>[0-9:]+)\\] \\[[^/]+/(?<level>[^\\]]+)\\]")
|
||||||
|
val MINECRAFT_LOGGER_CATEGORY = Pattern.compile("\\[(?<timestamp>[0-9:]+)\\] \\[[^/]+/(?<level>[^\\]]+)\\] \\[(?<category>[^\\]]+)\\]")
|
||||||
|
val JAVA_SYMBOL = "([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$][a-zA-Z\\d_$]*"
|
||||||
|
|
||||||
|
fun guessLevel(line: String): Log4jLevel? {
|
||||||
|
var level: Log4jLevel? = null
|
||||||
|
val m = MINECRAFT_LOGGER.matcher(line)
|
||||||
|
if (m.find()) {
|
||||||
|
// New style logs from log4j
|
||||||
|
val levelStr = m.group("level")
|
||||||
|
if (null != levelStr)
|
||||||
|
when (levelStr) {
|
||||||
|
"INFO" -> level = INFO
|
||||||
|
"WARN" -> level = WARN
|
||||||
|
"ERROR" -> level = ERROR
|
||||||
|
"FATAL" -> level = FATAL
|
||||||
|
"TRACE" -> level = TRACE
|
||||||
|
"DEBUG" -> level = DEBUG
|
||||||
|
else -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val m2 = MINECRAFT_LOGGER_CATEGORY.matcher(line)
|
||||||
|
if (m2.find()) {
|
||||||
|
val level2Str = m2.group("category")
|
||||||
|
if (null != level2Str)
|
||||||
|
when (level2Str) {
|
||||||
|
"STDOUT" -> level = INFO
|
||||||
|
"STDERR" -> level = ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]")
|
||||||
|
|| line.contains("[FINER]") || line.contains("[FINEST]"))
|
||||||
|
level = INFO
|
||||||
|
if (line.contains("[SEVERE]") || line.contains("[STDERR]"))
|
||||||
|
level = ERROR
|
||||||
|
if (line.contains("[WARNING]"))
|
||||||
|
level = WARN
|
||||||
|
if (line.contains("[DEBUG]"))
|
||||||
|
level = DEBUG
|
||||||
|
}
|
||||||
|
return if (line.contains("overwriting existing")) FATAL else level
|
||||||
|
|
||||||
|
/*if (line.contains("Exception in thread")
|
||||||
|
|| line.matches("\\s+at " + JAVA_SYMBOL)
|
||||||
|
|| line.matches("Caused by: " + JAVA_SYMBOL)
|
||||||
|
|| line.matches("([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$]?[a-zA-Z\\d_$]*(Exception|Error|Throwable)")
|
||||||
|
|| line.matches("... \\d+ more$"))
|
||||||
|
return ERROR;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isError(a: Log4jLevel?): Boolean {
|
||||||
|
return a?.lessOrEqual(ERROR) ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mergeLevel(a: Log4jLevel?, b: Log4jLevel?): Log4jLevel? {
|
||||||
|
return if (a == null) b
|
||||||
|
else if (b == null) a
|
||||||
|
else if (a.level < b.level) a else b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun guessLogLineError(log: String) = Log4jLevel.isError(Log4jLevel.guessLevel(log))
|
@ -22,17 +22,23 @@ import java.io.InputStream
|
|||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
|
|
||||||
internal class StreamPump @JvmOverloads constructor(
|
internal class StreamPump @JvmOverloads constructor(
|
||||||
val inputStream: InputStream,
|
private val inputStream: InputStream,
|
||||||
val callback: (String) -> Unit = {}
|
private val callback: (String) -> Unit = {}
|
||||||
) : Runnable {
|
) : Runnable {
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
try {
|
try {
|
||||||
inputStream.bufferedReader(SYSTEM_CHARSET).useLines {
|
inputStream.bufferedReader(SYSTEM_CHARSET).useLines { lines ->
|
||||||
it.forEach(callback)
|
for (line in lines) {
|
||||||
|
if (Thread.currentThread().isInterrupted) {
|
||||||
|
Thread.currentThread().interrupt()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
callback(line)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
LOG.log(Level.SEVERE, "An error occured when reading stream", e)
|
LOG.log(Level.SEVERE, "An error occurred when reading stream", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user