Complete Kotlin port

This commit is contained in:
comp500 2019-12-21 02:04:10 +00:00
parent 9d3587c72e
commit a15489f5e4
9 changed files with 537 additions and 557 deletions

View File

@ -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.IndexFile
import link.infra.packwiz.installer.metadata.ManifestFile; 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.SpaceSafeURI; import link.infra.packwiz.installer.metadata.hash.Hash
import link.infra.packwiz.installer.metadata.hash.GeneralHashingSource; import link.infra.packwiz.installer.metadata.hash.HashUtils.getHash
import link.infra.packwiz.installer.metadata.hash.Hash; import link.infra.packwiz.installer.metadata.hash.HashUtils.getHasher
import link.infra.packwiz.installer.metadata.hash.HashUtils; import link.infra.packwiz.installer.ui.ExceptionDetails
import link.infra.packwiz.installer.ui.IExceptionDetails; import link.infra.packwiz.installer.ui.IOptionDetails
import link.infra.packwiz.installer.ui.IOptionDetails; import okio.Buffer
import okio.Buffer; import okio.buffer
import okio.Okio; import java.io.IOException
import okio.Source; import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.util.*
import java.io.IOException; internal class DownloadTask private constructor(val metadata: IndexFile.File, defaultFormat: String, private val downloadSide: UpdateManager.Options.Side) : IOptionDetails {
import java.nio.file.Files; var cachedFile: ManifestFile.File? = null
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;
class DownloadTask implements IOptionDetails, IExceptionDetails { private var err: Exception? = null
final IndexFile.File metadata; val exceptionDetails get() = err?.let { e -> ExceptionDetails(name, e) }
ManifestFile.File cachedFile = null;
private Exception failure = null; fun failed() = err != null
private boolean alreadyUpToDate = false;
private boolean metadataRequired = true; private var alreadyUpToDate = false
private boolean invalidated = 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 // If file is new or isOptional changed to true, the option needs to be presented again
private boolean newOptional = true; private var newOptional = true
private final UpdateManager.Options.Side downloadSide;
private DownloadTask(IndexFile.File metadata, String defaultFormat, UpdateManager.Options.Side downloadSide) { val isOptional get() = metadata.linkedFile?.isOptional ?: false
this.metadata = metadata;
if (metadata.getHashFormat() == null || metadata.getHashFormat().length() == 0) { fun isNewOptional() = isOptional && newOptional
metadata.setHashFormat(defaultFormat);
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
} }
this.downloadSide = downloadSide; cachedFile?.optionValue = value
} }
void invalidate() { override val optionDescription get() = metadata.linkedFile?.option?.description ?: ""
invalidated = true;
alreadyUpToDate = false; init {
if (metadata.hashFormat?.isEmpty() != false) {
metadata.hashFormat = defaultFormat
}
} }
void updateFromCache(ManifestFile.File cachedFile) { fun invalidate() {
if (failure != null) return; invalidated = true
alreadyUpToDate = false
}
fun updateFromCache(cachedFile: ManifestFile.File?) {
if (err != null) return
if (cachedFile == null) { if (cachedFile == null) {
this.cachedFile = new ManifestFile.File(); this.cachedFile = ManifestFile.File()
return; return
} }
this.cachedFile = cachedFile
this.cachedFile = cachedFile;
if (!invalidated) { if (!invalidated) {
Hash currHash; val currHash = try {
try { getHash(metadata.hashFormat!!, metadata.hash!!)
currHash = HashUtils.getHash(Objects.requireNonNull(metadata.getHashFormat()), Objects.requireNonNull(metadata.getHash())); } catch (e: Exception) {
} catch (Exception e) { err = e
failure = e; return
return;
} }
if (currHash.equals(cachedFile.getHash())) { if (currHash == cachedFile.hash) { // Already up to date
// Already up to date alreadyUpToDate = true
alreadyUpToDate = true; metadataRequired = false
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 // 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 // the file, and to show the description and name
metadataRequired = true; metadataRequired = true
} }
} }
void downloadMetadata(IndexFile parentIndexFile, SpaceSafeURI indexUri) { fun downloadMetadata(parentIndexFile: IndexFile, indexUri: SpaceSafeURI) {
if (failure != null) return; if (err != null) return
if (metadataRequired) { if (metadataRequired) {
try { try {
metadata.downloadMeta(parentIndexFile, indexUri); // Retrieve the linked metadata file
} catch (Exception e) { metadata.downloadMeta(parentIndexFile, indexUri)
failure = e; } catch (e: Exception) {
return; err = e
return
} }
ModFile linkedFile = metadata.getLinkedFile(); cachedFile?.let { cachedFile ->
val linkedFile = metadata.linkedFile
if (linkedFile != null) { if (linkedFile != null) {
ModFile.Option option = linkedFile.getOption(); linkedFile.option?.let { opt ->
if (option != null) { if (opt.optional) {
if (option.getOptional()) { if (cachedFile.isOptional) {
if (cachedFile.isOptional()) {
// isOptional didn't change // isOptional didn't change
newOptional = false; newOptional = false
} else { } else {
// isOptional false -> true, set option to it's default value // isOptional false -> true, set option to it's default value
// TODO: preserve previous option value, somehow?? // TODO: preserve previous option value, somehow??
cachedFile.setOptionValue(option.getDefaultValue()); cachedFile.optionValue = opt.defaultValue
} }
} }
} }
cachedFile.setOptional(isOptional()); cachedFile.isOptional = isOptional
cachedFile.setOnlyOtherSide(!correctSide()); cachedFile.onlyOtherSide = !correctSide()
}
} }
} }
} }
void download(String packFolder, SpaceSafeURI indexUri) { fun download(packFolder: String, indexUri: SpaceSafeURI) {
if (failure != null) return; if (err != null) return
// Ensure it is removed // Ensure it is removed
if (!cachedFile.getOptionValue() || !correctSide()) { cachedFile?.let {
if (cachedFile.getCachedLocation() == null) return; if (!it.optionValue || !correctSide()) {
if (it.cachedLocation == null) return
try { try {
Files.deleteIfExists(Paths.get(packFolder, cachedFile.getCachedLocation())); Files.deleteIfExists(Paths.get(packFolder, it.cachedLocation))
} catch (IOException e) { } catch (e: IOException) {
// TODO: how much of a problem is this? use log4j/other log library to show warning? // TODO: how much of a problem is this? use log4j/other log library to show warning?
e.printStackTrace(); e.printStackTrace()
} }
cachedFile.setCachedLocation(null); it.cachedLocation = null
return;
} }
}
if (alreadyUpToDate) return
if (alreadyUpToDate) return; // TODO: should I be validating JSON properly, or this fine!!!!!!!??
assert(metadata.destURI != null)
Path destPath = Paths.get(packFolder, Objects.requireNonNull(metadata.getDestURI()).toString()); val destPath = Paths.get(packFolder, metadata.destURI.toString())
// Don't update files marked with preserve if they already exist on disk // Don't update files marked with preserve if they already exist on disk
if (metadata.getPreserve()) { if (metadata.preserve) {
if (destPath.toFile().exists()) { if (destPath.toFile().exists()) {
return; return
} }
} }
try { try {
Hash hash; val hash: Hash
String fileHashFormat; val fileHashFormat: String
ModFile linkedFile = metadata.getLinkedFile(); val linkedFile = metadata.linkedFile
if (linkedFile != null) { if (linkedFile != null) {
hash = linkedFile.getHash(); hash = linkedFile.hash
fileHashFormat = Objects.requireNonNull(linkedFile.getDownload()).getHashFormat(); fileHashFormat = Objects.requireNonNull(Objects.requireNonNull(linkedFile.download)!!.hashFormat)!!
} else { } else {
hash = metadata.getHashObj(); hash = metadata.getHashObj()
fileHashFormat = metadata.getHashFormat(); fileHashFormat = Objects.requireNonNull(metadata.hashFormat)!!
} }
Source src = metadata.getSource(indexUri); val src = metadata.getSource(indexUri)
assert fileHashFormat != null; val fileSource = getHasher(fileHashFormat).getHashingSource(src)
GeneralHashingSource fileSource = HashUtils.getHasher(fileHashFormat).getHashingSource(src); val data = Buffer()
Buffer data = new Buffer();
Okio.buffer(fileSource).readAll(data); // 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)) { if (fileSource.hashIsEqual(hash)) {
Files.createDirectories(destPath.getParent()); Files.createDirectories(destPath.parent)
Files.copy(data.inputStream(), destPath, StandardCopyOption.REPLACE_EXISTING); Files.copy(data.inputStream(), destPath, StandardCopyOption.REPLACE_EXISTING)
data.clear()
} else { } else {
// TODO: no more SYSOUT!!!!!!!!! // TODO: no more PRINTLN!!!!!!!!!
System.out.println("Invalid hash for " + metadata.getDestURI().toString()); println("Invalid hash for " + metadata.destURI.toString())
System.out.println("Calculated: " + fileSource.getHash()); println("Calculated: " + fileSource.hash)
System.out.println("Expected: " + hash); println("Expected: $hash")
failure = new Exception("Hash invalid!"); err = Exception("Hash invalid!")
} }
cachedFile?.cachedLocation?.let {
if (cachedFile.getCachedLocation() != null && !destPath.equals(Paths.get(packFolder, cachedFile.getCachedLocation()))) { if (destPath != Paths.get(packFolder, it)) {
// Delete old file if location changes // Delete old file if location changes
Files.delete(Paths.get(packFolder, cachedFile.getCachedLocation())); Files.delete(Paths.get(packFolder, cachedFile!!.cachedLocation))
} }
} catch (Exception e) {
failure = e;
} }
if (failure == null) { } catch (e: Exception) {
if (cachedFile == null) { err = e
cachedFile = new ManifestFile.File();
} }
if (err == null) {
// Update the manifest file // Update the manifest file
cachedFile = (cachedFile ?: ManifestFile.File()).also {
try { try {
cachedFile.setHash(metadata.getHashObj()); it.hash = metadata.getHashObj()
} catch (Exception e) { } catch (e: Exception) {
failure = e; err = e
return; return
} }
cachedFile.setOptional(isOptional()); it.isOptional = isOptional
cachedFile.setCachedLocation(metadata.getDestURI().toString()); it.cachedLocation = metadata.destURI.toString()
if (metadata.getLinkedFile() != null) { metadata.linkedFile?.let { linked ->
try { try {
cachedFile.setLinkedFileHash(metadata.getLinkedFile().getHash()); it.linkedFileHash = linked.hash
} catch (Exception e) { } catch (e: Exception) {
failure = e; err = e
}
} }
} }
} }
} }
public Exception getException() { companion object {
return failure; @JvmStatic
fun createTasksFromIndex(index: IndexFile, defaultFormat: String, downloadSide: UpdateManager.Options.Side): List<DownloadTask> {
val tasks = ArrayList<DownloadTask>()
for (file in Objects.requireNonNull(index.files)) {
tasks.add(DownloadTask(file, defaultFormat, downloadSide))
} }
return tasks
boolean isOptional() {
if (metadata.getLinkedFile() != null) {
return metadata.getLinkedFile().isOptional();
} }
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<DownloadTask> createTasksFromIndex(IndexFile index, String defaultFormat, UpdateManager.Options.Side downloadSide) {
ArrayList<DownloadTask> tasks = new ArrayList<>();
for (IndexFile.File file : Objects.requireNonNull(index.getFiles())) {
tasks.add(new DownloadTask(file, defaultFormat, downloadSide));
}
return tasks;
}
} }

View File

@ -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.GsonBuilder; import com.google.gson.JsonIOException
import com.google.gson.JsonIOException; import com.google.gson.JsonSyntaxException
import com.google.gson.JsonSyntaxException; import com.google.gson.annotations.SerializedName
import com.google.gson.annotations.SerializedName; import com.moandjiezana.toml.Toml
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.IndexFile
import link.infra.packwiz.installer.metadata.ManifestFile; import link.infra.packwiz.installer.metadata.ManifestFile
import link.infra.packwiz.installer.metadata.PackFile; import link.infra.packwiz.installer.metadata.PackFile
import link.infra.packwiz.installer.metadata.SpaceSafeURI; 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.Hash; import link.infra.packwiz.installer.metadata.hash.HashUtils.getHash
import link.infra.packwiz.installer.metadata.hash.HashUtils; import link.infra.packwiz.installer.metadata.hash.HashUtils.getHasher
import link.infra.packwiz.installer.request.HandlerManager; import link.infra.packwiz.installer.request.HandlerManager.getFileSource
import link.infra.packwiz.installer.ui.IExceptionDetails; import link.infra.packwiz.installer.request.HandlerManager.getNewLoc
import link.infra.packwiz.installer.ui.IUserInterface; import link.infra.packwiz.installer.ui.IUserInterface
import link.infra.packwiz.installer.ui.InputStateHandler; import link.infra.packwiz.installer.ui.IUserInterface.CancellationResult
import link.infra.packwiz.installer.ui.InstallProgress; import link.infra.packwiz.installer.ui.IUserInterface.ExceptionListResult
import okio.Okio; import link.infra.packwiz.installer.ui.InputStateHandler
import okio.Source; 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.*; class UpdateManager internal constructor(private val opts: Options, val ui: IUserInterface, private val stateHandler: InputStateHandler) {
import java.nio.file.Files; private var cancelled = false
import java.nio.file.Paths; private var cancelledStartGame = false
import java.util.*; private var errorsOccurred = false
import java.util.concurrent.*;
import java.util.stream.Collectors;
public class UpdateManager { init {
start()
}
private final Options opts; data class Options(
public final IUserInterface ui; var downloadURI: SpaceSafeURI? = null,
private boolean cancelled; var manifestFile: String = "packwiz.json", // TODO: make configurable
private boolean cancelledStartGame = false; var packFolder: String = ".",
private InputStateHandler stateHandler; var side: Side = Side.CLIENT
private boolean errorsOccurred = false; ) {
enum class Side {
public static class Options {
SpaceSafeURI downloadURI = null;
String manifestFile = "packwiz.json"; // TODO: make configurable
String packFolder = ".";
Side side = Side.CLIENT;
public enum Side {
@SerializedName("client") @SerializedName("client")
CLIENT("client"), CLIENT("client"),
@SerializedName("server") @SerializedName("server")
SERVER("server"), SERVER("server"),
@SerializedName("both") @SerializedName("both")
BOTH("both", new Side[] { CLIENT, SERVER }); @Suppress("unused")
BOTH("both", arrayOf(CLIENT, SERVER));
private final String sideName; private val sideName: String
private final Side[] depSides; private val depSides: Array<Side>?
Side(String sideName) { constructor(sideName: String) {
this.sideName = sideName.toLowerCase(); this.sideName = sideName.toLowerCase()
this.depSides = null; depSides = null
} }
Side(String sideName, Side[] depSides) { constructor(sideName: String, depSides: Array<Side>) {
this.sideName = sideName.toLowerCase(); this.sideName = sideName.toLowerCase()
this.depSides = depSides; this.depSides = depSides
} }
@Override override fun toString() = sideName
public String toString() {
return this.sideName; fun hasSide(tSide: Side): Boolean {
if (this == tSide) {
return true
}
if (depSides != null) {
for (depSide in depSides) {
if (depSide == tSide) {
return true
}
}
}
return false
} }
public boolean hasSide(Side tSide) { companion object {
if (this.equals(tSide)) { fun from(name: String): Side? {
return true; val lowerName = name.toLowerCase()
} for (side in values()) {
if (this.depSides != null) { if (side.sideName == lowerName) {
for (Side depSide : this.depSides) { return side
if (depSide.equals(tSide)) {
return true;
} }
} }
return null
} }
return false;
}
public static Side from(String name) {
String lowerName = name.toLowerCase();
for (Side side : Side.values()) {
if (side.sideName.equals(lowerName)) {
return side;
}
}
return null;
} }
} }
} }
UpdateManager(Options opts, IUserInterface ui, InputStateHandler inputStateHandler) { private fun start() {
this.opts = opts; checkOptions()
this.ui = ui;
this.stateHandler = inputStateHandler; ui.submitProgress(InstallProgress("Loading manifest file..."))
this.start(); 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
} }
private void start() { if (stateHandler.cancelButton) {
this.checkOptions(); showCancellationDialog()
handleCancellation()
}
ui.submitProgress(new InstallProgress("Loading manifest file...")); ui.submitProgress(InstallProgress("Loading pack file..."))
Gson gson = new GsonBuilder().registerTypeAdapter(Hash.class, new Hash.TypeHandler()).create(); val packFileSource = try {
ManifestFile manifest; Objects.requireNonNull(opts.downloadURI)
val src = getFileSource(opts.downloadURI!!)
getHasher("sha256").getHashingSource(src)
} catch (e: Exception) {
// TODO: run cancellation window?
ui.handleExceptionAndExit(e)
return
}
val pf = packFileSource.buffer().use {
try { try {
manifest = gson.fromJson(new FileReader(Paths.get(opts.packFolder, opts.manifestFile).toString()), Toml().read(it.inputStream()).to(PackFile::class.java)
ManifestFile.class); } catch (e: IllegalStateException) {
} catch (FileNotFoundException e) { ui.handleExceptionAndExit(e)
manifest = new ManifestFile(); return
} catch (JsonSyntaxException | JsonIOException e) { }
ui.handleExceptionAndExit(e);
return;
} }
if (stateHandler.getCancelButton()) { if (stateHandler.cancelButton) {
showCancellationDialog(); showCancellationDialog()
handleCancellation(); handleCancellation()
} }
ui.submitProgress(new InstallProgress("Loading pack file...")); ui.submitProgress(InstallProgress("Checking local files..."))
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;
}
PackFile pf;
try {
pf = new Toml().read(Okio.buffer(packFileSource).inputStream()).to(PackFile.class);
} catch (IllegalStateException e) {
ui.handleExceptionAndExit(e);
return;
}
if (stateHandler.getCancelButton()) {
showCancellationDialog();
handleCancellation();
}
ui.submitProgress(new InstallProgress("Checking local files..."));
// Invalidation checking must be done here, as it must happen before pack/index hashes are checked // Invalidation checking must be done here, as it must happen before pack/index hashes are checked
List<SpaceSafeURI> invalidatedUris = new ArrayList<>(); val invalidatedUris: MutableList<SpaceSafeURI> = ArrayList()
if (manifest.getCachedFiles() != null) { for ((fileUri, file) in manifest.cachedFiles) {
for (Map.Entry<SpaceSafeURI, ManifestFile.File> entry : manifest.getCachedFiles().entrySet()) {
// ignore onlyOtherSide files // ignore onlyOtherSide files
if (entry.getValue().getOnlyOtherSide()) { if (file.onlyOtherSide) {
continue; continue
} }
boolean invalid = false;
var invalid = false
// if isn't optional, or is optional but optionValue == true // if isn't optional, or is optional but optionValue == true
if (!entry.getValue().isOptional() || entry.getValue().getOptionValue()) { if (!file.isOptional || file.optionValue) {
if (entry.getValue().getCachedLocation() != null) { if (file.cachedLocation != null) {
if (!Paths.get(opts.packFolder, entry.getValue().getCachedLocation()).toFile().exists()) { if (!Paths.get(opts.packFolder, file.cachedLocation).toFile().exists()) {
invalid = true; invalid = true
} }
} else { } else {
// if cachedLocation == null, should probably be installed!! // if cachedLocation == null, should probably be installed!!
invalid = true; invalid = true
} }
} }
if (invalid) { if (invalid) {
SpaceSafeURI fileUri = entry.getKey(); println("File $fileUri invalidated, marked for redownloading")
System.out.println("File " + fileUri.toString() + " invalidated, marked for redownloading"); invalidatedUris.add(fileUri)
invalidatedUris.add(fileUri);
}
} }
} }
if (manifest.getPackFileHash() != null && packFileSource.hashIsEqual(manifest.getPackFileHash()) && invalidatedUris.isEmpty()) { if (manifest.packFileHash?.let { packFileSource.hashIsEqual(it) } == true && invalidatedUris.isEmpty()) {
System.out.println("Modpack is already up to date!"); println("Modpack is already up to date!")
// todo: --force? // todo: --force?
if (!stateHandler.getOptionsButton()) { if (!stateHandler.optionsButton) {
return; return
} }
} }
System.out.println("Modpack name: " + pf.getName()); println("Modpack name: " + pf.name)
if (stateHandler.getCancelButton()) { if (stateHandler.cancelButton) {
showCancellationDialog(); showCancellationDialog()
handleCancellation(); handleCancellation()
} }
try { try {
// This is badly written, I'll probably heavily refactor it at some point // This is badly written, I'll probably heavily refactor it at some point
processIndex(HandlerManager.getNewLoc(opts.downloadURI, Objects.requireNonNull(pf.getIndex()).getFile()), // The port to Kotlin made this REALLY messy!!!!
HashUtils.getHash(Objects.requireNonNull(pf.getIndex().getHashFormat()), Objects.requireNonNull(pf.getIndex().getHash())), pf.getIndex().getHashFormat(), manifest, invalidatedUris); getNewLoc(opts.downloadURI, Objects.requireNonNull(pf.index)!!.file)?.let {
} catch (Exception e1) { pf.index!!.hashFormat?.let { it1 ->
ui.handleExceptionAndExit(e1); 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 // 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 there were errors, don't write the manifest/index hashes, to ensure they are rechecked later
if (errorsOccurred) { if (errorsOccurred) {
manifest.setIndexFileHash(null); manifest.indexFileHash = null
manifest.setPackFileHash(null); manifest.packFileHash = null
} else { } else {
manifest.setPackFileHash(packFileSource.getHash()); manifest.packFileHash = packFileSource.hash
} }
manifest.setCachedSide(opts.side); manifest.cachedSide = opts.side
try (Writer writer = new FileWriter(Paths.get(opts.packFolder, opts.manifestFile).toString())) { try {
gson.toJson(manifest, writer); FileWriter(Paths.get(opts.packFolder, opts.manifestFile).toString()).use { writer -> gson.toJson(manifest, writer) }
} catch (IOException e) { } catch (e: IOException) {
// TODO: add message? // TODO: add message?
ui.handleException(e); ui.handleException(e)
}
} }
} private fun checkOptions() {
private void checkOptions() {
// TODO: implement // TODO: implement
} }
private void processIndex(SpaceSafeURI indexUri, Hash indexHash, String hashFormat, ManifestFile manifest, List<SpaceSafeURI> invalidatedUris) { private fun processIndex(indexUri: SpaceSafeURI, indexHash: Hash, hashFormat: String, manifest: ManifestFile, invalidatedUris: List<SpaceSafeURI>) {
if (manifest.getIndexFileHash() != null && manifest.getIndexFileHash().equals(indexHash) && invalidatedUris.isEmpty()) { if (manifest.indexFileHash == indexHash && invalidatedUris.isEmpty()) {
System.out.println("Modpack files are already up to date!"); println("Modpack files are already up to date!")
if (!stateHandler.getOptionsButton()) { if (!stateHandler.optionsButton) {
return; return
} }
} }
manifest.setIndexFileHash(indexHash); manifest.indexFileHash = indexHash
GeneralHashingSource indexFileSource; val indexFileSource = try {
try { val src = getFileSource(indexUri)
Source src = HandlerManager.getFileSource(indexUri); getHasher(hashFormat).getHashingSource(src)
indexFileSource = HashUtils.getHasher(hashFormat).getHashingSource(src); } catch (e: Exception) {
} catch (Exception e) { // TODO: run cancellation window?
// TODO: still launch the game if updating doesn't work? ui.handleExceptionAndExit(e)
// TODO: ask user if they want to launch the game, exit(1) if they don't return
ui.handleExceptionAndExit(e);
return;
} }
IndexFile indexFile; val indexFile = try {
try { Toml().read(indexFileSource.buffer().inputStream()).to(IndexFile::class.java)
indexFile = new Toml().read(Okio.buffer(indexFileSource).inputStream()).to(IndexFile.class); } catch (e: IllegalStateException) {
} catch (IllegalStateException e) { ui.handleExceptionAndExit(e)
ui.handleExceptionAndExit(e); return
return;
} }
if (!indexFileSource.hashIsEqual(indexHash)) { if (!indexFileSource.hashIsEqual(indexHash)) {
// TODO: throw exception // TODO: throw exception
System.out.println("I was meant to put an error message here but I'll do that later"); println("I was meant to put an error message here but I'll do that later")
return; return
}
if (stateHandler.cancelButton) {
showCancellationDialog()
return
} }
if (stateHandler.getCancelButton()) { ui.submitProgress(InstallProgress("Checking local files..."))
showCancellationDialog(); // TODO: use kotlin filtering/FP rather than an iterator?
return; val it: MutableIterator<Map.Entry<SpaceSafeURI, ManifestFile.File>> = manifest.cachedFiles.entries.iterator()
}
ui.submitProgress(new InstallProgress("Checking local files..."));
Iterator<Map.Entry<SpaceSafeURI, ManifestFile.File>> it = manifest.getCachedFiles().entrySet().iterator();
while (it.hasNext()) { while (it.hasNext()) {
Map.Entry<SpaceSafeURI, ManifestFile.File> entry = it.next(); val (uri, file) = it.next()
if (entry.getValue().getCachedLocation() != null) { if (file.cachedLocation != null) {
boolean alreadyDeleted = false; var alreadyDeleted = false
// Delete if option value has been set to false // Delete if option value has been set to false
if (entry.getValue().isOptional() && !entry.getValue().getOptionValue()) { if (file.isOptional && !file.optionValue) {
try { try {
Files.deleteIfExists(Paths.get(opts.packFolder, entry.getValue().getCachedLocation())); Files.deleteIfExists(Paths.get(opts.packFolder, file.cachedLocation))
} catch (IOException e) { } catch (e: IOException) {
// TODO: should this be shown to the user in some way? // TODO: should this be shown to the user in some way?
e.printStackTrace(); e.printStackTrace()
} }
// Set to null, as it doesn't exist anymore // Set to null, as it doesn't exist anymore
entry.getValue().setCachedLocation(null); file.cachedLocation = null
alreadyDeleted = true; alreadyDeleted = true
} }
if (indexFile.getFiles().stream().noneMatch(f -> Objects.equals(f.getFile(), entry.getKey()))) { if (indexFile.files.none { it.file == uri }) { // File has been removed from the index
// File has been removed from the index
if (!alreadyDeleted) { if (!alreadyDeleted) {
try { try {
Files.deleteIfExists(Paths.get(opts.packFolder, entry.getValue().getCachedLocation())); Files.deleteIfExists(Paths.get(opts.packFolder, file.cachedLocation))
} catch (IOException e) { } catch (e: IOException) { // TODO: should this be shown to the user in some way?
// TODO: should this be shown to the user in some way? e.printStackTrace()
e.printStackTrace();
} }
} }
it.remove(); it.remove()
} }
} }
} }
if (stateHandler.getCancelButton()) { if (stateHandler.cancelButton) {
showCancellationDialog(); showCancellationDialog()
return; return
} }
ui.submitProgress(new InstallProgress("Comparing new files...")); ui.submitProgress(InstallProgress("Comparing new files..."))
// TODO: progress bar? // TODO: progress bar?
if (indexFile.getFiles().isEmpty()) { if (indexFile.files.isEmpty()) {
System.out.println("Warning: Index is empty!"); println("Warning: Index is empty!")
} }
List<DownloadTask> 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 // If the side changes, invalidate EVERYTHING just in case
// Might not be needed, but done just to be safe // Might not be needed, but done just to be safe
boolean invalidateAll = !opts.side.equals(manifest.getCachedSide()); val invalidateAll = opts.side != manifest.cachedSide
if (invalidateAll) { 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? // TODO: should linkedfile be checked as well? should this be done in the download section?
if (invalidateAll) { if (invalidateAll) {
f.invalidate(); f.invalidate()
} else if (invalidatedUris.contains(f.metadata.getFile())) { } else if (invalidatedUris.contains(f.metadata.file)) {
f.invalidate(); f.invalidate()
} }
ManifestFile.File file = manifest.getCachedFiles().get(f.metadata.getFile()); val file = manifest.cachedFiles[f.metadata.file]
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 // 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(); file?.backup()
}
// If it is null, the DownloadTask will make a new empty cachedFile // If it is null, the DownloadTask will make a new empty cachedFile
f.updateFromCache(file); f.updateFromCache(file)
}); }
if (stateHandler.getCancelButton()) { if (stateHandler.cancelButton) {
showCancellationDialog(); showCancellationDialog()
return; return
} }
// Let's hope downloadMetadata is a pure function!!! // 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<IExceptionDetails> failedTasks = tasks.stream().filter(t -> t.getException() != null).collect(Collectors.toList()); val failedTaskDetails = tasks.asSequence().map(DownloadTask::exceptionDetails).filterNotNull().toList()
if (!failedTasks.isEmpty()) { if (failedTaskDetails.isNotEmpty()) {
errorsOccurred = true; errorsOccurred = true
IExceptionDetails.ExceptionListResult exceptionListResult; val exceptionListResult: ExceptionListResult
try { exceptionListResult = try {
exceptionListResult = ui.showExceptions(failedTasks, tasks.size(), true).get(); ui.showExceptions(failedTaskDetails, tasks.size, true).get()
} catch (InterruptedException | ExecutionException e) { } catch (e: InterruptedException) { // Interrupted means cancelled???
// Interrupted means cancelled??? ui.handleExceptionAndExit(e)
ui.handleExceptionAndExit(e); return
return; } catch (e: ExecutionException) {
ui.handleExceptionAndExit(e)
return
}
when (exceptionListResult) {
ExceptionListResult.CONTINUE -> {}
ExceptionListResult.CANCEL -> {
cancelled = true
return
}
ExceptionListResult.IGNORE -> {
cancelledStartGame = true
return
} }
switch (exceptionListResult) {
case CONTINUE:
break;
case CANCEL:
cancelled = true;
return;
case IGNORE:
cancelledStartGame = true;
return;
} }
} }
if (stateHandler.getCancelButton()) { if (stateHandler.cancelButton) {
showCancellationDialog(); showCancellationDialog()
return; return
} }
List<DownloadTask> nonFailedFirstTasks = tasks.stream().filter(t -> t.getException() == null).collect(Collectors.toList()); // TODO: task failed function?
List<DownloadTask> optionTasks = nonFailedFirstTasks.stream().filter(DownloadTask::correctSide).filter(DownloadTask::isOptional).collect(Collectors.toList()); 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 options changed, present all options again
if (stateHandler.getOptionsButton() || optionTasks.stream().anyMatch(DownloadTask::isNewOptional)) { if (stateHandler.optionsButton || optionTasks.any(DownloadTask::isNewOptional)) {
// new ArrayList is requires so it's an IOptionDetails rather than a DownloadTask list // new ArrayList is required so it's an IOptionDetails rather than a DownloadTask list
Future<Boolean> cancelledResult = ui.showOptions(new ArrayList<>(optionTasks)); val cancelledResult = ui.showOptions(ArrayList(optionTasks))
try { try {
if (cancelledResult.get()) { if (cancelledResult.get()) {
cancelled = true; cancelled = true
// TODO: Should the UI be closed somehow?? // TODO: Should the UI be closed somehow??
return; return
} }
} catch (InterruptedException | ExecutionException e) { } catch (e: InterruptedException) {
// Interrupted means cancelled??? // Interrupted means cancelled???
ui.handleExceptionAndExit(e); ui.handleExceptionAndExit(e)
} catch (e: ExecutionException) {
ui.handleExceptionAndExit(e)
} }
} }
ui.disableOptionsButton(); ui.disableOptionsButton()
// TODO: different thread pool type? // TODO: different thread pool type?
ExecutorService threadPool = Executors.newFixedThreadPool(10); val threadPool = Executors.newFixedThreadPool(10)
CompletionService<DownloadTask> completionService = new ExecutorCompletionService<>(threadPool); val completionService: CompletionService<DownloadTask> = ExecutorCompletionService(threadPool)
tasks.forEach { t ->
tasks.forEach(t -> completionService.submit(() -> { completionService.submit {
t.download(opts.packFolder, indexUri); t.download(opts.packFolder, indexUri)
return t; t
})); }
}
for (int i = 0; i < tasks.size(); i++) { for (i in tasks.indices) {
DownloadTask task; var task: DownloadTask?
try { task = try {
task = completionService.take().get(); completionService.take().get()
} catch (InterruptedException | ExecutionException e) { } catch (e: InterruptedException) {
ui.handleException(e); ui.handleException(e)
task = null; 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) // Update manifest - If there were no errors cachedFile has already been modified in place (good old pass by reference)
if (task != null) { task?.cachedFile?.let { file ->
if (task.getException() != null) { if (task.failed()) {
ManifestFile.File file = task.cachedFile.getRevert(); val oldFile = file.revert
if (file != null) { if (oldFile != null) {
manifest.getCachedFiles().putIfAbsent(task.metadata.getFile(), file); task.metadata.file?.let { uri -> manifest.cachedFiles.putIfAbsent(uri, oldFile) }
} } else { null }
} else { } else {
// idiot, if it wasn't there in the first place it won't magically appear there task.metadata.file?.let { uri -> manifest.cachedFiles.putIfAbsent(uri, file) }
manifest.getCachedFiles().putIfAbsent(task.metadata.getFile(), task.cachedFile);
} }
} }
String progress; var progress: String
if (task != null) { if (task != null) {
if (task.getException() != null) { val exDetails = task.exceptionDetails
progress = "Failed to download " + task.metadata.getName() + ": " + task.getException().getMessage(); if (exDetails != null) {
task.getException().printStackTrace(); progress = "Failed to download ${exDetails.name}: ${exDetails.exception.message}"
exDetails.exception.printStackTrace()
} else { } else {
// TODO: should this be revised for tasks that didn't actually download it? progress = "Downloaded ${task.name}"
progress = "Downloaded " + task.metadata.getName();
} }
} else { } 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()) { if (stateHandler.cancelButton) { // Stop all tasks, don't launch the game (it's in an invalid state!)
// Stop all tasks, don't launch the game (it's in an invalid state!) threadPool.shutdown()
threadPool.shutdown(); cancelled = true
cancelled = true; return
return;
} }
} }
// Shut down the thread pool when the update is done // Shut down the thread pool when the update is done
threadPool.shutdown(); threadPool.shutdown()
List<IExceptionDetails> failedTasks2ElectricBoogaloo = nonFailedFirstTasks.stream().filter(t -> t.getException() != null).collect(Collectors.toList()); val failedTasks2ElectricBoogaloo = nonFailedFirstTasks.asSequence().map(DownloadTask::exceptionDetails).filterNotNull().toList()
if (!failedTasks2ElectricBoogaloo.isEmpty()) { if (failedTasks2ElectricBoogaloo.isNotEmpty()) {
errorsOccurred = true; errorsOccurred = true
IExceptionDetails.ExceptionListResult exceptionListResult; val exceptionListResult: ExceptionListResult
try { exceptionListResult = try {
exceptionListResult = ui.showExceptions(failedTasks2ElectricBoogaloo, tasks.size(), false).get(); ui.showExceptions(failedTasks2ElectricBoogaloo, tasks.size, false).get()
} catch (InterruptedException | ExecutionException e) { } catch (e: InterruptedException) {
// Interrupted means cancelled??? // Interrupted means cancelled???
ui.handleExceptionAndExit(e); ui.handleExceptionAndExit(e)
return; return
} catch (e: ExecutionException) {
ui.handleExceptionAndExit(e)
return
} }
switch (exceptionListResult) { when (exceptionListResult) {
case CONTINUE: ExceptionListResult.CONTINUE -> {}
break; ExceptionListResult.CANCEL -> cancelled = true
case CANCEL: ExceptionListResult.IGNORE -> cancelledStartGame = true
cancelled = true;
return;
case IGNORE:
cancelledStartGame = true;
} }
} }
} }
private void showCancellationDialog() { private fun showCancellationDialog() {
IExceptionDetails.ExceptionListResult exceptionListResult; val cancellationResult: CancellationResult
try { cancellationResult = try {
exceptionListResult = ui.showCancellationDialog().get(); ui.showCancellationDialog().get()
} catch (InterruptedException | ExecutionException e) { } catch (e: InterruptedException) {
// Interrupted means cancelled??? // Interrupted means cancelled???
ui.handleExceptionAndExit(e); ui.handleExceptionAndExit(e)
return; return
} catch (e: ExecutionException) {
ui.handleExceptionAndExit(e)
return
} }
switch (exceptionListResult) { when (cancellationResult) {
case CONTINUE: CancellationResult.QUIT -> cancelled = true
throw new RuntimeException("Continuation not allowed here!"); CancellationResult.CONTINUE -> cancelledStartGame = true
case CANCEL:
cancelled = true;
return;
case IGNORE:
cancelledStartGame = true;
} }
} }
private void handleCancellation() { private fun handleCancellation() {
if (cancelled) { if (cancelled) {
System.out.println("Update cancelled by user!"); println("Update cancelled by user!")
System.exit(1); exitProcess(1)
} else if (cancelledStartGame) { } else if (cancelledStartGame) {
System.out.println("Update cancelled by user! Continuing to start game..."); println("Update cancelled by user! Continuing to start game...")
System.exit(0); exitProcess(0)
} }
} }
} }

View File

@ -74,10 +74,11 @@ class IndexFile {
} }
// TODO: throw some kind of exception? // TODO: throw some kind of exception?
val name: String? val name: String
get() { get() {
if (metafile) { 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" return file?.run { Paths.get(path).fileName.toString() } ?: "Invalid file"
} }

View File

@ -1,6 +1,6 @@
package link.infra.packwiz.installer.ui 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.CompletableFuture
import java.util.concurrent.Future import java.util.concurrent.Future
@ -39,7 +39,7 @@ class CLIHandler : IUserInterface {
} }
} }
override fun showExceptions(exceptions: List<IExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): Future<ExceptionListResult> { override fun showExceptions(exceptions: List<ExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): Future<ExceptionListResult> {
val future = CompletableFuture<ExceptionListResult>() val future = CompletableFuture<ExceptionListResult>()
future.complete(ExceptionListResult.CANCEL) future.complete(ExceptionListResult.CANCEL)
return future return future

View File

@ -0,0 +1,6 @@
package link.infra.packwiz.installer.ui
data class ExceptionDetails(
val name: String,
val exception: Exception
)

View File

@ -1,6 +1,5 @@
package link.infra.packwiz.installer.ui package link.infra.packwiz.installer.ui
import link.infra.packwiz.installer.ui.IExceptionDetails.ExceptionListResult
import java.awt.BorderLayout import java.awt.BorderLayout
import java.awt.Desktop import java.awt.Desktop
import java.awt.event.WindowAdapter import java.awt.event.WindowAdapter
@ -14,10 +13,10 @@ import java.util.concurrent.CompletableFuture
import javax.swing.* import javax.swing.*
import javax.swing.border.EmptyBorder import javax.swing.border.EmptyBorder
internal class ExceptionListWindow(eList: List<IExceptionDetails>, future: CompletableFuture<ExceptionListResult>, numTotal: Int, allowsIgnore: Boolean, parentWindow: JFrame?) : JDialog(parentWindow, "Failed file downloads", true) { class ExceptionListWindow(eList: List<ExceptionDetails>, future: CompletableFuture<IUserInterface.ExceptionListResult>, numTotal: Int, allowsIgnore: Boolean, parentWindow: JFrame?) : JDialog(parentWindow, "Failed file downloads", true) {
private val lblExceptionStacktrace: JTextArea private val lblExceptionStacktrace: JTextArea
private class ExceptionListModel internal constructor(private val details: List<IExceptionDetails>) : AbstractListModel<String>() { private class ExceptionListModel internal constructor(private val details: List<ExceptionDetails>) : AbstractListModel<String>() {
override fun getSize() = details.size override fun getSize() = details.size
override fun getElementAt(index: Int) = details[index].name override fun getElementAt(index: Int) = details[index].name
fun getExceptionAt(index: Int) = details[index].exception fun getExceptionAt(index: Int) = details[index].exception
@ -90,7 +89,7 @@ internal class ExceptionListWindow(eList: List<IExceptionDetails>, future: Compl
add(JButton("Continue").apply { add(JButton("Continue").apply {
toolTipText = "Attempt to continue installing, excluding the failed downloads" toolTipText = "Attempt to continue installing, excluding the failed downloads"
addActionListener { addActionListener {
future.complete(ExceptionListResult.CONTINUE) future.complete(IUserInterface.ExceptionListResult.CONTINUE)
this@ExceptionListWindow.dispose() this@ExceptionListWindow.dispose()
} }
}) })
@ -98,7 +97,7 @@ internal class ExceptionListWindow(eList: List<IExceptionDetails>, future: Compl
add(JButton("Cancel launch").apply { add(JButton("Cancel launch").apply {
toolTipText = "Stop launching the game" toolTipText = "Stop launching the game"
addActionListener { addActionListener {
future.complete(ExceptionListResult.CANCEL) future.complete(IUserInterface.ExceptionListResult.CANCEL)
this@ExceptionListWindow.dispose() this@ExceptionListWindow.dispose()
} }
}) })
@ -107,7 +106,7 @@ internal class ExceptionListWindow(eList: List<IExceptionDetails>, future: Compl
toolTipText = "Start the game without attempting to update" toolTipText = "Start the game without attempting to update"
isEnabled = allowsIgnore isEnabled = allowsIgnore
addActionListener { addActionListener {
future.complete(ExceptionListResult.IGNORE) future.complete(IUserInterface.ExceptionListResult.IGNORE)
this@ExceptionListWindow.dispose() this@ExceptionListWindow.dispose()
} }
}) })
@ -139,13 +138,13 @@ internal class ExceptionListWindow(eList: List<IExceptionDetails>, future: Compl
addWindowListener(object : WindowAdapter() { addWindowListener(object : WindowAdapter() {
override fun windowClosing(e: WindowEvent) { override fun windowClosing(e: WindowEvent) {
future.complete(ExceptionListResult.CANCEL) future.complete(IUserInterface.ExceptionListResult.CANCEL)
} }
override fun windowClosed(e: WindowEvent) { override fun windowClosed(e: WindowEvent) {
// Just in case closing didn't get triggered - if something else called dispose() the // Just in case closing didn't get triggered - if something else called dispose() the
// future will have already completed // future will have already completed
future.complete(ExceptionListResult.CANCEL) future.complete(IUserInterface.ExceptionListResult.CANCEL)
} }
}) })
} }

View File

@ -1,10 +0,0 @@
package link.infra.packwiz.installer.ui
interface IExceptionDetails {
val exception: Exception
val name: String
enum class ExceptionListResult {
CONTINUE, CANCEL, IGNORE
}
}

View File

@ -1,6 +1,5 @@
package link.infra.packwiz.installer.ui package link.infra.packwiz.installer.ui
import link.infra.packwiz.installer.ui.IExceptionDetails.ExceptionListResult
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.Future import java.util.concurrent.Future
import kotlin.system.exitProcess import kotlin.system.exitProcess
@ -21,14 +20,22 @@ interface IUserInterface {
// Return true if the installation was cancelled! // Return true if the installation was cancelled!
fun showOptions(options: List<IOptionDetails>): Future<Boolean> fun showOptions(options: List<IOptionDetails>): Future<Boolean>
fun showExceptions(exceptions: List<IExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): Future<ExceptionListResult> fun showExceptions(exceptions: List<ExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): Future<ExceptionListResult>
@JvmDefault @JvmDefault
fun disableOptionsButton() {} fun disableOptionsButton() {}
// Should not return CONTINUE
@JvmDefault @JvmDefault
fun showCancellationDialog(): Future<ExceptionListResult> { fun showCancellationDialog(): Future<CancellationResult> {
return CompletableFuture<ExceptionListResult>().apply { return CompletableFuture<CancellationResult>().apply {
complete(ExceptionListResult.CANCEL) complete(CancellationResult.QUIT)
} }
} }
enum class ExceptionListResult {
CONTINUE, CANCEL, IGNORE
}
enum class CancellationResult {
QUIT, CONTINUE
}
} }

View File

@ -1,6 +1,6 @@
package link.infra.packwiz.installer.ui 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.awt.*
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.Future import java.util.concurrent.Future
@ -186,7 +186,7 @@ class InstallWindow : IUserInterface {
return future return future
} }
override fun showExceptions(exceptions: List<IExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): Future<ExceptionListResult> { override fun showExceptions(exceptions: List<ExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): Future<ExceptionListResult> {
val future = CompletableFuture<ExceptionListResult>() val future = CompletableFuture<ExceptionListResult>()
EventQueue.invokeLater { EventQueue.invokeLater {
ExceptionListWindow(exceptions, future, numTotal, allowsIgnore, frmPackwizlauncher).apply { ExceptionListWindow(exceptions, future, numTotal, allowsIgnore, frmPackwizlauncher).apply {
@ -204,15 +204,15 @@ class InstallWindow : IUserInterface {
} }
} }
override fun showCancellationDialog(): Future<ExceptionListResult> { override fun showCancellationDialog(): Future<IUserInterface.CancellationResult> {
val future = CompletableFuture<ExceptionListResult>() val future = CompletableFuture<IUserInterface.CancellationResult>()
EventQueue.invokeLater { EventQueue.invokeLater {
val buttons = arrayOf("Quit", "Ignore") val buttons = arrayOf("Quit", "Ignore")
val result = JOptionPane.showOptionDialog(frmPackwizlauncher, val result = JOptionPane.showOptionDialog(frmPackwizlauncher,
"The installation was cancelled. Would you like to quit the game, or ignore the update and start the game?", "The installation was cancelled. Would you like to quit the game, or ignore the update and start the game?",
"Cancelled installation", "Cancelled installation",
JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, buttons, buttons[0]) 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 return future
} }