mirror of
				https://github.com/packwiz/packwiz-installer.git
				synced 2025-11-04 12:34:31 +01:00 
			
		
		
		
	Rework error handling to be more robust
This commit is contained in:
		@@ -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.ui.data.ExceptionDetails
 | 
			
		||||
import link.infra.packwiz.installer.ui.data.IOptionDetails
 | 
			
		||||
import link.infra.packwiz.installer.util.Log
 | 
			
		||||
import okio.Buffer
 | 
			
		||||
import okio.HashingSink
 | 
			
		||||
import okio.buffer
 | 
			
		||||
@@ -125,6 +126,7 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, de
 | 
			
		||||
	fun download(packFolder: String, indexUri: SpaceSafeURI) {
 | 
			
		||||
		if (err != null) return
 | 
			
		||||
 | 
			
		||||
		// TODO: is this necessary if we overwrite?
 | 
			
		||||
		// Ensure it is removed
 | 
			
		||||
		cachedFile?.let {
 | 
			
		||||
			if (!it.optionValue || !correctSide()) {
 | 
			
		||||
@@ -133,8 +135,7 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, de
 | 
			
		||||
				try {
 | 
			
		||||
					Files.deleteIfExists(Paths.get(packFolder, it.cachedLocation))
 | 
			
		||||
				} catch (e: IOException) {
 | 
			
		||||
					// TODO: how much of a problem is this? use log4j/other log library to show warning?
 | 
			
		||||
					e.printStackTrace()
 | 
			
		||||
					Log.warn("Failed to delete file before downloading", e)
 | 
			
		||||
				}
 | 
			
		||||
				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)
 | 
			
		||||
				data.clear()
 | 
			
		||||
			} else {
 | 
			
		||||
				// TODO: no more PRINTLN!!!!!!!!!
 | 
			
		||||
				// TODO: move println to something visible in the error window
 | 
			
		||||
				println("Invalid hash for " + metadata.destURI.toString())
 | 
			
		||||
				println("Calculated: " + fileSource.hash)
 | 
			
		||||
				println("Expected:   $hash")
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ package link.infra.packwiz.installer
 | 
			
		||||
import link.infra.packwiz.installer.metadata.SpaceSafeURI
 | 
			
		||||
import link.infra.packwiz.installer.ui.cli.CLIHandler
 | 
			
		||||
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.Options
 | 
			
		||||
import org.apache.commons.cli.ParseException
 | 
			
		||||
@@ -29,7 +30,7 @@ class Main(args: Array<String>) {
 | 
			
		||||
		val cmd = try {
 | 
			
		||||
			parser.parse(options, args)
 | 
			
		||||
		} catch (e: ParseException) {
 | 
			
		||||
			e.printStackTrace()
 | 
			
		||||
			Log.fatal("Failed to parse command line arguments", e)
 | 
			
		||||
			if (guiEnabled) {
 | 
			
		||||
				EventQueue.invokeAndWait {
 | 
			
		||||
					try {
 | 
			
		||||
@@ -37,7 +38,8 @@ class Main(args: Array<String>) {
 | 
			
		||||
					} catch (ignored: Exception) {
 | 
			
		||||
						// 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)
 | 
			
		||||
@@ -51,33 +53,34 @@ class Main(args: Array<String>) {
 | 
			
		||||
 | 
			
		||||
		val unparsedArgs = cmd.args
 | 
			
		||||
		if (unparsedArgs.size > 1) {
 | 
			
		||||
			ui.handleExceptionAndExit(RuntimeException("Too many arguments specified!"))
 | 
			
		||||
			ui.showErrorAndExit("Too many arguments specified!")
 | 
			
		||||
		} 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()
 | 
			
		||||
 | 
			
		||||
		val uOptions = UpdateManager.Options().apply {
 | 
			
		||||
			side = cmd.getOptionValue("side")?.let((UpdateManager.Options.Side)::from) ?: side
 | 
			
		||||
			packFolder = cmd.getOptionValue("pack-folder") ?: packFolder
 | 
			
		||||
			manifestFile = cmd.getOptionValue("meta-file") ?: manifestFile
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		try {
 | 
			
		||||
			uOptions.downloadURI = SpaceSafeURI(unparsedArgs[0])
 | 
			
		||||
		val uOptions = try {
 | 
			
		||||
			UpdateManager.Options.construct(
 | 
			
		||||
				downloadURI = SpaceSafeURI(unparsedArgs[0]),
 | 
			
		||||
				side = cmd.getOptionValue("side")?.let((UpdateManager.Options.Side)::from),
 | 
			
		||||
				packFolder = cmd.getOptionValue("pack-folder"),
 | 
			
		||||
				manifestFile = cmd.getOptionValue("meta-file")
 | 
			
		||||
			)
 | 
			
		||||
		} catch (e: URISyntaxException) {
 | 
			
		||||
			// TODO: better error message?
 | 
			
		||||
			ui.handleExceptionAndExit(e)
 | 
			
		||||
			ui.showErrorAndExit("Failed to read pack.toml URI", e)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Start update process!
 | 
			
		||||
		try {
 | 
			
		||||
			UpdateManager(uOptions, ui)
 | 
			
		||||
		} catch (e: Exception) { // TODO: better error message?
 | 
			
		||||
			ui.handleExceptionAndExit(e)
 | 
			
		||||
		} catch (e: Exception) {
 | 
			
		||||
			ui.showErrorAndExit("Update process failed", e)
 | 
			
		||||
		}
 | 
			
		||||
		println("Finished successfully!")
 | 
			
		||||
		ui.dispose()
 | 
			
		||||
@@ -111,17 +114,17 @@ class Main(args: Array<String>) {
 | 
			
		||||
		try {
 | 
			
		||||
			startup(args)
 | 
			
		||||
		} catch (e: Exception) {
 | 
			
		||||
			e.printStackTrace()
 | 
			
		||||
			Log.fatal("Error from main", e)
 | 
			
		||||
			if (guiEnabled) {
 | 
			
		||||
				EventQueue.invokeLater {
 | 
			
		||||
					JOptionPane.showMessageDialog(null,
 | 
			
		||||
						"A fatal error occurred: \n" + e.javaClass.canonicalName + ": " + e.message,
 | 
			
		||||
						"A fatal error occurred: \n$e",
 | 
			
		||||
						"packwiz-installer", JOptionPane.ERROR_MESSAGE)
 | 
			
		||||
					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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -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.ExceptionListResult
 | 
			
		||||
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 java.io.FileNotFoundException
 | 
			
		||||
import java.io.FileReader
 | 
			
		||||
@@ -43,11 +46,17 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	data class Options(
 | 
			
		||||
			var downloadURI: SpaceSafeURI? = null,
 | 
			
		||||
			var manifestFile: String = "packwiz.json", // TODO: make configurable
 | 
			
		||||
			var packFolder: String = ".",
 | 
			
		||||
			var side: Side = Side.CLIENT
 | 
			
		||||
		val downloadURI: SpaceSafeURI,
 | 
			
		||||
		val manifestFile: String,
 | 
			
		||||
		val packFolder: String,
 | 
			
		||||
		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 {
 | 
			
		||||
			@SerializedName("client")
 | 
			
		||||
			CLIENT("client"),
 | 
			
		||||
@@ -111,11 +120,9 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
 | 
			
		||||
		} catch (e: FileNotFoundException) {
 | 
			
		||||
			ManifestFile()
 | 
			
		||||
		} catch (e: JsonSyntaxException) {
 | 
			
		||||
			ui.handleExceptionAndExit(e)
 | 
			
		||||
			return
 | 
			
		||||
			ui.showErrorAndExit("Invalid local manifest file, try deleting ${opts.manifestFile}", e)
 | 
			
		||||
		} catch (e: JsonIOException) {
 | 
			
		||||
			ui.handleExceptionAndExit(e)
 | 
			
		||||
			return
 | 
			
		||||
			ui.showErrorAndExit("Failed to read local manifest file, try deleting ${opts.manifestFile}", e)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (ui.cancelButtonPressed) {
 | 
			
		||||
@@ -125,19 +132,16 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
 | 
			
		||||
 | 
			
		||||
		ui.submitProgress(InstallProgress("Loading pack file..."))
 | 
			
		||||
		val packFileSource = try {
 | 
			
		||||
			val src = getFileSource(opts.downloadURI!!)
 | 
			
		||||
			val src = getFileSource(opts.downloadURI)
 | 
			
		||||
			getHasher("sha256").getHashingSource(src)
 | 
			
		||||
		} catch (e: Exception) {
 | 
			
		||||
			// TODO: run cancellation window?
 | 
			
		||||
			ui.handleExceptionAndExit(e)
 | 
			
		||||
			return
 | 
			
		||||
			ui.showErrorAndExit("Failed to download pack.toml", e)
 | 
			
		||||
		}
 | 
			
		||||
		val pf = packFileSource.buffer().use {
 | 
			
		||||
			try {
 | 
			
		||||
				Toml().read(it.inputStream()).to(PackFile::class.java)
 | 
			
		||||
			} catch (e: IllegalStateException) {
 | 
			
		||||
				ui.handleExceptionAndExit(e)
 | 
			
		||||
				return
 | 
			
		||||
				ui.showErrorAndExit("Failed to parse pack.toml", e)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -169,40 +173,41 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if (invalid) {
 | 
			
		||||
				println("File $fileUri invalidated, marked for redownloading")
 | 
			
		||||
				Log.info("File $fileUri invalidated, marked for redownloading")
 | 
			
		||||
				invalidatedUris.add(fileUri)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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?
 | 
			
		||||
			if (!ui.optionsButtonPressed) {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		println("Modpack name: " + pf.name)
 | 
			
		||||
		Log.info("Modpack name: ${pf.name}")
 | 
			
		||||
 | 
			
		||||
		if (ui.cancelButtonPressed) {
 | 
			
		||||
			showCancellationDialog()
 | 
			
		||||
			handleCancellation()
 | 
			
		||||
		}
 | 
			
		||||
		try {
 | 
			
		||||
			val index = pf.index!!
 | 
			
		||||
			getNewLoc(opts.downloadURI, index.file)?.let { newLoc ->
 | 
			
		||||
				index.hashFormat?.let { hashFormat ->
 | 
			
		||||
					processIndex(
 | 
			
		||||
						newLoc,
 | 
			
		||||
						getHash(index.hashFormat!!, index.hash!!),
 | 
			
		||||
						hashFormat,
 | 
			
		||||
						manifest,
 | 
			
		||||
						invalidatedUris
 | 
			
		||||
					)
 | 
			
		||||
			ifletOrWarn(pf.index, "No index file found") { index ->
 | 
			
		||||
				ui.ifletOrErr(index.hashFormat, index.hash, "Pack has no hash or hashFormat for index") { hashFormat, hash ->
 | 
			
		||||
					ui.ifletOrErr(getNewLoc(opts.downloadURI, index.file), "Pack has invalid index file: " + index.file) { newLoc ->
 | 
			
		||||
						processIndex(
 | 
			
		||||
							newLoc,
 | 
			
		||||
							getHash(hashFormat, hash),
 | 
			
		||||
							hashFormat,
 | 
			
		||||
							manifest,
 | 
			
		||||
							invalidatedUris
 | 
			
		||||
						)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} catch (e1: Exception) {
 | 
			
		||||
			ui.handleExceptionAndExit(e1)
 | 
			
		||||
			ui.showErrorAndExit("Failed to process index file", e1)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		handleCancellation()
 | 
			
		||||
@@ -221,8 +226,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
 | 
			
		||||
		try {
 | 
			
		||||
			FileWriter(Paths.get(opts.packFolder, opts.manifestFile).toString()).use { writer -> gson.toJson(manifest, writer) }
 | 
			
		||||
		} catch (e: IOException) {
 | 
			
		||||
			// TODO: add message?
 | 
			
		||||
			ui.handleException(e)
 | 
			
		||||
			ui.showErrorAndExit("Failed to save local manifest file", 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>) {
 | 
			
		||||
		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) {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
@@ -243,20 +247,18 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
 | 
			
		||||
			val src = getFileSource(indexUri)
 | 
			
		||||
			getHasher(hashFormat).getHashingSource(src)
 | 
			
		||||
		} catch (e: Exception) {
 | 
			
		||||
			// TODO: run cancellation window?
 | 
			
		||||
			ui.handleExceptionAndExit(e)
 | 
			
		||||
			return
 | 
			
		||||
			ui.showErrorAndExit("Failed to download index file", e)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		val indexFile = try {
 | 
			
		||||
			Toml().read(indexFileSource.buffer().inputStream()).to(IndexFile::class.java)
 | 
			
		||||
		} catch (e: IllegalStateException) {
 | 
			
		||||
			ui.handleExceptionAndExit(e)
 | 
			
		||||
			return
 | 
			
		||||
			ui.showErrorAndExit("Failed to parse index file", e)
 | 
			
		||||
		}
 | 
			
		||||
		if (!indexFileSource.hashIsEqual(indexHash)) {
 | 
			
		||||
			ui.handleExceptionAndExit(RuntimeException("Your index hash is invalid! Please run packwiz refresh on the pack again"))
 | 
			
		||||
			return
 | 
			
		||||
			ui.showErrorAndExit("Your index file hash is invalid! The pack developer should packwiz refresh on the pack again")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (ui.cancelButtonPressed) {
 | 
			
		||||
			showCancellationDialog()
 | 
			
		||||
			return
 | 
			
		||||
@@ -274,8 +276,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
 | 
			
		||||
					try {
 | 
			
		||||
						Files.deleteIfExists(Paths.get(opts.packFolder, file.cachedLocation))
 | 
			
		||||
					} catch (e: IOException) {
 | 
			
		||||
						// TODO: should this be shown to the user in some way?
 | 
			
		||||
						e.printStackTrace()
 | 
			
		||||
						Log.warn("Failed to delete optional disabled file", e)
 | 
			
		||||
					}
 | 
			
		||||
					// Set to null, as it doesn't exist anymore
 | 
			
		||||
					file.cachedLocation = null
 | 
			
		||||
@@ -285,8 +286,8 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
 | 
			
		||||
					if (!alreadyDeleted) {
 | 
			
		||||
						try {
 | 
			
		||||
							Files.deleteIfExists(Paths.get(opts.packFolder, file.cachedLocation))
 | 
			
		||||
						} catch (e: IOException) { // TODO: should this be shown to the user in some way?
 | 
			
		||||
							e.printStackTrace()
 | 
			
		||||
						} catch (e: IOException) {
 | 
			
		||||
							Log.warn("Failed to delete file removed from index", e)
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					it.remove()
 | 
			
		||||
@@ -302,14 +303,14 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
 | 
			
		||||
 | 
			
		||||
		// TODO: progress bar?
 | 
			
		||||
		if (indexFile.files.isEmpty()) {
 | 
			
		||||
			println("Warning: Index is empty!")
 | 
			
		||||
			Log.warn("Index is empty!")
 | 
			
		||||
		}
 | 
			
		||||
		val tasks = createTasksFromIndex(indexFile, indexFile.hashFormat, opts.side)
 | 
			
		||||
		// If the side changes, invalidate EVERYTHING just in case
 | 
			
		||||
		// Might not be needed, but done just to be safe
 | 
			
		||||
		val invalidateAll = opts.side != manifest.cachedSide
 | 
			
		||||
		if (invalidateAll) {
 | 
			
		||||
			println("Side changed, invalidating all mods")
 | 
			
		||||
			Log.info("Side changed, invalidating all mods")
 | 
			
		||||
		}
 | 
			
		||||
		tasks.forEach{ f ->
 | 
			
		||||
			// 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) {
 | 
			
		||||
			var task: DownloadTask?
 | 
			
		||||
			task = try {
 | 
			
		||||
			val task: DownloadTask = try {
 | 
			
		||||
				completionService.take().get()
 | 
			
		||||
			} catch (e: InterruptedException) {
 | 
			
		||||
				ui.handleException(e)
 | 
			
		||||
				null
 | 
			
		||||
				ui.showErrorAndExit("Interrupted when consuming download tasks", e)
 | 
			
		||||
			} catch (e: ExecutionException) {
 | 
			
		||||
				ui.handleException(e)
 | 
			
		||||
				null
 | 
			
		||||
				ui.showErrorAndExit("Failed to execute download task", e)
 | 
			
		||||
			}
 | 
			
		||||
			// 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()) {
 | 
			
		||||
					val oldFile = file.revert
 | 
			
		||||
					if (oldFile != null) {
 | 
			
		||||
@@ -399,17 +397,11 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var progress: String
 | 
			
		||||
			if (task != null) {
 | 
			
		||||
				val exDetails = task.exceptionDetails
 | 
			
		||||
				if (exDetails != null) {
 | 
			
		||||
					progress = "Failed to download ${exDetails.name}: ${exDetails.exception.message}"
 | 
			
		||||
					exDetails.exception.printStackTrace()
 | 
			
		||||
				} else {
 | 
			
		||||
					progress = "Downloaded ${task.name}"
 | 
			
		||||
				}
 | 
			
		||||
			val exDetails = task.exceptionDetails
 | 
			
		||||
			val progress = if (exDetails != null) {
 | 
			
		||||
				"Failed to download ${exDetails.name}: ${exDetails.exception.message}"
 | 
			
		||||
			} else {
 | 
			
		||||
				progress = "Failed to download, unknown reason"
 | 
			
		||||
				"Downloaded ${task.name}"
 | 
			
		||||
			}
 | 
			
		||||
			ui.submitProgress(InstallProgress(progress, i + 1, tasks.size))
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ object HandlerManager {
 | 
			
		||||
			RequestHandlerFile()
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	 // TODO: get rid of nullable stuff here
 | 
			
		||||
	@JvmStatic
 | 
			
		||||
	fun getNewLoc(base: SpaceSafeURI?, loc: SpaceSafeURI?): SpaceSafeURI? {
 | 
			
		||||
		if (loc == null) {
 | 
			
		||||
@@ -32,6 +33,8 @@ object HandlerManager {
 | 
			
		||||
	// Zip handler discards once read, requesting multiple times on other handlers would cause multiple downloads
 | 
			
		||||
	// Caching system? Copy from already downloaded files?
 | 
			
		||||
 | 
			
		||||
	// TODO: change to use something more idiomatic than exceptions?
 | 
			
		||||
 | 
			
		||||
	@JvmStatic
 | 
			
		||||
	@Throws(Exception::class)
 | 
			
		||||
	fun getFileSource(loc: SpaceSafeURI): Source {
 | 
			
		||||
 
 | 
			
		||||
@@ -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.IOptionDetails
 | 
			
		||||
import link.infra.packwiz.installer.ui.data.InstallProgress
 | 
			
		||||
import kotlin.system.exitProcess
 | 
			
		||||
 | 
			
		||||
interface IUserInterface {
 | 
			
		||||
	fun show()
 | 
			
		||||
	fun dispose()
 | 
			
		||||
	fun handleException(e: Exception)
 | 
			
		||||
	fun handleExceptionAndExit(e: Exception) {
 | 
			
		||||
		handleException(e)
 | 
			
		||||
		exitProcess(1)
 | 
			
		||||
 | 
			
		||||
	fun showErrorAndExit(message: String): Nothing {
 | 
			
		||||
		showErrorAndExit(message, null)
 | 
			
		||||
	}
 | 
			
		||||
	fun showErrorAndExit(message: String, e: Exception?): Nothing
 | 
			
		||||
 | 
			
		||||
	fun setTitle(title: String) {}
 | 
			
		||||
	fun submitProgress(progress: InstallProgress)
 | 
			
		||||
 
 | 
			
		||||
@@ -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.IOptionDetails
 | 
			
		||||
import link.infra.packwiz.installer.ui.data.InstallProgress
 | 
			
		||||
import link.infra.packwiz.installer.util.Log
 | 
			
		||||
import kotlin.system.exitProcess
 | 
			
		||||
 | 
			
		||||
class CLIHandler : IUserInterface {
 | 
			
		||||
@@ -13,8 +14,13 @@ class CLIHandler : IUserInterface {
 | 
			
		||||
	@Volatile
 | 
			
		||||
	override var cancelButtonPressed = false
 | 
			
		||||
 | 
			
		||||
	override fun handleException(e: Exception) {
 | 
			
		||||
		e.printStackTrace()
 | 
			
		||||
	override fun showErrorAndExit(message: String, e: Exception?): Nothing {
 | 
			
		||||
		if (e != null) {
 | 
			
		||||
			Log.fatal(message, e)
 | 
			
		||||
		} else {
 | 
			
		||||
			Log.fatal(message)
 | 
			
		||||
		}
 | 
			
		||||
		exitProcess(1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override fun show() {}
 | 
			
		||||
@@ -36,7 +42,7 @@ class CLIHandler : IUserInterface {
 | 
			
		||||
		for (opt in options) {
 | 
			
		||||
			opt.optionValue = true
 | 
			
		||||
			// 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!
 | 
			
		||||
	}
 | 
			
		||||
@@ -44,9 +50,9 @@ class CLIHandler : IUserInterface {
 | 
			
		||||
	override fun showExceptions(exceptions: List<ExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): ExceptionListResult {
 | 
			
		||||
		println("Failed to download modpack, the following errors were encountered:")
 | 
			
		||||
		for (ex in exceptions) {
 | 
			
		||||
			println(ex.name + ": ")
 | 
			
		||||
			print(ex.name + ": ")
 | 
			
		||||
			ex.exception.printStackTrace()
 | 
			
		||||
		}
 | 
			
		||||
		exitProcess(1)
 | 
			
		||||
		return ExceptionListResult.CANCEL
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -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.IOptionDetails
 | 
			
		||||
import link.infra.packwiz.installer.ui.data.InstallProgress
 | 
			
		||||
import link.infra.packwiz.installer.util.Log
 | 
			
		||||
import java.awt.EventQueue
 | 
			
		||||
import java.util.concurrent.CompletableFuture
 | 
			
		||||
import javax.swing.JDialog
 | 
			
		||||
@@ -27,8 +28,7 @@ class GUIHandler : IUserInterface {
 | 
			
		||||
			try {
 | 
			
		||||
				UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
 | 
			
		||||
			} catch (e: Exception) {
 | 
			
		||||
				println("Failed to set look and feel:")
 | 
			
		||||
				e.printStackTrace()
 | 
			
		||||
				Log.warn("Failed to set look and feel", e)
 | 
			
		||||
			}
 | 
			
		||||
			frmPackwizlauncher = InstallWindow(this).apply {
 | 
			
		||||
				title = this@GUIHandler.title
 | 
			
		||||
@@ -44,23 +44,23 @@ class GUIHandler : IUserInterface {
 | 
			
		||||
		frmPackwizlauncher.dispose()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override fun handleException(e: Exception) {
 | 
			
		||||
		e.printStackTrace()
 | 
			
		||||
		EventQueue.invokeAndWait {
 | 
			
		||||
			JOptionPane.showMessageDialog(null,
 | 
			
		||||
					"An error occurred: \n" + e.javaClass.canonicalName + ": " + e.message,
 | 
			
		||||
	override fun showErrorAndExit(message: String, e: Exception?): Nothing {
 | 
			
		||||
		if (e != null) {
 | 
			
		||||
			Log.fatal(message, e)
 | 
			
		||||
			EventQueue.invokeAndWait {
 | 
			
		||||
				JOptionPane.showMessageDialog(null,
 | 
			
		||||
					"$message: $e",
 | 
			
		||||
					title, JOptionPane.ERROR_MESSAGE)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override fun handleExceptionAndExit(e: Exception) {
 | 
			
		||||
		e.printStackTrace()
 | 
			
		||||
		EventQueue.invokeAndWait {
 | 
			
		||||
			JOptionPane.showMessageDialog(null,
 | 
			
		||||
					"A fatal error occurred: \n" + e.javaClass.canonicalName + ": " + e.message,
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			Log.fatal(message)
 | 
			
		||||
			EventQueue.invokeAndWait {
 | 
			
		||||
				JOptionPane.showMessageDialog(null,
 | 
			
		||||
					message,
 | 
			
		||||
					title, JOptionPane.ERROR_MESSAGE)
 | 
			
		||||
			exitProcess(1)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		exitProcess(1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override fun setTitle(title: String) {
 | 
			
		||||
@@ -78,8 +78,7 @@ class GUIHandler : IUserInterface {
 | 
			
		||||
			sb.append(") ")
 | 
			
		||||
		}
 | 
			
		||||
		sb.append(progress.message)
 | 
			
		||||
		// TODO: better logging library?
 | 
			
		||||
		println(sb.toString())
 | 
			
		||||
		Log.info(sb.toString())
 | 
			
		||||
		EventQueue.invokeLater {
 | 
			
		||||
			frmPackwizlauncher.displayProgress(progress)
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								src/main/kotlin/link/infra/packwiz/installer/util/Exts.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/main/kotlin/link/infra/packwiz/installer/util/Exts.kt
									
									
									
									
									
										Normal 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()
 | 
			
		||||
	}
 | 
			
		||||
							
								
								
									
										16
									
								
								src/main/kotlin/link/infra/packwiz/installer/util/Log.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/main/kotlin/link/infra/packwiz/installer/util/Log.kt
									
									
									
									
									
										Normal 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()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user