From a15489f5e4f2f163192abc2fe8a0c4c70011b112 Mon Sep 17 00:00:00 2001 From: comp500 Date: Sat, 21 Dec 2019 02:04:10 +0000 Subject: [PATCH] Complete Kotlin port --- .../infra/packwiz/installer/DownloadTask.kt | 355 +++++----- .../infra/packwiz/installer/UpdateManager.kt | 670 +++++++++--------- .../packwiz/installer/metadata/IndexFile.kt | 5 +- .../infra/packwiz/installer/ui/CLIHandler.kt | 4 +- .../packwiz/installer/ui/ExceptionDetails.kt | 6 + .../installer/ui/ExceptionListWindow.kt | 15 +- .../packwiz/installer/ui/IExceptionDetails.kt | 10 - .../packwiz/installer/ui/IUserInterface.kt | 19 +- .../packwiz/installer/ui/InstallWindow.kt | 10 +- 9 files changed, 537 insertions(+), 557 deletions(-) create mode 100644 src/main/kotlin/link/infra/packwiz/installer/ui/ExceptionDetails.kt delete mode 100644 src/main/kotlin/link/infra/packwiz/installer/ui/IExceptionDetails.kt diff --git a/src/main/kotlin/link/infra/packwiz/installer/DownloadTask.kt b/src/main/kotlin/link/infra/packwiz/installer/DownloadTask.kt index 0771379..40cc190 100644 --- a/src/main/kotlin/link/infra/packwiz/installer/DownloadTask.kt +++ b/src/main/kotlin/link/infra/packwiz/installer/DownloadTask.kt @@ -1,251 +1,230 @@ -package link.infra.packwiz.installer; +package link.infra.packwiz.installer -import link.infra.packwiz.installer.metadata.IndexFile; -import link.infra.packwiz.installer.metadata.ManifestFile; -import link.infra.packwiz.installer.metadata.ModFile; -import link.infra.packwiz.installer.metadata.SpaceSafeURI; -import link.infra.packwiz.installer.metadata.hash.GeneralHashingSource; -import link.infra.packwiz.installer.metadata.hash.Hash; -import link.infra.packwiz.installer.metadata.hash.HashUtils; -import link.infra.packwiz.installer.ui.IExceptionDetails; -import link.infra.packwiz.installer.ui.IOptionDetails; -import okio.Buffer; -import okio.Okio; -import okio.Source; +import link.infra.packwiz.installer.metadata.IndexFile +import link.infra.packwiz.installer.metadata.ManifestFile +import link.infra.packwiz.installer.metadata.SpaceSafeURI +import link.infra.packwiz.installer.metadata.hash.Hash +import link.infra.packwiz.installer.metadata.hash.HashUtils.getHash +import link.infra.packwiz.installer.metadata.hash.HashUtils.getHasher +import link.infra.packwiz.installer.ui.ExceptionDetails +import link.infra.packwiz.installer.ui.IOptionDetails +import okio.Buffer +import okio.buffer +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.StandardCopyOption +import java.util.* -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; +internal class DownloadTask private constructor(val metadata: IndexFile.File, defaultFormat: String, private val downloadSide: UpdateManager.Options.Side) : IOptionDetails { + var cachedFile: ManifestFile.File? = null -class DownloadTask implements IOptionDetails, IExceptionDetails { - final IndexFile.File metadata; - ManifestFile.File cachedFile = null; - private Exception failure = null; - private boolean alreadyUpToDate = false; - private boolean metadataRequired = true; - private boolean invalidated = false; + private var err: Exception? = null + val exceptionDetails get() = err?.let { e -> ExceptionDetails(name, e) } + + fun failed() = err != null + + private var alreadyUpToDate = false + private var metadataRequired = true + private var invalidated = false // If file is new or isOptional changed to true, the option needs to be presented again - private boolean newOptional = true; - private final UpdateManager.Options.Side downloadSide; + private var newOptional = true - private DownloadTask(IndexFile.File metadata, String defaultFormat, UpdateManager.Options.Side downloadSide) { - this.metadata = metadata; - if (metadata.getHashFormat() == null || metadata.getHashFormat().length() == 0) { - metadata.setHashFormat(defaultFormat); + val isOptional get() = metadata.linkedFile?.isOptional ?: false + + fun isNewOptional() = isOptional && newOptional + + fun correctSide() = metadata.linkedFile?.side?.hasSide(downloadSide) ?: true + + override val name get() = metadata.name + + // Ensure that an update is done if it changes from false to true, or from true to false + override var optionValue: Boolean + get() = cachedFile?.optionValue ?: true + set(value) { + if (value && !optionValue) { // Ensure that an update is done if it changes from false to true, or from true to false + alreadyUpToDate = false + } + cachedFile?.optionValue = value + } + + override val optionDescription get() = metadata.linkedFile?.option?.description ?: "" + + init { + if (metadata.hashFormat?.isEmpty() != false) { + metadata.hashFormat = defaultFormat } - this.downloadSide = downloadSide; } - void invalidate() { - invalidated = true; - alreadyUpToDate = false; + fun invalidate() { + invalidated = true + alreadyUpToDate = false } - void updateFromCache(ManifestFile.File cachedFile) { - if (failure != null) return; + fun updateFromCache(cachedFile: ManifestFile.File?) { + if (err != null) return + if (cachedFile == null) { - this.cachedFile = new ManifestFile.File(); - return; + this.cachedFile = ManifestFile.File() + return } - - this.cachedFile = cachedFile; - + this.cachedFile = cachedFile if (!invalidated) { - Hash currHash; - try { - currHash = HashUtils.getHash(Objects.requireNonNull(metadata.getHashFormat()), Objects.requireNonNull(metadata.getHash())); - } catch (Exception e) { - failure = e; - return; + val currHash = try { + getHash(metadata.hashFormat!!, metadata.hash!!) + } catch (e: Exception) { + err = e + return } - if (currHash.equals(cachedFile.getHash())) { - // Already up to date - alreadyUpToDate = true; - metadataRequired = false; + if (currHash == cachedFile.hash) { // Already up to date + alreadyUpToDate = true + metadataRequired = false } } - if (cachedFile.isOptional()) { + if (cachedFile.isOptional) { // Because option selection dialog might set this task to true/false, metadata is always needed to download // the file, and to show the description and name - metadataRequired = true; + metadataRequired = true } } - void downloadMetadata(IndexFile parentIndexFile, SpaceSafeURI indexUri) { - if (failure != null) return; + fun downloadMetadata(parentIndexFile: IndexFile, indexUri: SpaceSafeURI) { + if (err != null) return + if (metadataRequired) { try { - metadata.downloadMeta(parentIndexFile, indexUri); - } catch (Exception e) { - failure = e; - return; + // Retrieve the linked metadata file + metadata.downloadMeta(parentIndexFile, indexUri) + } catch (e: Exception) { + err = e + return } - ModFile linkedFile = metadata.getLinkedFile(); - if (linkedFile != null) { - ModFile.Option option = linkedFile.getOption(); - if (option != null) { - if (option.getOptional()) { - if (cachedFile.isOptional()) { - // isOptional didn't change - newOptional = false; - } else { - // isOptional false -> true, set option to it's default value - // TODO: preserve previous option value, somehow?? - cachedFile.setOptionValue(option.getDefaultValue()); + cachedFile?.let { cachedFile -> + val linkedFile = metadata.linkedFile + if (linkedFile != null) { + linkedFile.option?.let { opt -> + if (opt.optional) { + if (cachedFile.isOptional) { + // isOptional didn't change + newOptional = false + } else { + // isOptional false -> true, set option to it's default value + // TODO: preserve previous option value, somehow?? + cachedFile.optionValue = opt.defaultValue + } } } + cachedFile.isOptional = isOptional + cachedFile.onlyOtherSide = !correctSide() } - cachedFile.setOptional(isOptional()); - cachedFile.setOnlyOtherSide(!correctSide()); } } } - void download(String packFolder, SpaceSafeURI indexUri) { - if (failure != null) return; + fun download(packFolder: String, indexUri: SpaceSafeURI) { + if (err != null) return // Ensure it is removed - if (!cachedFile.getOptionValue() || !correctSide()) { - if (cachedFile.getCachedLocation() == null) return; - try { - Files.deleteIfExists(Paths.get(packFolder, cachedFile.getCachedLocation())); - } catch (IOException e) { - // TODO: how much of a problem is this? use log4j/other log library to show warning? - e.printStackTrace(); + cachedFile?.let { + if (!it.optionValue || !correctSide()) { + if (it.cachedLocation == null) return + + 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() + } + it.cachedLocation = null } - cachedFile.setCachedLocation(null); - return; } + if (alreadyUpToDate) return - if (alreadyUpToDate) return; - - Path destPath = Paths.get(packFolder, Objects.requireNonNull(metadata.getDestURI()).toString()); + // TODO: should I be validating JSON properly, or this fine!!!!!!!?? + assert(metadata.destURI != null) + val destPath = Paths.get(packFolder, metadata.destURI.toString()) // Don't update files marked with preserve if they already exist on disk - if (metadata.getPreserve()) { + if (metadata.preserve) { if (destPath.toFile().exists()) { - return; + return } } try { - Hash hash; - String fileHashFormat; - ModFile linkedFile = metadata.getLinkedFile(); + val hash: Hash + val fileHashFormat: String + val linkedFile = metadata.linkedFile + if (linkedFile != null) { - hash = linkedFile.getHash(); - fileHashFormat = Objects.requireNonNull(linkedFile.getDownload()).getHashFormat(); + hash = linkedFile.hash + fileHashFormat = Objects.requireNonNull(Objects.requireNonNull(linkedFile.download)!!.hashFormat)!! } else { - hash = metadata.getHashObj(); - fileHashFormat = metadata.getHashFormat(); + hash = metadata.getHashObj() + fileHashFormat = Objects.requireNonNull(metadata.hashFormat)!! } - Source src = metadata.getSource(indexUri); - assert fileHashFormat != null; - GeneralHashingSource fileSource = HashUtils.getHasher(fileHashFormat).getHashingSource(src); - Buffer data = new Buffer(); - Okio.buffer(fileSource).readAll(data); + val src = metadata.getSource(indexUri) + val fileSource = getHasher(fileHashFormat).getHashingSource(src) + val data = Buffer() + + // Read all the data into a buffer (very inefficient for large files! but allows rollback if hash check fails) + // TODO: should we instead rename the existing file, then stream straight to the file and rollback from the renamed file? + fileSource.buffer().use { + it.readAll(data) + } if (fileSource.hashIsEqual(hash)) { - Files.createDirectories(destPath.getParent()); - Files.copy(data.inputStream(), destPath, StandardCopyOption.REPLACE_EXISTING); + Files.createDirectories(destPath.parent) + Files.copy(data.inputStream(), destPath, StandardCopyOption.REPLACE_EXISTING) + data.clear() } else { - // TODO: no more SYSOUT!!!!!!!!! - System.out.println("Invalid hash for " + metadata.getDestURI().toString()); - System.out.println("Calculated: " + fileSource.getHash()); - System.out.println("Expected: " + hash); - failure = new Exception("Hash invalid!"); + // TODO: no more PRINTLN!!!!!!!!! + println("Invalid hash for " + metadata.destURI.toString()) + println("Calculated: " + fileSource.hash) + println("Expected: $hash") + err = Exception("Hash invalid!") } - - if (cachedFile.getCachedLocation() != null && !destPath.equals(Paths.get(packFolder, cachedFile.getCachedLocation()))) { - // Delete old file if location changes - Files.delete(Paths.get(packFolder, cachedFile.getCachedLocation())); + cachedFile?.cachedLocation?.let { + if (destPath != Paths.get(packFolder, it)) { + // Delete old file if location changes + Files.delete(Paths.get(packFolder, cachedFile!!.cachedLocation)) + } } - } catch (Exception e) { - failure = e; + } catch (e: Exception) { + err = e } - if (failure == null) { - if (cachedFile == null) { - cachedFile = new ManifestFile.File(); - } + + if (err == null) { // Update the manifest file - try { - cachedFile.setHash(metadata.getHashObj()); - } catch (Exception e) { - failure = e; - return; - } - cachedFile.setOptional(isOptional()); - cachedFile.setCachedLocation(metadata.getDestURI().toString()); - if (metadata.getLinkedFile() != null) { + cachedFile = (cachedFile ?: ManifestFile.File()).also { try { - cachedFile.setLinkedFileHash(metadata.getLinkedFile().getHash()); - } catch (Exception e) { - failure = e; + it.hash = metadata.getHashObj() + } catch (e: Exception) { + err = e + return + } + it.isOptional = isOptional + it.cachedLocation = metadata.destURI.toString() + metadata.linkedFile?.let { linked -> + try { + it.linkedFileHash = linked.hash + } catch (e: Exception) { + err = e + } } } } } - public Exception getException() { - return failure; - } - - boolean isOptional() { - if (metadata.getLinkedFile() != null) { - return metadata.getLinkedFile().isOptional(); + companion object { + @JvmStatic + fun createTasksFromIndex(index: IndexFile, defaultFormat: String, downloadSide: UpdateManager.Options.Side): List { + val tasks = ArrayList() + for (file in Objects.requireNonNull(index.files)) { + tasks.add(DownloadTask(file, defaultFormat, downloadSide)) + } + return tasks } - return false; } - - boolean isNewOptional() { - return isOptional() && this.newOptional; - } - - boolean correctSide() { - if (metadata.getLinkedFile() != null && metadata.getLinkedFile().getSide() != null) { - return metadata.getLinkedFile().getSide().hasSide(downloadSide); - } - return true; - } - - public String getName() { - return metadata.getName(); - } - - @Override - public boolean getOptionValue() { - return cachedFile.getOptionValue(); - } - - @Override - public String getOptionDescription() { - if (metadata.getLinkedFile() != null && metadata.getLinkedFile().getOption() != null) { - return metadata.getLinkedFile().getOption().getDescription(); - } - return null; - } - - public void setOptionValue(boolean value) { - if (value && !cachedFile.getOptionValue()) { - // Ensure that an update is done if it changes from false to true, or from true to false - alreadyUpToDate = false; - } - cachedFile.setOptionValue(value); - } - - static List createTasksFromIndex(IndexFile index, String defaultFormat, UpdateManager.Options.Side downloadSide) { - ArrayList tasks = new ArrayList<>(); - for (IndexFile.File file : Objects.requireNonNull(index.getFiles())) { - tasks.add(new DownloadTask(file, defaultFormat, downloadSide)); - } - return tasks; - } - - } \ No newline at end of file diff --git a/src/main/kotlin/link/infra/packwiz/installer/UpdateManager.kt b/src/main/kotlin/link/infra/packwiz/installer/UpdateManager.kt index 7eea84e..ae687c9 100644 --- a/src/main/kotlin/link/infra/packwiz/installer/UpdateManager.kt +++ b/src/main/kotlin/link/infra/packwiz/installer/UpdateManager.kt @@ -1,495 +1,493 @@ -package link.infra.packwiz.installer; +package link.infra.packwiz.installer -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonIOException; -import com.google.gson.JsonSyntaxException; -import com.google.gson.annotations.SerializedName; -import com.moandjiezana.toml.Toml; -import link.infra.packwiz.installer.metadata.IndexFile; -import link.infra.packwiz.installer.metadata.ManifestFile; -import link.infra.packwiz.installer.metadata.PackFile; -import link.infra.packwiz.installer.metadata.SpaceSafeURI; -import link.infra.packwiz.installer.metadata.hash.GeneralHashingSource; -import link.infra.packwiz.installer.metadata.hash.Hash; -import link.infra.packwiz.installer.metadata.hash.HashUtils; -import link.infra.packwiz.installer.request.HandlerManager; -import link.infra.packwiz.installer.ui.IExceptionDetails; -import link.infra.packwiz.installer.ui.IUserInterface; -import link.infra.packwiz.installer.ui.InputStateHandler; -import link.infra.packwiz.installer.ui.InstallProgress; -import okio.Okio; -import okio.Source; +import com.google.gson.GsonBuilder +import com.google.gson.JsonIOException +import com.google.gson.JsonSyntaxException +import com.google.gson.annotations.SerializedName +import com.moandjiezana.toml.Toml +import link.infra.packwiz.installer.DownloadTask.Companion.createTasksFromIndex +import link.infra.packwiz.installer.metadata.IndexFile +import link.infra.packwiz.installer.metadata.ManifestFile +import link.infra.packwiz.installer.metadata.PackFile +import link.infra.packwiz.installer.metadata.SpaceSafeURI +import link.infra.packwiz.installer.metadata.hash.Hash +import link.infra.packwiz.installer.metadata.hash.HashUtils.getHash +import link.infra.packwiz.installer.metadata.hash.HashUtils.getHasher +import link.infra.packwiz.installer.request.HandlerManager.getFileSource +import link.infra.packwiz.installer.request.HandlerManager.getNewLoc +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.InputStateHandler +import link.infra.packwiz.installer.ui.InstallProgress +import okio.buffer +import java.io.FileNotFoundException +import java.io.FileReader +import java.io.FileWriter +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Paths +import java.util.* +import java.util.concurrent.CompletionService +import java.util.concurrent.ExecutionException +import java.util.concurrent.ExecutorCompletionService +import java.util.concurrent.Executors +import kotlin.system.exitProcess -import java.io.*; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.*; -import java.util.concurrent.*; -import java.util.stream.Collectors; +class UpdateManager internal constructor(private val opts: Options, val ui: IUserInterface, private val stateHandler: InputStateHandler) { + private var cancelled = false + private var cancelledStartGame = false + private var errorsOccurred = false -public class UpdateManager { + init { + start() + } - private final Options opts; - public final IUserInterface ui; - private boolean cancelled; - private boolean cancelledStartGame = false; - private InputStateHandler stateHandler; - private boolean errorsOccurred = false; - - public static class Options { - SpaceSafeURI downloadURI = null; - String manifestFile = "packwiz.json"; // TODO: make configurable - String packFolder = "."; - Side side = Side.CLIENT; - - public enum Side { + data class Options( + var downloadURI: SpaceSafeURI? = null, + var manifestFile: String = "packwiz.json", // TODO: make configurable + var packFolder: String = ".", + var side: Side = Side.CLIENT + ) { + enum class Side { @SerializedName("client") CLIENT("client"), @SerializedName("server") SERVER("server"), @SerializedName("both") - BOTH("both", new Side[] { CLIENT, SERVER }); + @Suppress("unused") + BOTH("both", arrayOf(CLIENT, SERVER)); - private final String sideName; - private final Side[] depSides; + private val sideName: String + private val depSides: Array? - Side(String sideName) { - this.sideName = sideName.toLowerCase(); - this.depSides = null; + constructor(sideName: String) { + this.sideName = sideName.toLowerCase() + depSides = null } - Side(String sideName, Side[] depSides) { - this.sideName = sideName.toLowerCase(); - this.depSides = depSides; + constructor(sideName: String, depSides: Array) { + this.sideName = sideName.toLowerCase() + this.depSides = depSides } - @Override - public String toString() { - return this.sideName; - } + override fun toString() = sideName - public boolean hasSide(Side tSide) { - if (this.equals(tSide)) { - return true; + fun hasSide(tSide: Side): Boolean { + if (this == tSide) { + return true } - if (this.depSides != null) { - for (Side depSide : this.depSides) { - if (depSide.equals(tSide)) { - return true; + if (depSides != null) { + for (depSide in depSides) { + if (depSide == tSide) { + return true } } } - return false; + return false } - public static Side from(String name) { - String lowerName = name.toLowerCase(); - for (Side side : Side.values()) { - if (side.sideName.equals(lowerName)) { - return side; + companion object { + fun from(name: String): Side? { + val lowerName = name.toLowerCase() + for (side in values()) { + if (side.sideName == lowerName) { + return side + } } + return null } - return null; } } } - UpdateManager(Options opts, IUserInterface ui, InputStateHandler inputStateHandler) { - this.opts = opts; - this.ui = ui; - this.stateHandler = inputStateHandler; - this.start(); - } + private fun start() { + checkOptions() - private void start() { - this.checkOptions(); - - ui.submitProgress(new InstallProgress("Loading manifest file...")); - Gson gson = new GsonBuilder().registerTypeAdapter(Hash.class, new Hash.TypeHandler()).create(); - ManifestFile manifest; - try { - manifest = gson.fromJson(new FileReader(Paths.get(opts.packFolder, opts.manifestFile).toString()), - ManifestFile.class); - } catch (FileNotFoundException e) { - manifest = new ManifestFile(); - } catch (JsonSyntaxException | JsonIOException e) { - ui.handleExceptionAndExit(e); - return; + ui.submitProgress(InstallProgress("Loading manifest file...")) + val gson = GsonBuilder().registerTypeAdapter(Hash::class.java, Hash.TypeHandler()).create() + val manifest = try { + gson.fromJson(FileReader(Paths.get(opts.packFolder, opts.manifestFile).toString()), + ManifestFile::class.java) + } catch (e: FileNotFoundException) { + ManifestFile() + } catch (e: JsonSyntaxException) { + ui.handleExceptionAndExit(e) + return + } catch (e: JsonIOException) { + ui.handleExceptionAndExit(e) + return } - if (stateHandler.getCancelButton()) { - showCancellationDialog(); - handleCancellation(); + if (stateHandler.cancelButton) { + showCancellationDialog() + handleCancellation() } - ui.submitProgress(new InstallProgress("Loading pack file...")); - GeneralHashingSource packFileSource; - try { - Source src = HandlerManager.getFileSource(opts.downloadURI); - packFileSource = HashUtils.getHasher("sha256").getHashingSource(src); - } catch (Exception e) { - // TODO: still launch the game if updating doesn't work? - // TODO: ask user if they want to launch the game, exit(1) if they don't - ui.handleExceptionAndExit(e); - return; + ui.submitProgress(InstallProgress("Loading pack file...")) + val packFileSource = try { + Objects.requireNonNull(opts.downloadURI) + val src = getFileSource(opts.downloadURI!!) + getHasher("sha256").getHashingSource(src) + } catch (e: Exception) { + // TODO: run cancellation window? + ui.handleExceptionAndExit(e) + return } - PackFile pf; - try { - pf = new Toml().read(Okio.buffer(packFileSource).inputStream()).to(PackFile.class); - } catch (IllegalStateException e) { - ui.handleExceptionAndExit(e); - return; + val pf = packFileSource.buffer().use { + try { + Toml().read(it.inputStream()).to(PackFile::class.java) + } catch (e: IllegalStateException) { + ui.handleExceptionAndExit(e) + return + } } - if (stateHandler.getCancelButton()) { - showCancellationDialog(); - handleCancellation(); + if (stateHandler.cancelButton) { + showCancellationDialog() + handleCancellation() } - ui.submitProgress(new InstallProgress("Checking local files...")); + ui.submitProgress(InstallProgress("Checking local files...")) // Invalidation checking must be done here, as it must happen before pack/index hashes are checked - List invalidatedUris = new ArrayList<>(); - if (manifest.getCachedFiles() != null) { - for (Map.Entry entry : manifest.getCachedFiles().entrySet()) { - // ignore onlyOtherSide files - if (entry.getValue().getOnlyOtherSide()) { - continue; - } - boolean invalid = false; - // if isn't optional, or is optional but optionValue == true - if (!entry.getValue().isOptional() || entry.getValue().getOptionValue()) { - if (entry.getValue().getCachedLocation() != null) { - if (!Paths.get(opts.packFolder, entry.getValue().getCachedLocation()).toFile().exists()) { - invalid = true; - } - } else { - // if cachedLocation == null, should probably be installed!! - invalid = true; + val invalidatedUris: MutableList = ArrayList() + for ((fileUri, file) in manifest.cachedFiles) { + // ignore onlyOtherSide files + if (file.onlyOtherSide) { + continue + } + + var invalid = false + // if isn't optional, or is optional but optionValue == true + if (!file.isOptional || file.optionValue) { + if (file.cachedLocation != null) { + if (!Paths.get(opts.packFolder, file.cachedLocation).toFile().exists()) { + invalid = true } + } else { + // if cachedLocation == null, should probably be installed!! + invalid = true } - if (invalid) { - SpaceSafeURI fileUri = entry.getKey(); - System.out.println("File " + fileUri.toString() + " invalidated, marked for redownloading"); - invalidatedUris.add(fileUri); - } + } + if (invalid) { + println("File $fileUri invalidated, marked for redownloading") + invalidatedUris.add(fileUri) } } - if (manifest.getPackFileHash() != null && packFileSource.hashIsEqual(manifest.getPackFileHash()) && invalidatedUris.isEmpty()) { - System.out.println("Modpack is already up to date!"); + if (manifest.packFileHash?.let { packFileSource.hashIsEqual(it) } == true && invalidatedUris.isEmpty()) { + println("Modpack is already up to date!") // todo: --force? - if (!stateHandler.getOptionsButton()) { - return; + if (!stateHandler.optionsButton) { + return } } - System.out.println("Modpack name: " + pf.getName()); + println("Modpack name: " + pf.name) - if (stateHandler.getCancelButton()) { - showCancellationDialog(); - handleCancellation(); + if (stateHandler.cancelButton) { + showCancellationDialog() + handleCancellation() } - try { // This is badly written, I'll probably heavily refactor it at some point - processIndex(HandlerManager.getNewLoc(opts.downloadURI, Objects.requireNonNull(pf.getIndex()).getFile()), - HashUtils.getHash(Objects.requireNonNull(pf.getIndex().getHashFormat()), Objects.requireNonNull(pf.getIndex().getHash())), pf.getIndex().getHashFormat(), manifest, invalidatedUris); - } catch (Exception e1) { - ui.handleExceptionAndExit(e1); + // The port to Kotlin made this REALLY messy!!!! + getNewLoc(opts.downloadURI, Objects.requireNonNull(pf.index)!!.file)?.let { + pf.index!!.hashFormat?.let { it1 -> + processIndex(it, + getHash(Objects.requireNonNull(pf.index!!.hashFormat)!!, Objects.requireNonNull(pf.index!!.hash)!!), it1, manifest, invalidatedUris) + } + } + } catch (e1: Exception) { + ui.handleExceptionAndExit(e1) } - handleCancellation(); + handleCancellation() // TODO: update MMC params, java args etc // If there were errors, don't write the manifest/index hashes, to ensure they are rechecked later if (errorsOccurred) { - manifest.setIndexFileHash(null); - manifest.setPackFileHash(null); + manifest.indexFileHash = null + manifest.packFileHash = null } else { - manifest.setPackFileHash(packFileSource.getHash()); + manifest.packFileHash = packFileSource.hash } - manifest.setCachedSide(opts.side); - try (Writer writer = new FileWriter(Paths.get(opts.packFolder, opts.manifestFile).toString())) { - gson.toJson(manifest, writer); - } catch (IOException e) { + manifest.cachedSide = opts.side + 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.handleException(e) } - } - private void checkOptions() { + private fun checkOptions() { // TODO: implement } - private void processIndex(SpaceSafeURI indexUri, Hash indexHash, String hashFormat, ManifestFile manifest, List invalidatedUris) { - if (manifest.getIndexFileHash() != null && manifest.getIndexFileHash().equals(indexHash) && invalidatedUris.isEmpty()) { - System.out.println("Modpack files are already up to date!"); - if (!stateHandler.getOptionsButton()) { - return; + private fun processIndex(indexUri: SpaceSafeURI, indexHash: Hash, hashFormat: String, manifest: ManifestFile, invalidatedUris: List) { + if (manifest.indexFileHash == indexHash && invalidatedUris.isEmpty()) { + println("Modpack files are already up to date!") + if (!stateHandler.optionsButton) { + return } } - manifest.setIndexFileHash(indexHash); + manifest.indexFileHash = indexHash - GeneralHashingSource indexFileSource; - try { - Source src = HandlerManager.getFileSource(indexUri); - indexFileSource = HashUtils.getHasher(hashFormat).getHashingSource(src); - } catch (Exception e) { - // TODO: still launch the game if updating doesn't work? - // TODO: ask user if they want to launch the game, exit(1) if they don't - ui.handleExceptionAndExit(e); - return; + val indexFileSource = try { + val src = getFileSource(indexUri) + getHasher(hashFormat).getHashingSource(src) + } catch (e: Exception) { + // TODO: run cancellation window? + ui.handleExceptionAndExit(e) + return } - IndexFile indexFile; - try { - indexFile = new Toml().read(Okio.buffer(indexFileSource).inputStream()).to(IndexFile.class); - } catch (IllegalStateException e) { - ui.handleExceptionAndExit(e); - return; + val indexFile = try { + Toml().read(indexFileSource.buffer().inputStream()).to(IndexFile::class.java) + } catch (e: IllegalStateException) { + ui.handleExceptionAndExit(e) + return } - if (!indexFileSource.hashIsEqual(indexHash)) { // TODO: throw exception - System.out.println("I was meant to put an error message here but I'll do that later"); - return; + println("I was meant to put an error message here but I'll do that later") + return + } + if (stateHandler.cancelButton) { + showCancellationDialog() + return } - if (stateHandler.getCancelButton()) { - showCancellationDialog(); - return; - } - - ui.submitProgress(new InstallProgress("Checking local files...")); - Iterator> it = manifest.getCachedFiles().entrySet().iterator(); + ui.submitProgress(InstallProgress("Checking local files...")) + // TODO: use kotlin filtering/FP rather than an iterator? + val it: MutableIterator> = manifest.cachedFiles.entries.iterator() while (it.hasNext()) { - Map.Entry entry = it.next(); - if (entry.getValue().getCachedLocation() != null) { - boolean alreadyDeleted = false; + val (uri, file) = it.next() + if (file.cachedLocation != null) { + var alreadyDeleted = false // Delete if option value has been set to false - if (entry.getValue().isOptional() && !entry.getValue().getOptionValue()) { + if (file.isOptional && !file.optionValue) { try { - Files.deleteIfExists(Paths.get(opts.packFolder, entry.getValue().getCachedLocation())); - } catch (IOException e) { + Files.deleteIfExists(Paths.get(opts.packFolder, file.cachedLocation)) + } catch (e: IOException) { // TODO: should this be shown to the user in some way? - e.printStackTrace(); + e.printStackTrace() } // Set to null, as it doesn't exist anymore - entry.getValue().setCachedLocation(null); - alreadyDeleted = true; + file.cachedLocation = null + alreadyDeleted = true } - if (indexFile.getFiles().stream().noneMatch(f -> Objects.equals(f.getFile(), entry.getKey()))) { - // File has been removed from the index + if (indexFile.files.none { it.file == uri }) { // File has been removed from the index if (!alreadyDeleted) { try { - Files.deleteIfExists(Paths.get(opts.packFolder, entry.getValue().getCachedLocation())); - } catch (IOException e) { - // TODO: should this be shown to the user in some way? - e.printStackTrace(); + Files.deleteIfExists(Paths.get(opts.packFolder, file.cachedLocation)) + } catch (e: IOException) { // TODO: should this be shown to the user in some way? + e.printStackTrace() } } - it.remove(); + it.remove() } } } - if (stateHandler.getCancelButton()) { - showCancellationDialog(); - return; + if (stateHandler.cancelButton) { + showCancellationDialog() + return } - ui.submitProgress(new InstallProgress("Comparing new files...")); + ui.submitProgress(InstallProgress("Comparing new files...")) // TODO: progress bar? - if (indexFile.getFiles().isEmpty()) { - System.out.println("Warning: Index is empty!"); + if (indexFile.files.isEmpty()) { + println("Warning: Index is empty!") } - List tasks = DownloadTask.createTasksFromIndex(indexFile, indexFile.getHashFormat(), opts.side); + 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 - boolean invalidateAll = !opts.side.equals(manifest.getCachedSide()); + val invalidateAll = opts.side != manifest.cachedSide if (invalidateAll) { - System.out.println("Side changed, invalidating all mods"); + println("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? if (invalidateAll) { - f.invalidate(); - } else if (invalidatedUris.contains(f.metadata.getFile())) { - f.invalidate(); - } - ManifestFile.File file = manifest.getCachedFiles().get(f.metadata.getFile()); - if (file != null) { - // Ensure the file can be reverted later if necessary - the DownloadTask modifies the file so if it fails we need the old version back - file.backup(); + f.invalidate() + } else if (invalidatedUris.contains(f.metadata.file)) { + f.invalidate() } + val file = manifest.cachedFiles[f.metadata.file] + // Ensure the file can be reverted later if necessary - the DownloadTask modifies the file so if it fails we need the old version back + file?.backup() // If it is null, the DownloadTask will make a new empty cachedFile - f.updateFromCache(file); - }); + f.updateFromCache(file) + } - if (stateHandler.getCancelButton()) { - showCancellationDialog(); - return; + if (stateHandler.cancelButton) { + showCancellationDialog() + return } // Let's hope downloadMetadata is a pure function!!! - tasks.parallelStream().forEach(f -> f.downloadMetadata(indexFile, indexUri)); + tasks.parallelStream().forEach { f -> f.downloadMetadata(indexFile, indexUri) } - List failedTasks = tasks.stream().filter(t -> t.getException() != null).collect(Collectors.toList()); - if (!failedTasks.isEmpty()) { - errorsOccurred = true; - IExceptionDetails.ExceptionListResult exceptionListResult; - try { - exceptionListResult = ui.showExceptions(failedTasks, tasks.size(), true).get(); - } catch (InterruptedException | ExecutionException e) { - // Interrupted means cancelled??? - ui.handleExceptionAndExit(e); - return; + val failedTaskDetails = tasks.asSequence().map(DownloadTask::exceptionDetails).filterNotNull().toList() + if (failedTaskDetails.isNotEmpty()) { + errorsOccurred = true + val exceptionListResult: ExceptionListResult + exceptionListResult = try { + ui.showExceptions(failedTaskDetails, tasks.size, true).get() + } catch (e: InterruptedException) { // Interrupted means cancelled??? + ui.handleExceptionAndExit(e) + return + } catch (e: ExecutionException) { + ui.handleExceptionAndExit(e) + return } - switch (exceptionListResult) { - case CONTINUE: - break; - case CANCEL: - cancelled = true; - return; - case IGNORE: - cancelledStartGame = true; - return; + when (exceptionListResult) { + ExceptionListResult.CONTINUE -> {} + ExceptionListResult.CANCEL -> { + cancelled = true + return + } + ExceptionListResult.IGNORE -> { + cancelledStartGame = true + return + } } } - if (stateHandler.getCancelButton()) { - showCancellationDialog(); - return; + if (stateHandler.cancelButton) { + showCancellationDialog() + return } - List nonFailedFirstTasks = tasks.stream().filter(t -> t.getException() == null).collect(Collectors.toList()); - List optionTasks = nonFailedFirstTasks.stream().filter(DownloadTask::correctSide).filter(DownloadTask::isOptional).collect(Collectors.toList()); + // TODO: task failed function? + val nonFailedFirstTasks = tasks.filter { t -> !t.failed() }.toList() + val optionTasks = nonFailedFirstTasks.filter(DownloadTask::correctSide).filter(DownloadTask::isOptional).toList() // If options changed, present all options again - if (stateHandler.getOptionsButton() || optionTasks.stream().anyMatch(DownloadTask::isNewOptional)) { - // new ArrayList is requires so it's an IOptionDetails rather than a DownloadTask list - Future cancelledResult = ui.showOptions(new ArrayList<>(optionTasks)); + if (stateHandler.optionsButton || optionTasks.any(DownloadTask::isNewOptional)) { + // new ArrayList is required so it's an IOptionDetails rather than a DownloadTask list + val cancelledResult = ui.showOptions(ArrayList(optionTasks)) try { if (cancelledResult.get()) { - cancelled = true; + cancelled = true // TODO: Should the UI be closed somehow?? - return; + return } - } catch (InterruptedException | ExecutionException e) { + } catch (e: InterruptedException) { // Interrupted means cancelled??? - ui.handleExceptionAndExit(e); + ui.handleExceptionAndExit(e) + } catch (e: ExecutionException) { + ui.handleExceptionAndExit(e) } } - ui.disableOptionsButton(); + ui.disableOptionsButton() // TODO: different thread pool type? - ExecutorService threadPool = Executors.newFixedThreadPool(10); - CompletionService completionService = new ExecutorCompletionService<>(threadPool); - - tasks.forEach(t -> completionService.submit(() -> { - t.download(opts.packFolder, indexUri); - return t; - })); - - for (int i = 0; i < tasks.size(); i++) { - DownloadTask task; - try { - task = completionService.take().get(); - } catch (InterruptedException | ExecutionException e) { - ui.handleException(e); - task = null; + val threadPool = Executors.newFixedThreadPool(10) + val completionService: CompletionService = ExecutorCompletionService(threadPool) + tasks.forEach { t -> + completionService.submit { + t.download(opts.packFolder, indexUri) + t + } + } + for (i in tasks.indices) { + var task: DownloadTask? + task = try { + completionService.take().get() + } catch (e: InterruptedException) { + ui.handleException(e) + null + } catch (e: ExecutionException) { + ui.handleException(e) + null } // Update manifest - If there were no errors cachedFile has already been modified in place (good old pass by reference) - if (task != null) { - if (task.getException() != null) { - ManifestFile.File file = task.cachedFile.getRevert(); - if (file != null) { - manifest.getCachedFiles().putIfAbsent(task.metadata.getFile(), file); - } + task?.cachedFile?.let { file -> + if (task.failed()) { + val oldFile = file.revert + if (oldFile != null) { + task.metadata.file?.let { uri -> manifest.cachedFiles.putIfAbsent(uri, oldFile) } + } else { null } } else { - // idiot, if it wasn't there in the first place it won't magically appear there - manifest.getCachedFiles().putIfAbsent(task.metadata.getFile(), task.cachedFile); + task.metadata.file?.let { uri -> manifest.cachedFiles.putIfAbsent(uri, file) } } } - String progress; + var progress: String if (task != null) { - if (task.getException() != null) { - progress = "Failed to download " + task.metadata.getName() + ": " + task.getException().getMessage(); - task.getException().printStackTrace(); + val exDetails = task.exceptionDetails + if (exDetails != null) { + progress = "Failed to download ${exDetails.name}: ${exDetails.exception.message}" + exDetails.exception.printStackTrace() } else { - // TODO: should this be revised for tasks that didn't actually download it? - progress = "Downloaded " + task.metadata.getName(); + progress = "Downloaded ${task.name}" } } else { - progress = "Failed to download, unknown reason"; + progress = "Failed to download, unknown reason" } - ui.submitProgress(new InstallProgress(progress, i + 1, tasks.size())); + ui.submitProgress(InstallProgress(progress, i + 1, tasks.size)) - if (stateHandler.getCancelButton()) { - // Stop all tasks, don't launch the game (it's in an invalid state!) - threadPool.shutdown(); - cancelled = true; - return; + if (stateHandler.cancelButton) { // Stop all tasks, don't launch the game (it's in an invalid state!) + threadPool.shutdown() + cancelled = true + return } } // Shut down the thread pool when the update is done - threadPool.shutdown(); + threadPool.shutdown() - List failedTasks2ElectricBoogaloo = nonFailedFirstTasks.stream().filter(t -> t.getException() != null).collect(Collectors.toList()); - if (!failedTasks2ElectricBoogaloo.isEmpty()) { - errorsOccurred = true; - IExceptionDetails.ExceptionListResult exceptionListResult; - try { - exceptionListResult = ui.showExceptions(failedTasks2ElectricBoogaloo, tasks.size(), false).get(); - } catch (InterruptedException | ExecutionException e) { + val failedTasks2ElectricBoogaloo = nonFailedFirstTasks.asSequence().map(DownloadTask::exceptionDetails).filterNotNull().toList() + if (failedTasks2ElectricBoogaloo.isNotEmpty()) { + errorsOccurred = true + val exceptionListResult: ExceptionListResult + exceptionListResult = try { + ui.showExceptions(failedTasks2ElectricBoogaloo, tasks.size, false).get() + } catch (e: InterruptedException) { // Interrupted means cancelled??? - ui.handleExceptionAndExit(e); - return; + ui.handleExceptionAndExit(e) + return + } catch (e: ExecutionException) { + ui.handleExceptionAndExit(e) + return } - switch (exceptionListResult) { - case CONTINUE: - break; - case CANCEL: - cancelled = true; - return; - case IGNORE: - cancelledStartGame = true; + when (exceptionListResult) { + ExceptionListResult.CONTINUE -> {} + ExceptionListResult.CANCEL -> cancelled = true + ExceptionListResult.IGNORE -> cancelledStartGame = true } } } - private void showCancellationDialog() { - IExceptionDetails.ExceptionListResult exceptionListResult; - try { - exceptionListResult = ui.showCancellationDialog().get(); - } catch (InterruptedException | ExecutionException e) { + private fun showCancellationDialog() { + val cancellationResult: CancellationResult + cancellationResult = try { + ui.showCancellationDialog().get() + } catch (e: InterruptedException) { // Interrupted means cancelled??? - ui.handleExceptionAndExit(e); - return; + ui.handleExceptionAndExit(e) + return + } catch (e: ExecutionException) { + ui.handleExceptionAndExit(e) + return } - switch (exceptionListResult) { - case CONTINUE: - throw new RuntimeException("Continuation not allowed here!"); - case CANCEL: - cancelled = true; - return; - case IGNORE: - cancelledStartGame = true; + when (cancellationResult) { + CancellationResult.QUIT -> cancelled = true + CancellationResult.CONTINUE -> cancelledStartGame = true } } - private void handleCancellation() { + private fun handleCancellation() { if (cancelled) { - System.out.println("Update cancelled by user!"); - System.exit(1); + println("Update cancelled by user!") + exitProcess(1) } else if (cancelledStartGame) { - System.out.println("Update cancelled by user! Continuing to start game..."); - System.exit(0); + println("Update cancelled by user! Continuing to start game...") + exitProcess(0) } } -} +} \ No newline at end of file diff --git a/src/main/kotlin/link/infra/packwiz/installer/metadata/IndexFile.kt b/src/main/kotlin/link/infra/packwiz/installer/metadata/IndexFile.kt index 24ba614..7429382 100644 --- a/src/main/kotlin/link/infra/packwiz/installer/metadata/IndexFile.kt +++ b/src/main/kotlin/link/infra/packwiz/installer/metadata/IndexFile.kt @@ -74,10 +74,11 @@ class IndexFile { } // TODO: throw some kind of exception? - val name: String? + val name: String get() { if (metafile) { - return linkedFile?.name ?: linkedFile?.filename + return linkedFile?.name ?: linkedFile?.filename ?: + file?.run { Paths.get(path).fileName.toString() } ?: "Invalid file" } return file?.run { Paths.get(path).fileName.toString() } ?: "Invalid file" } diff --git a/src/main/kotlin/link/infra/packwiz/installer/ui/CLIHandler.kt b/src/main/kotlin/link/infra/packwiz/installer/ui/CLIHandler.kt index 4564624..b15ef0d 100644 --- a/src/main/kotlin/link/infra/packwiz/installer/ui/CLIHandler.kt +++ b/src/main/kotlin/link/infra/packwiz/installer/ui/CLIHandler.kt @@ -1,6 +1,6 @@ package link.infra.packwiz.installer.ui -import link.infra.packwiz.installer.ui.IExceptionDetails.ExceptionListResult +import link.infra.packwiz.installer.ui.IUserInterface.ExceptionListResult import java.util.concurrent.CompletableFuture import java.util.concurrent.Future @@ -39,7 +39,7 @@ class CLIHandler : IUserInterface { } } - override fun showExceptions(exceptions: List, numTotal: Int, allowsIgnore: Boolean): Future { + override fun showExceptions(exceptions: List, numTotal: Int, allowsIgnore: Boolean): Future { val future = CompletableFuture() future.complete(ExceptionListResult.CANCEL) return future diff --git a/src/main/kotlin/link/infra/packwiz/installer/ui/ExceptionDetails.kt b/src/main/kotlin/link/infra/packwiz/installer/ui/ExceptionDetails.kt new file mode 100644 index 0000000..f4fc81c --- /dev/null +++ b/src/main/kotlin/link/infra/packwiz/installer/ui/ExceptionDetails.kt @@ -0,0 +1,6 @@ +package link.infra.packwiz.installer.ui + +data class ExceptionDetails( + val name: String, + val exception: Exception +) \ No newline at end of file diff --git a/src/main/kotlin/link/infra/packwiz/installer/ui/ExceptionListWindow.kt b/src/main/kotlin/link/infra/packwiz/installer/ui/ExceptionListWindow.kt index 094844e..d4a0cd3 100644 --- a/src/main/kotlin/link/infra/packwiz/installer/ui/ExceptionListWindow.kt +++ b/src/main/kotlin/link/infra/packwiz/installer/ui/ExceptionListWindow.kt @@ -1,6 +1,5 @@ package link.infra.packwiz.installer.ui -import link.infra.packwiz.installer.ui.IExceptionDetails.ExceptionListResult import java.awt.BorderLayout import java.awt.Desktop import java.awt.event.WindowAdapter @@ -14,10 +13,10 @@ import java.util.concurrent.CompletableFuture import javax.swing.* import javax.swing.border.EmptyBorder -internal class ExceptionListWindow(eList: List, future: CompletableFuture, numTotal: Int, allowsIgnore: Boolean, parentWindow: JFrame?) : JDialog(parentWindow, "Failed file downloads", true) { +class ExceptionListWindow(eList: List, future: CompletableFuture, numTotal: Int, allowsIgnore: Boolean, parentWindow: JFrame?) : JDialog(parentWindow, "Failed file downloads", true) { private val lblExceptionStacktrace: JTextArea - private class ExceptionListModel internal constructor(private val details: List) : AbstractListModel() { + private class ExceptionListModel internal constructor(private val details: List) : AbstractListModel() { override fun getSize() = details.size override fun getElementAt(index: Int) = details[index].name fun getExceptionAt(index: Int) = details[index].exception @@ -90,7 +89,7 @@ internal class ExceptionListWindow(eList: List, future: Compl add(JButton("Continue").apply { toolTipText = "Attempt to continue installing, excluding the failed downloads" addActionListener { - future.complete(ExceptionListResult.CONTINUE) + future.complete(IUserInterface.ExceptionListResult.CONTINUE) this@ExceptionListWindow.dispose() } }) @@ -98,7 +97,7 @@ internal class ExceptionListWindow(eList: List, future: Compl add(JButton("Cancel launch").apply { toolTipText = "Stop launching the game" addActionListener { - future.complete(ExceptionListResult.CANCEL) + future.complete(IUserInterface.ExceptionListResult.CANCEL) this@ExceptionListWindow.dispose() } }) @@ -107,7 +106,7 @@ internal class ExceptionListWindow(eList: List, future: Compl toolTipText = "Start the game without attempting to update" isEnabled = allowsIgnore addActionListener { - future.complete(ExceptionListResult.IGNORE) + future.complete(IUserInterface.ExceptionListResult.IGNORE) this@ExceptionListWindow.dispose() } }) @@ -139,13 +138,13 @@ internal class ExceptionListWindow(eList: List, future: Compl addWindowListener(object : WindowAdapter() { override fun windowClosing(e: WindowEvent) { - future.complete(ExceptionListResult.CANCEL) + future.complete(IUserInterface.ExceptionListResult.CANCEL) } override fun windowClosed(e: WindowEvent) { // Just in case closing didn't get triggered - if something else called dispose() the // future will have already completed - future.complete(ExceptionListResult.CANCEL) + future.complete(IUserInterface.ExceptionListResult.CANCEL) } }) } diff --git a/src/main/kotlin/link/infra/packwiz/installer/ui/IExceptionDetails.kt b/src/main/kotlin/link/infra/packwiz/installer/ui/IExceptionDetails.kt deleted file mode 100644 index a092ad6..0000000 --- a/src/main/kotlin/link/infra/packwiz/installer/ui/IExceptionDetails.kt +++ /dev/null @@ -1,10 +0,0 @@ -package link.infra.packwiz.installer.ui - -interface IExceptionDetails { - val exception: Exception - val name: String - - enum class ExceptionListResult { - CONTINUE, CANCEL, IGNORE - } -} \ No newline at end of file diff --git a/src/main/kotlin/link/infra/packwiz/installer/ui/IUserInterface.kt b/src/main/kotlin/link/infra/packwiz/installer/ui/IUserInterface.kt index 1c37a67..3744436 100644 --- a/src/main/kotlin/link/infra/packwiz/installer/ui/IUserInterface.kt +++ b/src/main/kotlin/link/infra/packwiz/installer/ui/IUserInterface.kt @@ -1,6 +1,5 @@ package link.infra.packwiz.installer.ui -import link.infra.packwiz.installer.ui.IExceptionDetails.ExceptionListResult import java.util.concurrent.CompletableFuture import java.util.concurrent.Future import kotlin.system.exitProcess @@ -21,14 +20,22 @@ interface IUserInterface { // Return true if the installation was cancelled! fun showOptions(options: List): Future - fun showExceptions(exceptions: List, numTotal: Int, allowsIgnore: Boolean): Future + fun showExceptions(exceptions: List, numTotal: Int, allowsIgnore: Boolean): Future @JvmDefault fun disableOptionsButton() {} - // Should not return CONTINUE + @JvmDefault - fun showCancellationDialog(): Future { - return CompletableFuture().apply { - complete(ExceptionListResult.CANCEL) + fun showCancellationDialog(): Future { + return CompletableFuture().apply { + complete(CancellationResult.QUIT) } } + + enum class ExceptionListResult { + CONTINUE, CANCEL, IGNORE + } + + enum class CancellationResult { + QUIT, CONTINUE + } } \ No newline at end of file diff --git a/src/main/kotlin/link/infra/packwiz/installer/ui/InstallWindow.kt b/src/main/kotlin/link/infra/packwiz/installer/ui/InstallWindow.kt index e4aea73..009e8c7 100644 --- a/src/main/kotlin/link/infra/packwiz/installer/ui/InstallWindow.kt +++ b/src/main/kotlin/link/infra/packwiz/installer/ui/InstallWindow.kt @@ -1,6 +1,6 @@ package link.infra.packwiz.installer.ui -import link.infra.packwiz.installer.ui.IExceptionDetails.ExceptionListResult +import link.infra.packwiz.installer.ui.IUserInterface.ExceptionListResult import java.awt.* import java.util.concurrent.CompletableFuture import java.util.concurrent.Future @@ -186,7 +186,7 @@ class InstallWindow : IUserInterface { return future } - override fun showExceptions(exceptions: List, numTotal: Int, allowsIgnore: Boolean): Future { + override fun showExceptions(exceptions: List, numTotal: Int, allowsIgnore: Boolean): Future { val future = CompletableFuture() EventQueue.invokeLater { ExceptionListWindow(exceptions, future, numTotal, allowsIgnore, frmPackwizlauncher).apply { @@ -204,15 +204,15 @@ class InstallWindow : IUserInterface { } } - override fun showCancellationDialog(): Future { - val future = CompletableFuture() + override fun showCancellationDialog(): Future { + val future = CompletableFuture() EventQueue.invokeLater { val buttons = arrayOf("Quit", "Ignore") val result = JOptionPane.showOptionDialog(frmPackwizlauncher, "The installation was cancelled. Would you like to quit the game, or ignore the update and start the game?", "Cancelled installation", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, buttons, buttons[0]) - future.complete(if (result == 0) ExceptionListResult.CANCEL else ExceptionListResult.IGNORE) + future.complete(if (result == 0) IUserInterface.CancellationResult.QUIT else IUserInterface.CancellationResult.CONTINUE) } return future }