Rework error handling to be more robust

This commit is contained in:
comp500 2020-12-15 17:28:23 +00:00
parent 1d4c94f5b6
commit 0858c90079
10 changed files with 170 additions and 115 deletions

View File

@ -14,8 +14,6 @@ java {
dependencies { dependencies {
implementation("commons-cli:commons-cli:1.4") implementation("commons-cli:commons-cli:1.4")
implementation("com.moandjiezana.toml:toml4j:0.7.2") implementation("com.moandjiezana.toml:toml4j:0.7.2")
// TODO: Implement tests
//testImplementation "junit:junit:4.12"
implementation("com.google.code.gson:gson:2.8.1") implementation("com.google.code.gson:gson:2.8.1")
implementation("com.squareup.okio:okio:2.2.2") implementation("com.squareup.okio:okio:2.2.2")
implementation(kotlin("stdlib-jdk8")) implementation(kotlin("stdlib-jdk8"))

View File

@ -8,6 +8,7 @@ import link.infra.packwiz.installer.metadata.hash.HashUtils.getHash
import link.infra.packwiz.installer.metadata.hash.HashUtils.getHasher import link.infra.packwiz.installer.metadata.hash.HashUtils.getHasher
import link.infra.packwiz.installer.ui.data.ExceptionDetails import link.infra.packwiz.installer.ui.data.ExceptionDetails
import link.infra.packwiz.installer.ui.data.IOptionDetails import link.infra.packwiz.installer.ui.data.IOptionDetails
import link.infra.packwiz.installer.util.Log
import okio.Buffer import okio.Buffer
import okio.HashingSink import okio.HashingSink
import okio.buffer import okio.buffer
@ -125,6 +126,7 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, de
fun download(packFolder: String, indexUri: SpaceSafeURI) { fun download(packFolder: String, indexUri: SpaceSafeURI) {
if (err != null) return if (err != null) return
// TODO: is this necessary if we overwrite?
// Ensure it is removed // Ensure it is removed
cachedFile?.let { cachedFile?.let {
if (!it.optionValue || !correctSide()) { if (!it.optionValue || !correctSide()) {
@ -133,8 +135,7 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, de
try { try {
Files.deleteIfExists(Paths.get(packFolder, it.cachedLocation)) Files.deleteIfExists(Paths.get(packFolder, it.cachedLocation))
} catch (e: IOException) { } catch (e: IOException) {
// TODO: how much of a problem is this? use log4j/other log library to show warning? Log.warn("Failed to delete file before downloading", e)
e.printStackTrace()
} }
it.cachedLocation = null it.cachedLocation = null
} }
@ -190,7 +191,7 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, de
Files.copy(data.inputStream(), destPath, StandardCopyOption.REPLACE_EXISTING) Files.copy(data.inputStream(), destPath, StandardCopyOption.REPLACE_EXISTING)
data.clear() data.clear()
} else { } else {
// TODO: no more PRINTLN!!!!!!!!! // TODO: move println to something visible in the error window
println("Invalid hash for " + metadata.destURI.toString()) println("Invalid hash for " + metadata.destURI.toString())
println("Calculated: " + fileSource.hash) println("Calculated: " + fileSource.hash)
println("Expected: $hash") println("Expected: $hash")

View File

@ -5,6 +5,7 @@ package link.infra.packwiz.installer
import link.infra.packwiz.installer.metadata.SpaceSafeURI import link.infra.packwiz.installer.metadata.SpaceSafeURI
import link.infra.packwiz.installer.ui.cli.CLIHandler import link.infra.packwiz.installer.ui.cli.CLIHandler
import link.infra.packwiz.installer.ui.gui.GUIHandler import link.infra.packwiz.installer.ui.gui.GUIHandler
import link.infra.packwiz.installer.util.Log
import org.apache.commons.cli.DefaultParser import org.apache.commons.cli.DefaultParser
import org.apache.commons.cli.Options import org.apache.commons.cli.Options
import org.apache.commons.cli.ParseException import org.apache.commons.cli.ParseException
@ -29,7 +30,7 @@ class Main(args: Array<String>) {
val cmd = try { val cmd = try {
parser.parse(options, args) parser.parse(options, args)
} catch (e: ParseException) { } catch (e: ParseException) {
e.printStackTrace() Log.fatal("Failed to parse command line arguments", e)
if (guiEnabled) { if (guiEnabled) {
EventQueue.invokeAndWait { EventQueue.invokeAndWait {
try { try {
@ -37,7 +38,8 @@ class Main(args: Array<String>) {
} catch (ignored: Exception) { } catch (ignored: Exception) {
// Ignore the exceptions, just continue using the ugly L&F // Ignore the exceptions, just continue using the ugly L&F
} }
JOptionPane.showMessageDialog(null, e.message, "packwiz-installer", JOptionPane.ERROR_MESSAGE) JOptionPane.showMessageDialog(null, "Failed to parse command line arguments: $e",
"packwiz-installer", JOptionPane.ERROR_MESSAGE)
} }
} }
exitProcess(1) exitProcess(1)
@ -51,33 +53,34 @@ class Main(args: Array<String>) {
val unparsedArgs = cmd.args val unparsedArgs = cmd.args
if (unparsedArgs.size > 1) { if (unparsedArgs.size > 1) {
ui.handleExceptionAndExit(RuntimeException("Too many arguments specified!")) ui.showErrorAndExit("Too many arguments specified!")
} else if (unparsedArgs.isEmpty()) { } else if (unparsedArgs.isEmpty()) {
ui.handleExceptionAndExit(RuntimeException("URI to install from must be specified!")) ui.showErrorAndExit("pack.toml URI to install from must be specified!")
} }
cmd.getOptionValue("title")?.also(ui::setTitle) val title = cmd.getOptionValue("title")
if (title != null) {
ui.setTitle(title)
}
ui.show() ui.show()
val uOptions = UpdateManager.Options().apply { val uOptions = try {
side = cmd.getOptionValue("side")?.let((UpdateManager.Options.Side)::from) ?: side UpdateManager.Options.construct(
packFolder = cmd.getOptionValue("pack-folder") ?: packFolder downloadURI = SpaceSafeURI(unparsedArgs[0]),
manifestFile = cmd.getOptionValue("meta-file") ?: manifestFile side = cmd.getOptionValue("side")?.let((UpdateManager.Options.Side)::from),
} packFolder = cmd.getOptionValue("pack-folder"),
manifestFile = cmd.getOptionValue("meta-file")
try { )
uOptions.downloadURI = SpaceSafeURI(unparsedArgs[0])
} catch (e: URISyntaxException) { } catch (e: URISyntaxException) {
// TODO: better error message? ui.showErrorAndExit("Failed to read pack.toml URI", e)
ui.handleExceptionAndExit(e)
} }
// Start update process! // Start update process!
try { try {
UpdateManager(uOptions, ui) UpdateManager(uOptions, ui)
} catch (e: Exception) { // TODO: better error message? } catch (e: Exception) {
ui.handleExceptionAndExit(e) ui.showErrorAndExit("Update process failed", e)
} }
println("Finished successfully!") println("Finished successfully!")
ui.dispose() ui.dispose()
@ -111,17 +114,17 @@ class Main(args: Array<String>) {
try { try {
startup(args) startup(args)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() Log.fatal("Error from main", e)
if (guiEnabled) { if (guiEnabled) {
EventQueue.invokeLater { EventQueue.invokeLater {
JOptionPane.showMessageDialog(null, JOptionPane.showMessageDialog(null,
"A fatal error occurred: \n" + e.javaClass.canonicalName + ": " + e.message, "A fatal error occurred: \n$e",
"packwiz-installer", JOptionPane.ERROR_MESSAGE) "packwiz-installer", JOptionPane.ERROR_MESSAGE)
exitProcess(1) exitProcess(1)
} }
// In case the EventQueue is broken, exit after 1 minute
Thread.sleep(60 * 1000.toLong())
} }
// In case the EventQueue is broken, exit after 1 minute
Thread.sleep(60 * 1000.toLong())
exitProcess(1) exitProcess(1)
} }
} }

View File

@ -19,6 +19,9 @@ import link.infra.packwiz.installer.ui.IUserInterface
import link.infra.packwiz.installer.ui.IUserInterface.CancellationResult import link.infra.packwiz.installer.ui.IUserInterface.CancellationResult
import link.infra.packwiz.installer.ui.IUserInterface.ExceptionListResult import link.infra.packwiz.installer.ui.IUserInterface.ExceptionListResult
import link.infra.packwiz.installer.ui.data.InstallProgress import link.infra.packwiz.installer.ui.data.InstallProgress
import link.infra.packwiz.installer.util.Log
import link.infra.packwiz.installer.util.ifletOrErr
import link.infra.packwiz.installer.util.ifletOrWarn
import okio.buffer import okio.buffer
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.FileReader import java.io.FileReader
@ -43,11 +46,17 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
} }
data class Options( data class Options(
var downloadURI: SpaceSafeURI? = null, val downloadURI: SpaceSafeURI,
var manifestFile: String = "packwiz.json", // TODO: make configurable val manifestFile: String,
var packFolder: String = ".", val packFolder: String,
var side: Side = Side.CLIENT val side: Side
) { ) {
// Horrible workaround for default params not working cleanly with nullable values
companion object {
fun construct(downloadURI: SpaceSafeURI, manifestFile: String?, packFolder: String?, side: Side?) =
Options(downloadURI, manifestFile ?: "packwiz.json", packFolder ?: ".", side ?: Side.CLIENT)
}
enum class Side { enum class Side {
@SerializedName("client") @SerializedName("client")
CLIENT("client"), CLIENT("client"),
@ -111,11 +120,9 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
} catch (e: FileNotFoundException) { } catch (e: FileNotFoundException) {
ManifestFile() ManifestFile()
} catch (e: JsonSyntaxException) { } catch (e: JsonSyntaxException) {
ui.handleExceptionAndExit(e) ui.showErrorAndExit("Invalid local manifest file, try deleting ${opts.manifestFile}", e)
return
} catch (e: JsonIOException) { } catch (e: JsonIOException) {
ui.handleExceptionAndExit(e) ui.showErrorAndExit("Failed to read local manifest file, try deleting ${opts.manifestFile}", e)
return
} }
if (ui.cancelButtonPressed) { if (ui.cancelButtonPressed) {
@ -125,19 +132,16 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
ui.submitProgress(InstallProgress("Loading pack file...")) ui.submitProgress(InstallProgress("Loading pack file..."))
val packFileSource = try { val packFileSource = try {
val src = getFileSource(opts.downloadURI!!) val src = getFileSource(opts.downloadURI)
getHasher("sha256").getHashingSource(src) getHasher("sha256").getHashingSource(src)
} catch (e: Exception) { } catch (e: Exception) {
// TODO: run cancellation window? ui.showErrorAndExit("Failed to download pack.toml", e)
ui.handleExceptionAndExit(e)
return
} }
val pf = packFileSource.buffer().use { val pf = packFileSource.buffer().use {
try { try {
Toml().read(it.inputStream()).to(PackFile::class.java) Toml().read(it.inputStream()).to(PackFile::class.java)
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
ui.handleExceptionAndExit(e) ui.showErrorAndExit("Failed to parse pack.toml", e)
return
} }
} }
@ -169,40 +173,41 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
} }
} }
if (invalid) { if (invalid) {
println("File $fileUri invalidated, marked for redownloading") Log.info("File $fileUri invalidated, marked for redownloading")
invalidatedUris.add(fileUri) invalidatedUris.add(fileUri)
} }
} }
if (manifest.packFileHash?.let { packFileSource.hashIsEqual(it) } == true && invalidatedUris.isEmpty()) { if (manifest.packFileHash?.let { packFileSource.hashIsEqual(it) } == true && invalidatedUris.isEmpty()) {
println("Modpack is already up to date!") Log.info("Modpack is already up to date!")
// todo: --force? // todo: --force?
if (!ui.optionsButtonPressed) { if (!ui.optionsButtonPressed) {
return return
} }
} }
println("Modpack name: " + pf.name) Log.info("Modpack name: ${pf.name}")
if (ui.cancelButtonPressed) { if (ui.cancelButtonPressed) {
showCancellationDialog() showCancellationDialog()
handleCancellation() handleCancellation()
} }
try { try {
val index = pf.index!! ifletOrWarn(pf.index, "No index file found") { index ->
getNewLoc(opts.downloadURI, index.file)?.let { newLoc -> ui.ifletOrErr(index.hashFormat, index.hash, "Pack has no hash or hashFormat for index") { hashFormat, hash ->
index.hashFormat?.let { hashFormat -> ui.ifletOrErr(getNewLoc(opts.downloadURI, index.file), "Pack has invalid index file: " + index.file) { newLoc ->
processIndex( processIndex(
newLoc, newLoc,
getHash(index.hashFormat!!, index.hash!!), getHash(hashFormat, hash),
hashFormat, hashFormat,
manifest, manifest,
invalidatedUris invalidatedUris
) )
}
} }
} }
} catch (e1: Exception) { } catch (e1: Exception) {
ui.handleExceptionAndExit(e1) ui.showErrorAndExit("Failed to process index file", e1)
} }
handleCancellation() handleCancellation()
@ -221,8 +226,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
try { try {
FileWriter(Paths.get(opts.packFolder, opts.manifestFile).toString()).use { writer -> gson.toJson(manifest, writer) } FileWriter(Paths.get(opts.packFolder, opts.manifestFile).toString()).use { writer -> gson.toJson(manifest, writer) }
} catch (e: IOException) { } catch (e: IOException) {
// TODO: add message? ui.showErrorAndExit("Failed to save local manifest file", e)
ui.handleException(e)
} }
} }
@ -232,7 +236,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
private fun processIndex(indexUri: SpaceSafeURI, indexHash: Hash, hashFormat: String, manifest: ManifestFile, invalidatedUris: List<SpaceSafeURI>) { private fun processIndex(indexUri: SpaceSafeURI, indexHash: Hash, hashFormat: String, manifest: ManifestFile, invalidatedUris: List<SpaceSafeURI>) {
if (manifest.indexFileHash == indexHash && invalidatedUris.isEmpty()) { if (manifest.indexFileHash == indexHash && invalidatedUris.isEmpty()) {
println("Modpack files are already up to date!") Log.info("Modpack files are already up to date!")
if (!ui.optionsButtonPressed) { if (!ui.optionsButtonPressed) {
return return
} }
@ -243,20 +247,18 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
val src = getFileSource(indexUri) val src = getFileSource(indexUri)
getHasher(hashFormat).getHashingSource(src) getHasher(hashFormat).getHashingSource(src)
} catch (e: Exception) { } catch (e: Exception) {
// TODO: run cancellation window? ui.showErrorAndExit("Failed to download index file", e)
ui.handleExceptionAndExit(e)
return
} }
val indexFile = try { val indexFile = try {
Toml().read(indexFileSource.buffer().inputStream()).to(IndexFile::class.java) Toml().read(indexFileSource.buffer().inputStream()).to(IndexFile::class.java)
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
ui.handleExceptionAndExit(e) ui.showErrorAndExit("Failed to parse index file", e)
return
} }
if (!indexFileSource.hashIsEqual(indexHash)) { if (!indexFileSource.hashIsEqual(indexHash)) {
ui.handleExceptionAndExit(RuntimeException("Your index hash is invalid! Please run packwiz refresh on the pack again")) ui.showErrorAndExit("Your index file hash is invalid! The pack developer should packwiz refresh on the pack again")
return
} }
if (ui.cancelButtonPressed) { if (ui.cancelButtonPressed) {
showCancellationDialog() showCancellationDialog()
return return
@ -274,8 +276,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
try { try {
Files.deleteIfExists(Paths.get(opts.packFolder, file.cachedLocation)) Files.deleteIfExists(Paths.get(opts.packFolder, file.cachedLocation))
} catch (e: IOException) { } catch (e: IOException) {
// TODO: should this be shown to the user in some way? Log.warn("Failed to delete optional disabled file", e)
e.printStackTrace()
} }
// Set to null, as it doesn't exist anymore // Set to null, as it doesn't exist anymore
file.cachedLocation = null file.cachedLocation = null
@ -285,8 +286,8 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
if (!alreadyDeleted) { if (!alreadyDeleted) {
try { try {
Files.deleteIfExists(Paths.get(opts.packFolder, file.cachedLocation)) Files.deleteIfExists(Paths.get(opts.packFolder, file.cachedLocation))
} catch (e: IOException) { // TODO: should this be shown to the user in some way? } catch (e: IOException) {
e.printStackTrace() Log.warn("Failed to delete file removed from index", e)
} }
} }
it.remove() it.remove()
@ -302,14 +303,14 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
// TODO: progress bar? // TODO: progress bar?
if (indexFile.files.isEmpty()) { if (indexFile.files.isEmpty()) {
println("Warning: Index is empty!") Log.warn("Index is empty!")
} }
val tasks = createTasksFromIndex(indexFile, indexFile.hashFormat, opts.side) val tasks = createTasksFromIndex(indexFile, indexFile.hashFormat, opts.side)
// If the side changes, invalidate EVERYTHING just in case // If the side changes, invalidate EVERYTHING just in case
// Might not be needed, but done just to be safe // Might not be needed, but done just to be safe
val invalidateAll = opts.side != manifest.cachedSide val invalidateAll = opts.side != manifest.cachedSide
if (invalidateAll) { if (invalidateAll) {
println("Side changed, invalidating all mods") Log.info("Side changed, invalidating all mods")
} }
tasks.forEach{ f -> tasks.forEach{ f ->
// TODO: should linkedfile be checked as well? should this be done in the download section? // TODO: should linkedfile be checked as well? should this be done in the download section?
@ -377,18 +378,15 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
} }
} }
for (i in tasks.indices) { for (i in tasks.indices) {
var task: DownloadTask? val task: DownloadTask = try {
task = try {
completionService.take().get() completionService.take().get()
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
ui.handleException(e) ui.showErrorAndExit("Interrupted when consuming download tasks", e)
null
} catch (e: ExecutionException) { } catch (e: ExecutionException) {
ui.handleException(e) ui.showErrorAndExit("Failed to execute download task", e)
null
} }
// Update manifest - If there were no errors cachedFile has already been modified in place (good old pass by reference) // Update manifest - If there were no errors cachedFile has already been modified in place (good old pass by reference)
task?.cachedFile?.let { file -> task.cachedFile?.let { file ->
if (task.failed()) { if (task.failed()) {
val oldFile = file.revert val oldFile = file.revert
if (oldFile != null) { if (oldFile != null) {
@ -399,17 +397,11 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
} }
} }
var progress: String val exDetails = task.exceptionDetails
if (task != null) { val progress = if (exDetails != null) {
val exDetails = task.exceptionDetails "Failed to download ${exDetails.name}: ${exDetails.exception.message}"
if (exDetails != null) {
progress = "Failed to download ${exDetails.name}: ${exDetails.exception.message}"
exDetails.exception.printStackTrace()
} else {
progress = "Downloaded ${task.name}"
}
} else { } else {
progress = "Failed to download, unknown reason" "Downloaded ${task.name}"
} }
ui.submitProgress(InstallProgress(progress, i + 1, tasks.size)) ui.submitProgress(InstallProgress(progress, i + 1, tasks.size))

View File

@ -14,6 +14,7 @@ object HandlerManager {
RequestHandlerFile() RequestHandlerFile()
) )
// TODO: get rid of nullable stuff here
@JvmStatic @JvmStatic
fun getNewLoc(base: SpaceSafeURI?, loc: SpaceSafeURI?): SpaceSafeURI? { fun getNewLoc(base: SpaceSafeURI?, loc: SpaceSafeURI?): SpaceSafeURI? {
if (loc == null) { if (loc == null) {
@ -32,6 +33,8 @@ object HandlerManager {
// Zip handler discards once read, requesting multiple times on other handlers would cause multiple downloads // Zip handler discards once read, requesting multiple times on other handlers would cause multiple downloads
// Caching system? Copy from already downloaded files? // Caching system? Copy from already downloaded files?
// TODO: change to use something more idiomatic than exceptions?
@JvmStatic @JvmStatic
@Throws(Exception::class) @Throws(Exception::class)
fun getFileSource(loc: SpaceSafeURI): Source { fun getFileSource(loc: SpaceSafeURI): Source {

View File

@ -3,16 +3,15 @@ package link.infra.packwiz.installer.ui
import link.infra.packwiz.installer.ui.data.ExceptionDetails import link.infra.packwiz.installer.ui.data.ExceptionDetails
import link.infra.packwiz.installer.ui.data.IOptionDetails import link.infra.packwiz.installer.ui.data.IOptionDetails
import link.infra.packwiz.installer.ui.data.InstallProgress import link.infra.packwiz.installer.ui.data.InstallProgress
import kotlin.system.exitProcess
interface IUserInterface { interface IUserInterface {
fun show() fun show()
fun dispose() fun dispose()
fun handleException(e: Exception)
fun handleExceptionAndExit(e: Exception) { fun showErrorAndExit(message: String): Nothing {
handleException(e) showErrorAndExit(message, null)
exitProcess(1)
} }
fun showErrorAndExit(message: String, e: Exception?): Nothing
fun setTitle(title: String) {} fun setTitle(title: String) {}
fun submitProgress(progress: InstallProgress) fun submitProgress(progress: InstallProgress)

View File

@ -5,6 +5,7 @@ import link.infra.packwiz.installer.ui.IUserInterface.ExceptionListResult
import link.infra.packwiz.installer.ui.data.ExceptionDetails import link.infra.packwiz.installer.ui.data.ExceptionDetails
import link.infra.packwiz.installer.ui.data.IOptionDetails import link.infra.packwiz.installer.ui.data.IOptionDetails
import link.infra.packwiz.installer.ui.data.InstallProgress import link.infra.packwiz.installer.ui.data.InstallProgress
import link.infra.packwiz.installer.util.Log
import kotlin.system.exitProcess import kotlin.system.exitProcess
class CLIHandler : IUserInterface { class CLIHandler : IUserInterface {
@ -13,8 +14,13 @@ class CLIHandler : IUserInterface {
@Volatile @Volatile
override var cancelButtonPressed = false override var cancelButtonPressed = false
override fun handleException(e: Exception) { override fun showErrorAndExit(message: String, e: Exception?): Nothing {
e.printStackTrace() if (e != null) {
Log.fatal(message, e)
} else {
Log.fatal(message)
}
exitProcess(1)
} }
override fun show() {} override fun show() {}
@ -36,7 +42,7 @@ class CLIHandler : IUserInterface {
for (opt in options) { for (opt in options) {
opt.optionValue = true opt.optionValue = true
// TODO: implement option choice in the CLI? // TODO: implement option choice in the CLI?
println("Warning: accepting option " + opt.name + " as option choosing is not implemented in the CLI") Log.warn("Accepting option ${opt.name} as option choosing is not implemented in the CLI")
} }
return false // Can't be cancelled! return false // Can't be cancelled!
} }
@ -44,9 +50,9 @@ class CLIHandler : IUserInterface {
override fun showExceptions(exceptions: List<ExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): ExceptionListResult { override fun showExceptions(exceptions: List<ExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): ExceptionListResult {
println("Failed to download modpack, the following errors were encountered:") println("Failed to download modpack, the following errors were encountered:")
for (ex in exceptions) { for (ex in exceptions) {
println(ex.name + ": ") print(ex.name + ": ")
ex.exception.printStackTrace() ex.exception.printStackTrace()
} }
exitProcess(1) return ExceptionListResult.CANCEL
} }
} }

View File

@ -5,6 +5,7 @@ import link.infra.packwiz.installer.ui.IUserInterface.ExceptionListResult
import link.infra.packwiz.installer.ui.data.ExceptionDetails import link.infra.packwiz.installer.ui.data.ExceptionDetails
import link.infra.packwiz.installer.ui.data.IOptionDetails import link.infra.packwiz.installer.ui.data.IOptionDetails
import link.infra.packwiz.installer.ui.data.InstallProgress import link.infra.packwiz.installer.ui.data.InstallProgress
import link.infra.packwiz.installer.util.Log
import java.awt.EventQueue import java.awt.EventQueue
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import javax.swing.JDialog import javax.swing.JDialog
@ -27,8 +28,7 @@ class GUIHandler : IUserInterface {
try { try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()) UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
} catch (e: Exception) { } catch (e: Exception) {
println("Failed to set look and feel:") Log.warn("Failed to set look and feel", e)
e.printStackTrace()
} }
frmPackwizlauncher = InstallWindow(this).apply { frmPackwizlauncher = InstallWindow(this).apply {
title = this@GUIHandler.title title = this@GUIHandler.title
@ -44,23 +44,23 @@ class GUIHandler : IUserInterface {
frmPackwizlauncher.dispose() frmPackwizlauncher.dispose()
} }
override fun handleException(e: Exception) { override fun showErrorAndExit(message: String, e: Exception?): Nothing {
e.printStackTrace() if (e != null) {
EventQueue.invokeAndWait { Log.fatal(message, e)
JOptionPane.showMessageDialog(null, EventQueue.invokeAndWait {
"An error occurred: \n" + e.javaClass.canonicalName + ": " + e.message, JOptionPane.showMessageDialog(null,
"$message: $e",
title, JOptionPane.ERROR_MESSAGE) title, JOptionPane.ERROR_MESSAGE)
} }
} } else {
Log.fatal(message)
override fun handleExceptionAndExit(e: Exception) { EventQueue.invokeAndWait {
e.printStackTrace() JOptionPane.showMessageDialog(null,
EventQueue.invokeAndWait { message,
JOptionPane.showMessageDialog(null,
"A fatal error occurred: \n" + e.javaClass.canonicalName + ": " + e.message,
title, JOptionPane.ERROR_MESSAGE) title, JOptionPane.ERROR_MESSAGE)
exitProcess(1) }
} }
exitProcess(1)
} }
override fun setTitle(title: String) { override fun setTitle(title: String) {
@ -78,8 +78,7 @@ class GUIHandler : IUserInterface {
sb.append(") ") sb.append(") ")
} }
sb.append(progress.message) sb.append(progress.message)
// TODO: better logging library? Log.info(sb.toString())
println(sb.toString())
EventQueue.invokeLater { EventQueue.invokeLater {
frmPackwizlauncher.displayProgress(progress) frmPackwizlauncher.displayProgress(progress)
} }

View File

@ -0,0 +1,38 @@
package link.infra.packwiz.installer.util
import link.infra.packwiz.installer.ui.IUserInterface
inline fun <T> iflet(value: T?, whenNotNull: (T) -> Unit) {
if (value != null) {
whenNotNull(value)
}
}
inline fun <T, U> IUserInterface.ifletOrErr(value: T?, message: String, whenNotNull: (T) -> U): U =
if (value != null) {
whenNotNull(value)
} else {
this.showErrorAndExit(message)
}
inline fun <T, U, V> IUserInterface.ifletOrErr(value: T?, value2: U?, message: String, whenNotNull: (T, U) -> V): V =
if (value != null && value2 != null) {
whenNotNull(value, value2)
} else {
this.showErrorAndExit(message)
}
inline fun <T> ifletOrWarn(value: T?, message: String, whenNotNull: (T) -> Unit) {
if (value != null) {
whenNotNull(value)
} else {
Log.warn(message)
}
}
inline fun <T, U> iflet(value: T?, whenNotNull: (T) -> U, whenNull: () -> U): U =
if (value != null) {
whenNotNull(value)
} else {
whenNull()
}

View File

@ -0,0 +1,16 @@
package link.infra.packwiz.installer.util
object Log {
fun info(message: String) = println(message)
fun warn(message: String) = println("[Warning] $message")
fun warn(message: String, exception: Exception) = println("[Warning] $message: $exception")
fun fatal(message: String) {
println("[FATAL] $message")
}
fun fatal(message: String, exception: Exception) {
println("[FATAL] $message: ")
exception.printStackTrace()
}
}