From 7a70ab32c98441f3bc8f6b2af830332bb3f5cf79 Mon Sep 17 00:00:00 2001 From: Daniel Wolf Date: Tue, 10 Apr 2018 22:04:19 +0200 Subject: [PATCH] Show stack trace for internal exceptions --- .../rhubarb_for_spine/AudioFileModel.kt | 12 +++++------ .../rhubarb_for_spine/EndUserException.kt | 4 ++++ .../rhubarb_for_spine/MainModel.kt | 6 +++--- .../rhubarb_for_spine/RhubarbTask.kt | 10 +++++----- .../rhubarb_for_spine/SpineJson.kt | 20 +++++++++---------- .../rhubarb_for_spine/tools.kt | 12 +++++++---- 6 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/EndUserException.kt diff --git a/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/AudioFileModel.kt b/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/AudioFileModel.kt index fa293ba..bf46277 100644 --- a/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/AudioFileModel.kt +++ b/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/AudioFileModel.kt @@ -162,21 +162,21 @@ class AudioFileModel( } } catch (e: InterruptedException) { } catch (e: Exception) { - e.printStackTrace(System.err); + e.printStackTrace(System.err) Platform.runLater { Alert(Alert.AlertType.ERROR).apply { headerText = "Error performing lip sync for event '$eventName'." - contentText = if (e.message.isNullOrEmpty()) - // Some exceptions don't have a message - "An internal error of type ${e.javaClass.name} occurred." - else + contentText = if (e is EndUserException) e.message + else + ("An internal error occurred.\n" + + "Please report an issue, including the following information.\n" + + getStackTrace(e)) show() } } } - } future = executor.submit(wrapperTask) } diff --git a/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/EndUserException.kt b/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/EndUserException.kt new file mode 100644 index 0000000..dc79066 --- /dev/null +++ b/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/EndUserException.kt @@ -0,0 +1,4 @@ +package com.rhubarb_lip_sync.rhubarb_for_spine + +// An exception with a human-readable message that can be shown to the end user +class EndUserException(message: String): Exception(message) \ No newline at end of file diff --git a/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/MainModel.kt b/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/MainModel.kt index ffe55e9..82b1686 100644 --- a/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/MainModel.kt +++ b/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/MainModel.kt @@ -15,18 +15,18 @@ class MainModel(private val executor: ExecutorService) { filePathError = getExceptionMessage { animationFileModel = null if (value.isNullOrBlank()) { - throw Exception("No input file specified.") + throw EndUserException("No input file specified.") } val path = try { val trimmed = value.removeSurrounding("\"") Paths.get(trimmed) } catch (e: InvalidPathException) { - throw Exception("Not a valid file path.") + throw EndUserException("Not a valid file path.") } if (!Files.exists(path)) { - throw Exception("File does not exist.") + throw EndUserException("File does not exist.") } animationFileModel = AnimationFileModel(this, path, executor) diff --git a/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/RhubarbTask.kt b/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/RhubarbTask.kt index d0e24a7..29bb34d 100644 --- a/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/RhubarbTask.kt +++ b/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/RhubarbTask.kt @@ -24,7 +24,7 @@ class RhubarbTask( throw InterruptedException() } if (!Files.exists(audioFilePath)) { - throw IllegalArgumentException("File '$audioFilePath' does not exist."); + throw EndUserException("File '$audioFilePath' does not exist."); } val dialogFile = if (dialog != null) TemporaryTextFile(dialog) else null @@ -50,7 +50,7 @@ class RhubarbTask( return parseRhubarbResult(resultString) } "failure" -> { - throw Exception(message.string("reason")) + throw EndUserException(message.string("reason") ?: "Rhubarb failed without reason.") } } } @@ -58,13 +58,13 @@ class RhubarbTask( process.destroyForcibly() throw e } catch (e: EOFException) { - throw Exception("Rhubarb terminated unexpectedly.") + throw EndUserException("Rhubarb terminated unexpectedly.") } finally { process.waitFor(); } }} - throw Exception("An unexpected error occurred.") + throw EndUserException("Audio file processing terminated in an unexpected way.") } private fun parseRhubarbResult(jsonString: String): List { @@ -118,7 +118,7 @@ class RhubarbTask( } currentDirectory = currentDirectory.parent } - throw Exception("Could not find Rhubarb Lip Sync executable '$rhubarbBinName'." + throw EndUserException("Could not find Rhubarb Lip Sync executable '$rhubarbBinName'." + " Expected to find it in '$guiBinDirectory' or any directory above.") } diff --git a/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/SpineJson.kt b/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/SpineJson.kt index d351019..f59df1e 100644 --- a/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/SpineJson.kt +++ b/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/SpineJson.kt @@ -14,16 +14,16 @@ class SpineJson(val filePath: Path) { init { if (!Files.exists(filePath)) { - throw Exception("File '$filePath' does not exist.") + throw EndUserException("File '$filePath' does not exist.") } try { json = Parser().parse(filePath.toString()) as JsonObject } catch (e: Exception) { - throw Exception("Wrong file format. This is not a valid JSON file.") + throw EndUserException("Wrong file format. This is not a valid JSON file.") } - skeleton = json.obj("skeleton") ?: throw Exception("JSON file is corrupted.") - val skins = json.obj("skins") ?: throw Exception("JSON file doesn't contain skins.") - defaultSkin = skins.obj("default") ?: throw Exception("JSON file doesn't have a default skin.") + skeleton = json.obj("skeleton") ?: throw EndUserException("JSON file is corrupted.") + val skins = json.obj("skins") ?: throw EndUserException("JSON file doesn't contain skins.") + defaultSkin = skins.obj("default") ?: throw EndUserException("JSON file doesn't have a default skin.") validateProperties() } @@ -34,12 +34,12 @@ class SpineJson(val filePath: Path) { val imagesDirectoryPath: Path get() { val relativeImagesDirectory = skeleton.string("images") - ?: throw Exception("JSON file is incomplete: Images path is missing." + ?: throw EndUserException("JSON file is incomplete: Images path is missing." + "Make sure to check 'Nonessential data' when exporting.") val imagesDirectoryPath = fileDirectoryPath.resolve(relativeImagesDirectory).normalize() if (!Files.exists(imagesDirectoryPath)) { - throw Exception("Could not find images directory relative to the JSON file." + throw EndUserException("Could not find images directory relative to the JSON file." + " Make sure the JSON file is in the same directory as the original Spine file.") } @@ -48,12 +48,12 @@ class SpineJson(val filePath: Path) { val audioDirectoryPath: Path get() { val relativeAudioDirectory = skeleton.string("audio") - ?: throw Exception("JSON file is incomplete: Audio path is missing." + ?: throw EndUserException("JSON file is incomplete: Audio path is missing." + "Make sure to check 'Nonessential data' when exporting.") val audioDirectoryPath = fileDirectoryPath.resolve(relativeAudioDirectory).normalize() if (!Files.exists(audioDirectoryPath)) { - throw Exception("Could not find audio directory relative to the JSON file." + throw EndUserException("Could not find audio directory relative to the JSON file." + " Make sure the JSON file is in the same directory as the original Spine file.") } @@ -80,7 +80,7 @@ class SpineJson(val filePath: Path) { val events = json.obj("events") ?: JsonObject() val result = mutableListOf() for ((name, value) in events) { - if (value !is JsonObject) throw Exception("Invalid event found.") + if (value !is JsonObject) throw EndUserException("Invalid event found.") val relativeAudioFilePath = value.string("audio") ?: continue diff --git a/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/tools.kt b/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/tools.kt index c5811c0..b8cacbd 100644 --- a/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/tools.kt +++ b/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/tools.kt @@ -2,11 +2,10 @@ package com.rhubarb_lip_sync.rhubarb_for_spine import javafx.application.Platform import javafx.beans.property.Property -import java.util.concurrent.ExecutionException -import java.util.concurrent.locks.Condition import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock - +import java.io.PrintWriter +import java.io.StringWriter val List.commonPrefix: String get() { return if (isEmpty()) "" else this.reduce { result, string -> result.commonPrefixWith(string) } @@ -36,7 +35,6 @@ fun getExceptionMessage(action: () -> Unit): String? { return null } - /** * Invokes a Runnable on the JFX thread and waits until it's finished. * Similar to SwingUtilities.invokeAndWait. @@ -68,4 +66,10 @@ fun runAndWait(action: () -> Unit) { throwable?.let { throw it } } } +} + +fun getStackTrace(e: Exception): String { + val stringWriter = StringWriter() + e.printStackTrace(PrintWriter(stringWriter)) + return stringWriter.toString() } \ No newline at end of file