mirror of
https://github.com/packwiz/packwiz-installer.git
synced 2025-10-16 16:04:32 +02:00
Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
bca2d758e1 | ||
|
46771ce870 | ||
|
b143f67acd | ||
|
03b0f1b09b | ||
|
6c6a0100fd | ||
|
6d47c0d61f | ||
|
226e754547 | ||
|
2c02703101 | ||
|
81a60cc759 | ||
|
92afa93fd7 | ||
|
0858c90079 | ||
|
1d4c94f5b6 | ||
|
74ddca5d54 | ||
|
0df48d19a9 | ||
|
f5b22f37a4 | ||
|
f52cd19ad4 | ||
|
60887a4312 | ||
|
a368268038 |
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2019
|
Copyright (c) 2021 comp500
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@@ -1,23 +1,36 @@
|
|||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath("com.guardsquare:proguard-gradle:7.0.0") {
|
||||||
|
exclude("com.android.tools.build")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
java
|
java
|
||||||
application
|
application
|
||||||
id("com.github.johnrengelman.shadow") version "5.0.0"
|
id("com.github.johnrengelman.shadow") version "6.1.0"
|
||||||
id("com.palantir.git-version") version "0.11.0"
|
id("com.palantir.git-version") version "0.12.3"
|
||||||
id("com.github.breadmoirai.github-release") version "2.2.9"
|
id("com.github.breadmoirai.github-release") version "2.2.12"
|
||||||
kotlin("jvm") version "1.3.61"
|
kotlin("jvm") version "1.4.21"
|
||||||
|
id("com.github.jk1.dependency-license-report") version "1.16"
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val shrinkClasspath: Configuration by configurations.creating
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("commons-cli:commons-cli:1.4")
|
implementation("commons-cli:commons-cli:1.4")
|
||||||
|
shrinkClasspath("commons-cli:commons-cli:1.4")
|
||||||
implementation("com.moandjiezana.toml:toml4j:0.7.2")
|
implementation("com.moandjiezana.toml:toml4j:0.7.2")
|
||||||
// TODO: Implement tests
|
|
||||||
//testImplementation "junit:junit:4.12"
|
|
||||||
implementation("com.google.code.gson:gson:2.8.1")
|
implementation("com.google.code.gson:gson:2.8.1")
|
||||||
implementation("com.squareup.okio:okio:2.2.2")
|
implementation("com.squareup.okio:okio:2.9.0")
|
||||||
implementation(kotlin("stdlib-jdk8"))
|
implementation(kotlin("stdlib-jdk8"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,17 +52,53 @@ tasks.jar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
licenseReport {
|
||||||
|
renderers = arrayOf<com.github.jk1.license.render.ReportRenderer>(
|
||||||
|
com.github.jk1.license.render.InventoryMarkdownReportRenderer("licenses.md", "packwiz-installer")
|
||||||
|
)
|
||||||
|
filters = arrayOf<com.github.jk1.license.filter.DependencyFilter>(com.github.jk1.license.filter.LicenseBundleNormalizer())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: build relocated jar for minecraft launcher lib, non-relocated jar for packwiz-installer
|
||||||
|
//tasks.register<com.github.jengelman.gradle.plugins.shadow.tasks.ConfigureShadowRelocation>("relocateShadowJar") {
|
||||||
|
// target = tasks.shadowJar.get()
|
||||||
|
// prefix = "link.infra.packwiz.deps"
|
||||||
|
//}
|
||||||
|
|
||||||
// Commons CLI and Minimal JSON are already included in packwiz-installer-bootstrap
|
// Commons CLI and Minimal JSON are already included in packwiz-installer-bootstrap
|
||||||
tasks.shadowJar {
|
tasks.shadowJar {
|
||||||
dependencies {
|
dependencies {
|
||||||
exclude(dependency("commons-cli:commons-cli:1.4"))
|
exclude(dependency("commons-cli:commons-cli:1.4"))
|
||||||
exclude(dependency("com.eclipsesource.minimal-json:minimal-json:0.9.5"))
|
exclude(dependency("com.eclipsesource.minimal-json:minimal-json:0.9.5"))
|
||||||
|
// TODO: exclude meta inf files
|
||||||
}
|
}
|
||||||
|
exclude("**/*.kotlin_metadata")
|
||||||
|
exclude("**/*.kotlin_builtins")
|
||||||
|
exclude("META-INF/maven/**/*")
|
||||||
|
exclude("META-INF/proguard/**/*")
|
||||||
|
//dependsOn(tasks.named("relocateShadowJar"))
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register<proguard.gradle.ProGuardTask>("shrinkJar") {
|
||||||
|
injars(tasks.shadowJar)
|
||||||
|
libraryjars(files(shrinkClasspath.files))
|
||||||
|
outjars("build/libs/" + tasks.shadowJar.get().outputs.files.first().name.removeSuffix(".jar") + "-shrink.jar")
|
||||||
|
if (System.getProperty("java.version").startsWith("1.")) {
|
||||||
|
libraryjars("${System.getProperty("java.home")}/lib/rt.jar")
|
||||||
|
libraryjars("${System.getProperty("java.home")}/lib/jce.jar")
|
||||||
|
} else {
|
||||||
|
throw RuntimeException("Compiling with Java 9+ not supported!")
|
||||||
|
}
|
||||||
|
|
||||||
|
keep("class link.infra.packwiz.installer.** { *; }")
|
||||||
|
dontoptimize()
|
||||||
|
dontobfuscate()
|
||||||
|
dontwarn("org.codehaus.mojo.animal_sniffer.*")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used for vscode launch.json
|
// Used for vscode launch.json
|
||||||
tasks.register<Copy>("copyJar") {
|
tasks.register<Copy>("copyJar") {
|
||||||
from(tasks.shadowJar)
|
from(tasks.named("shrinkJar"))
|
||||||
rename("packwiz-installer-(.*)\\.jar", "packwiz-installer.jar")
|
rename("packwiz-installer-(.*)\\.jar", "packwiz-installer.jar")
|
||||||
into("build/libs/")
|
into("build/libs/")
|
||||||
}
|
}
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@@ -6,8 +6,9 @@ import link.infra.packwiz.installer.metadata.SpaceSafeURI
|
|||||||
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.getHash
|
||||||
import link.infra.packwiz.installer.metadata.hash.HashUtils.getHasher
|
import link.infra.packwiz.installer.metadata.hash.HashUtils.getHasher
|
||||||
import link.infra.packwiz.installer.ui.ExceptionDetails
|
import link.infra.packwiz.installer.ui.data.ExceptionDetails
|
||||||
import link.infra.packwiz.installer.ui.IOptionDetails
|
import link.infra.packwiz.installer.ui.data.IOptionDetails
|
||||||
|
import link.infra.packwiz.installer.util.Log
|
||||||
import okio.Buffer
|
import okio.Buffer
|
||||||
import okio.HashingSink
|
import okio.HashingSink
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
@@ -125,6 +126,7 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, de
|
|||||||
fun download(packFolder: String, indexUri: SpaceSafeURI) {
|
fun download(packFolder: String, indexUri: SpaceSafeURI) {
|
||||||
if (err != null) return
|
if (err != null) return
|
||||||
|
|
||||||
|
// TODO: is this necessary if we overwrite?
|
||||||
// Ensure it is removed
|
// Ensure it is removed
|
||||||
cachedFile?.let {
|
cachedFile?.let {
|
||||||
if (!it.optionValue || !correctSide()) {
|
if (!it.optionValue || !correctSide()) {
|
||||||
@@ -133,8 +135,7 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, de
|
|||||||
try {
|
try {
|
||||||
Files.deleteIfExists(Paths.get(packFolder, it.cachedLocation))
|
Files.deleteIfExists(Paths.get(packFolder, it.cachedLocation))
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
// TODO: how much of a problem is this? use log4j/other log library to show warning?
|
Log.warn("Failed to delete file before downloading", e)
|
||||||
e.printStackTrace()
|
|
||||||
}
|
}
|
||||||
it.cachedLocation = null
|
it.cachedLocation = null
|
||||||
}
|
}
|
||||||
@@ -152,6 +153,9 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, de
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: if already exists and has correct hash, ignore?
|
||||||
|
// TODO: add .disabled support?
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val hash: Hash
|
val hash: Hash
|
||||||
val fileHashFormat: String
|
val fileHashFormat: String
|
||||||
@@ -176,11 +180,18 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, de
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fileSource.hashIsEqual(hash)) {
|
if (fileSource.hashIsEqual(hash)) {
|
||||||
Files.createDirectories(destPath.parent)
|
// isDirectory follows symlinks, but createDirectories doesn't
|
||||||
|
try {
|
||||||
|
Files.createDirectories(destPath.parent)
|
||||||
|
} catch (e: java.nio.file.FileAlreadyExistsException) {
|
||||||
|
if (!Files.isDirectory(destPath.parent)) {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
Files.copy(data.inputStream(), destPath, StandardCopyOption.REPLACE_EXISTING)
|
Files.copy(data.inputStream(), destPath, StandardCopyOption.REPLACE_EXISTING)
|
||||||
data.clear()
|
data.clear()
|
||||||
} else {
|
} else {
|
||||||
// TODO: no more PRINTLN!!!!!!!!!
|
// TODO: move println to something visible in the error window
|
||||||
println("Invalid hash for " + metadata.destURI.toString())
|
println("Invalid hash for " + metadata.destURI.toString())
|
||||||
println("Calculated: " + fileSource.hash)
|
println("Calculated: " + fileSource.hash)
|
||||||
println("Expected: $hash")
|
println("Expected: $hash")
|
||||||
|
@@ -3,9 +3,9 @@
|
|||||||
package link.infra.packwiz.installer
|
package link.infra.packwiz.installer
|
||||||
|
|
||||||
import link.infra.packwiz.installer.metadata.SpaceSafeURI
|
import link.infra.packwiz.installer.metadata.SpaceSafeURI
|
||||||
import link.infra.packwiz.installer.ui.CLIHandler
|
import link.infra.packwiz.installer.ui.cli.CLIHandler
|
||||||
import link.infra.packwiz.installer.ui.InputStateHandler
|
import link.infra.packwiz.installer.ui.gui.GUIHandler
|
||||||
import link.infra.packwiz.installer.ui.InstallWindow
|
import link.infra.packwiz.installer.util.Log
|
||||||
import org.apache.commons.cli.DefaultParser
|
import org.apache.commons.cli.DefaultParser
|
||||||
import org.apache.commons.cli.Options
|
import org.apache.commons.cli.Options
|
||||||
import org.apache.commons.cli.ParseException
|
import org.apache.commons.cli.ParseException
|
||||||
@@ -19,7 +19,7 @@ import kotlin.system.exitProcess
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
class Main(args: Array<String>) {
|
class Main(args: Array<String>) {
|
||||||
// Don't attempt to start a GUI if we are headless
|
// Don't attempt to start a GUI if we are headless
|
||||||
var guiEnabled = !GraphicsEnvironment.isHeadless()
|
private var guiEnabled = !GraphicsEnvironment.isHeadless()
|
||||||
|
|
||||||
private fun startup(args: Array<String>) {
|
private fun startup(args: Array<String>) {
|
||||||
val options = Options()
|
val options = Options()
|
||||||
@@ -30,7 +30,7 @@ class Main(args: Array<String>) {
|
|||||||
val cmd = try {
|
val cmd = try {
|
||||||
parser.parse(options, args)
|
parser.parse(options, args)
|
||||||
} catch (e: ParseException) {
|
} catch (e: ParseException) {
|
||||||
e.printStackTrace()
|
Log.fatal("Failed to parse command line arguments", e)
|
||||||
if (guiEnabled) {
|
if (guiEnabled) {
|
||||||
EventQueue.invokeAndWait {
|
EventQueue.invokeAndWait {
|
||||||
try {
|
try {
|
||||||
@@ -38,7 +38,8 @@ class Main(args: Array<String>) {
|
|||||||
} catch (ignored: Exception) {
|
} catch (ignored: Exception) {
|
||||||
// Ignore the exceptions, just continue using the ugly L&F
|
// Ignore the exceptions, just continue using the ugly L&F
|
||||||
}
|
}
|
||||||
JOptionPane.showMessageDialog(null, e.message, "packwiz-installer", JOptionPane.ERROR_MESSAGE)
|
JOptionPane.showMessageDialog(null, "Failed to parse command line arguments: $e",
|
||||||
|
"packwiz-installer", JOptionPane.ERROR_MESSAGE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
@@ -48,46 +49,41 @@ class Main(args: Array<String>) {
|
|||||||
guiEnabled = false
|
guiEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
val ui = if (guiEnabled) InstallWindow() else CLIHandler()
|
val ui = if (guiEnabled) GUIHandler() else CLIHandler()
|
||||||
|
|
||||||
val unparsedArgs = cmd.args
|
val unparsedArgs = cmd.args
|
||||||
if (unparsedArgs.size > 1) {
|
if (unparsedArgs.size > 1) {
|
||||||
ui.handleExceptionAndExit(RuntimeException("Too many arguments specified!"))
|
ui.showErrorAndExit("Too many arguments specified!")
|
||||||
} else if (unparsedArgs.isEmpty()) {
|
} else if (unparsedArgs.isEmpty()) {
|
||||||
ui.handleExceptionAndExit(RuntimeException("URI to install from must be specified!"))
|
ui.showErrorAndExit("pack.toml URI to install from must be specified!")
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.getOptionValue("title")?.also(ui::setTitle)
|
val title = cmd.getOptionValue("title")
|
||||||
|
if (title != null) {
|
||||||
val inputStateHandler = InputStateHandler()
|
ui.title = title
|
||||||
ui.show(inputStateHandler)
|
|
||||||
|
|
||||||
val uOptions = UpdateManager.Options().apply {
|
|
||||||
side = cmd.getOptionValue("side")?.let((UpdateManager.Options.Side)::from) ?: side
|
|
||||||
packFolder = cmd.getOptionValue("pack-folder") ?: packFolder
|
|
||||||
manifestFile = cmd.getOptionValue("meta-file") ?: manifestFile
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
ui.show()
|
||||||
uOptions.downloadURI = SpaceSafeURI(unparsedArgs[0])
|
|
||||||
|
val uOptions = try {
|
||||||
|
UpdateManager.Options.construct(
|
||||||
|
downloadURI = SpaceSafeURI(unparsedArgs[0]),
|
||||||
|
side = cmd.getOptionValue("side")?.let((UpdateManager.Options.Side)::from),
|
||||||
|
packFolder = cmd.getOptionValue("pack-folder"),
|
||||||
|
manifestFile = cmd.getOptionValue("meta-file")
|
||||||
|
)
|
||||||
} catch (e: URISyntaxException) {
|
} catch (e: URISyntaxException) {
|
||||||
// TODO: better error message?
|
ui.showErrorAndExit("Failed to read pack.toml URI", e)
|
||||||
ui.handleExceptionAndExit(e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start update process!
|
// Start update process!
|
||||||
// TODO: start in SwingWorker?
|
|
||||||
try {
|
try {
|
||||||
ui.executeManager {
|
UpdateManager(uOptions, ui)
|
||||||
try {
|
} catch (e: Exception) {
|
||||||
UpdateManager(uOptions, ui, inputStateHandler)
|
ui.showErrorAndExit("Update process failed", e)
|
||||||
} catch (e: Exception) { // TODO: better error message?
|
|
||||||
ui.handleExceptionAndExit(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) { // TODO: better error message?
|
|
||||||
ui.handleExceptionAndExit(e)
|
|
||||||
}
|
}
|
||||||
|
println("Finished successfully!")
|
||||||
|
ui.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -118,17 +114,17 @@ class Main(args: Array<String>) {
|
|||||||
try {
|
try {
|
||||||
startup(args)
|
startup(args)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
Log.fatal("Error from main", e)
|
||||||
if (guiEnabled) {
|
if (guiEnabled) {
|
||||||
EventQueue.invokeLater {
|
EventQueue.invokeLater {
|
||||||
JOptionPane.showMessageDialog(null,
|
JOptionPane.showMessageDialog(null,
|
||||||
"A fatal error occurred: \n" + e.javaClass.canonicalName + ": " + e.message,
|
"A fatal error occurred: \n$e",
|
||||||
"packwiz-installer", JOptionPane.ERROR_MESSAGE)
|
"packwiz-installer", JOptionPane.ERROR_MESSAGE)
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
}
|
}
|
||||||
|
// In case the EventQueue is broken, exit after 1 minute
|
||||||
|
Thread.sleep(60 * 1000.toLong())
|
||||||
}
|
}
|
||||||
// In case the EventQueue is broken, exit after 1 minute
|
|
||||||
Thread.sleep(60 * 1000.toLong())
|
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,8 +18,9 @@ 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.IUserInterface.CancellationResult
|
import link.infra.packwiz.installer.ui.IUserInterface.CancellationResult
|
||||||
import link.infra.packwiz.installer.ui.IUserInterface.ExceptionListResult
|
import link.infra.packwiz.installer.ui.IUserInterface.ExceptionListResult
|
||||||
import link.infra.packwiz.installer.ui.InputStateHandler
|
import link.infra.packwiz.installer.ui.data.InstallProgress
|
||||||
import link.infra.packwiz.installer.ui.InstallProgress
|
import link.infra.packwiz.installer.util.Log
|
||||||
|
import link.infra.packwiz.installer.util.ifletOrErr
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.io.FileReader
|
import java.io.FileReader
|
||||||
@@ -27,14 +28,13 @@ import java.io.FileWriter
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.CompletionService
|
import java.util.concurrent.CompletionService
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
import java.util.concurrent.ExecutorCompletionService
|
import java.util.concurrent.ExecutorCompletionService
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
class UpdateManager internal constructor(private val opts: Options, val ui: IUserInterface, private val stateHandler: InputStateHandler) {
|
class UpdateManager internal constructor(private val opts: Options, val ui: IUserInterface) {
|
||||||
private var cancelled = false
|
private var cancelled = false
|
||||||
private var cancelledStartGame = false
|
private var cancelledStartGame = false
|
||||||
private var errorsOccurred = false
|
private var errorsOccurred = false
|
||||||
@@ -44,11 +44,17 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class Options(
|
data class Options(
|
||||||
var downloadURI: SpaceSafeURI? = null,
|
val downloadURI: SpaceSafeURI,
|
||||||
var manifestFile: String = "packwiz.json", // TODO: make configurable
|
val manifestFile: String,
|
||||||
var packFolder: String = ".",
|
val packFolder: String,
|
||||||
var side: Side = Side.CLIENT
|
val side: Side
|
||||||
) {
|
) {
|
||||||
|
// Horrible workaround for default params not working cleanly with nullable values
|
||||||
|
companion object {
|
||||||
|
fun construct(downloadURI: SpaceSafeURI, manifestFile: String?, packFolder: String?, side: Side?) =
|
||||||
|
Options(downloadURI, manifestFile ?: "packwiz.json", packFolder ?: ".", side ?: Side.CLIENT)
|
||||||
|
}
|
||||||
|
|
||||||
enum class Side {
|
enum class Side {
|
||||||
@SerializedName("client")
|
@SerializedName("client")
|
||||||
CLIENT("client"),
|
CLIENT("client"),
|
||||||
@@ -110,39 +116,35 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
|||||||
gson.fromJson(FileReader(Paths.get(opts.packFolder, opts.manifestFile).toString()),
|
gson.fromJson(FileReader(Paths.get(opts.packFolder, opts.manifestFile).toString()),
|
||||||
ManifestFile::class.java)
|
ManifestFile::class.java)
|
||||||
} catch (e: FileNotFoundException) {
|
} catch (e: FileNotFoundException) {
|
||||||
|
ui.firstInstall = true
|
||||||
ManifestFile()
|
ManifestFile()
|
||||||
} catch (e: JsonSyntaxException) {
|
} catch (e: JsonSyntaxException) {
|
||||||
ui.handleExceptionAndExit(e)
|
ui.showErrorAndExit("Invalid local manifest file, try deleting ${opts.manifestFile}", e)
|
||||||
return
|
|
||||||
} catch (e: JsonIOException) {
|
} catch (e: JsonIOException) {
|
||||||
ui.handleExceptionAndExit(e)
|
ui.showErrorAndExit("Failed to read local manifest file, try deleting ${opts.manifestFile}", e)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stateHandler.cancelButton) {
|
if (ui.cancelButtonPressed) {
|
||||||
showCancellationDialog()
|
showCancellationDialog()
|
||||||
handleCancellation()
|
handleCancellation()
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.submitProgress(InstallProgress("Loading pack file..."))
|
ui.submitProgress(InstallProgress("Loading pack file..."))
|
||||||
val packFileSource = try {
|
val packFileSource = try {
|
||||||
val src = getFileSource(opts.downloadURI!!)
|
val src = getFileSource(opts.downloadURI)
|
||||||
getHasher("sha256").getHashingSource(src)
|
getHasher("sha256").getHashingSource(src)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// TODO: run cancellation window?
|
ui.showErrorAndExit("Failed to download pack.toml", e)
|
||||||
ui.handleExceptionAndExit(e)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
val pf = packFileSource.buffer().use {
|
val pf = packFileSource.buffer().use {
|
||||||
try {
|
try {
|
||||||
Toml().read(it.inputStream()).to(PackFile::class.java)
|
Toml().read(it.inputStream()).to(PackFile::class.java)
|
||||||
} catch (e: IllegalStateException) {
|
} catch (e: IllegalStateException) {
|
||||||
ui.handleExceptionAndExit(e)
|
ui.showErrorAndExit("Failed to parse pack.toml", e)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stateHandler.cancelButton) {
|
if (ui.cancelButtonPressed) {
|
||||||
showCancellationDialog()
|
showCancellationDialog()
|
||||||
handleCancellation()
|
handleCancellation()
|
||||||
}
|
}
|
||||||
@@ -170,40 +172,42 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (invalid) {
|
if (invalid) {
|
||||||
println("File $fileUri invalidated, marked for redownloading")
|
Log.info("File $fileUri invalidated, marked for redownloading")
|
||||||
invalidatedUris.add(fileUri)
|
invalidatedUris.add(fileUri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (manifest.packFileHash?.let { packFileSource.hashIsEqual(it) } == true && invalidatedUris.isEmpty()) {
|
if (manifest.packFileHash?.let { packFileSource.hashIsEqual(it) } == true && invalidatedUris.isEmpty()) {
|
||||||
println("Modpack is already up to date!")
|
Log.info("Modpack is already up to date!")
|
||||||
// todo: --force?
|
// todo: --force?
|
||||||
if (!stateHandler.optionsButton) {
|
if (!ui.optionsButtonPressed) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println("Modpack name: " + pf.name)
|
Log.info("Modpack name: ${pf.name}")
|
||||||
|
|
||||||
if (stateHandler.cancelButton) {
|
if (ui.cancelButtonPressed) {
|
||||||
showCancellationDialog()
|
showCancellationDialog()
|
||||||
handleCancellation()
|
handleCancellation()
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
val index = pf.index!!
|
// TODO: switch to OkHttp for better redirect handling
|
||||||
getNewLoc(opts.downloadURI, index.file)?.let { newLoc ->
|
ui.ifletOrErr(pf.index, "No index file found, or the pack file is empty; note that Java doesn't automatically follow redirects from HTTP to HTTPS (and may cause this error)") { index ->
|
||||||
index.hashFormat?.let { hashFormat ->
|
ui.ifletOrErr(index.hashFormat, index.hash, "Pack has no hash or hashFormat for index") { hashFormat, hash ->
|
||||||
processIndex(
|
ui.ifletOrErr(getNewLoc(opts.downloadURI, index.file), "Pack has invalid index file: " + index.file) { newLoc ->
|
||||||
newLoc,
|
processIndex(
|
||||||
getHash(index.hashFormat!!, index.hash!!),
|
newLoc,
|
||||||
hashFormat,
|
getHash(hashFormat, hash),
|
||||||
manifest,
|
hashFormat,
|
||||||
invalidatedUris
|
manifest,
|
||||||
)
|
invalidatedUris
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e1: Exception) {
|
} catch (e1: Exception) {
|
||||||
ui.handleExceptionAndExit(e1)
|
ui.showErrorAndExit("Failed to process index file", e1)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCancellation()
|
handleCancellation()
|
||||||
@@ -222,8 +226,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
|||||||
try {
|
try {
|
||||||
FileWriter(Paths.get(opts.packFolder, opts.manifestFile).toString()).use { writer -> gson.toJson(manifest, writer) }
|
FileWriter(Paths.get(opts.packFolder, opts.manifestFile).toString()).use { writer -> gson.toJson(manifest, writer) }
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
// TODO: add message?
|
ui.showErrorAndExit("Failed to save local manifest file", e)
|
||||||
ui.handleException(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,8 +236,8 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
|||||||
|
|
||||||
private fun processIndex(indexUri: SpaceSafeURI, indexHash: Hash, hashFormat: String, manifest: ManifestFile, invalidatedUris: List<SpaceSafeURI>) {
|
private fun processIndex(indexUri: SpaceSafeURI, indexHash: Hash, hashFormat: String, manifest: ManifestFile, invalidatedUris: List<SpaceSafeURI>) {
|
||||||
if (manifest.indexFileHash == indexHash && invalidatedUris.isEmpty()) {
|
if (manifest.indexFileHash == indexHash && invalidatedUris.isEmpty()) {
|
||||||
println("Modpack files are already up to date!")
|
Log.info("Modpack files are already up to date!")
|
||||||
if (!stateHandler.optionsButton) {
|
if (!ui.optionsButtonPressed) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,21 +247,19 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
|||||||
val src = getFileSource(indexUri)
|
val src = getFileSource(indexUri)
|
||||||
getHasher(hashFormat).getHashingSource(src)
|
getHasher(hashFormat).getHashingSource(src)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// TODO: run cancellation window?
|
ui.showErrorAndExit("Failed to download index file", e)
|
||||||
ui.handleExceptionAndExit(e)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val indexFile = try {
|
val indexFile = try {
|
||||||
Toml().read(indexFileSource.buffer().inputStream()).to(IndexFile::class.java)
|
Toml().read(indexFileSource.buffer().inputStream()).to(IndexFile::class.java)
|
||||||
} catch (e: IllegalStateException) {
|
} catch (e: IllegalStateException) {
|
||||||
ui.handleExceptionAndExit(e)
|
ui.showErrorAndExit("Failed to parse index file", e)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if (!indexFileSource.hashIsEqual(indexHash)) {
|
if (!indexFileSource.hashIsEqual(indexHash)) {
|
||||||
ui.handleExceptionAndExit(RuntimeException("Your index hash is invalid! Please run packwiz refresh on the pack again"))
|
ui.showErrorAndExit("Your index file hash is invalid! The pack developer should packwiz refresh on the pack again")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if (stateHandler.cancelButton) {
|
|
||||||
|
if (ui.cancelButtonPressed) {
|
||||||
showCancellationDialog()
|
showCancellationDialog()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -275,8 +276,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
|||||||
try {
|
try {
|
||||||
Files.deleteIfExists(Paths.get(opts.packFolder, file.cachedLocation))
|
Files.deleteIfExists(Paths.get(opts.packFolder, file.cachedLocation))
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
// TODO: should this be shown to the user in some way?
|
Log.warn("Failed to delete optional disabled file", e)
|
||||||
e.printStackTrace()
|
|
||||||
}
|
}
|
||||||
// Set to null, as it doesn't exist anymore
|
// Set to null, as it doesn't exist anymore
|
||||||
file.cachedLocation = null
|
file.cachedLocation = null
|
||||||
@@ -286,8 +286,8 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
|||||||
if (!alreadyDeleted) {
|
if (!alreadyDeleted) {
|
||||||
try {
|
try {
|
||||||
Files.deleteIfExists(Paths.get(opts.packFolder, file.cachedLocation))
|
Files.deleteIfExists(Paths.get(opts.packFolder, file.cachedLocation))
|
||||||
} catch (e: IOException) { // TODO: should this be shown to the user in some way?
|
} catch (e: IOException) {
|
||||||
e.printStackTrace()
|
Log.warn("Failed to delete file removed from index", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
it.remove()
|
it.remove()
|
||||||
@@ -295,7 +295,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stateHandler.cancelButton) {
|
if (ui.cancelButtonPressed) {
|
||||||
showCancellationDialog()
|
showCancellationDialog()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -303,14 +303,14 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
|||||||
|
|
||||||
// TODO: progress bar?
|
// TODO: progress bar?
|
||||||
if (indexFile.files.isEmpty()) {
|
if (indexFile.files.isEmpty()) {
|
||||||
println("Warning: Index is empty!")
|
Log.warn("Index is empty!")
|
||||||
}
|
}
|
||||||
val tasks = createTasksFromIndex(indexFile, indexFile.hashFormat, opts.side)
|
val tasks = createTasksFromIndex(indexFile, indexFile.hashFormat, opts.side)
|
||||||
// If the side changes, invalidate EVERYTHING just in case
|
// If the side changes, invalidate EVERYTHING just in case
|
||||||
// Might not be needed, but done just to be safe
|
// Might not be needed, but done just to be safe
|
||||||
val invalidateAll = opts.side != manifest.cachedSide
|
val invalidateAll = opts.side != manifest.cachedSide
|
||||||
if (invalidateAll) {
|
if (invalidateAll) {
|
||||||
println("Side changed, invalidating all mods")
|
Log.info("Side changed, invalidating all mods")
|
||||||
}
|
}
|
||||||
tasks.forEach{ f ->
|
tasks.forEach{ f ->
|
||||||
// TODO: should linkedfile be checked as well? should this be done in the download section?
|
// TODO: should linkedfile be checked as well? should this be done in the download section?
|
||||||
@@ -326,7 +326,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
|||||||
f.updateFromCache(file)
|
f.updateFromCache(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stateHandler.cancelButton) {
|
if (ui.cancelButtonPressed) {
|
||||||
showCancellationDialog()
|
showCancellationDialog()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -337,17 +337,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
|||||||
val failedTaskDetails = tasks.asSequence().map(DownloadTask::exceptionDetails).filterNotNull().toList()
|
val failedTaskDetails = tasks.asSequence().map(DownloadTask::exceptionDetails).filterNotNull().toList()
|
||||||
if (failedTaskDetails.isNotEmpty()) {
|
if (failedTaskDetails.isNotEmpty()) {
|
||||||
errorsOccurred = true
|
errorsOccurred = true
|
||||||
val exceptionListResult: ExceptionListResult
|
when (ui.showExceptions(failedTaskDetails, tasks.size, true)) {
|
||||||
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
|
|
||||||
}
|
|
||||||
when (exceptionListResult) {
|
|
||||||
ExceptionListResult.CONTINUE -> {}
|
ExceptionListResult.CONTINUE -> {}
|
||||||
ExceptionListResult.CANCEL -> {
|
ExceptionListResult.CANCEL -> {
|
||||||
cancelled = true
|
cancelled = true
|
||||||
@@ -360,7 +350,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stateHandler.cancelButton) {
|
if (ui.cancelButtonPressed) {
|
||||||
showCancellationDialog()
|
showCancellationDialog()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -369,23 +359,15 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
|||||||
val nonFailedFirstTasks = tasks.filter { t -> !t.failed() }.toList()
|
val nonFailedFirstTasks = tasks.filter { t -> !t.failed() }.toList()
|
||||||
val optionTasks = nonFailedFirstTasks.filter(DownloadTask::correctSide).filter(DownloadTask::isOptional).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.optionsButton || optionTasks.any(DownloadTask::isNewOptional)) {
|
if (ui.optionsButtonPressed || optionTasks.any(DownloadTask::isNewOptional)) {
|
||||||
// new ArrayList is required so it's an IOptionDetails rather than a DownloadTask list
|
// new ArrayList is required so it's an IOptionDetails rather than a DownloadTask list
|
||||||
val cancelledResult = ui.showOptions(ArrayList(optionTasks))
|
if (ui.showOptions(ArrayList(optionTasks))) {
|
||||||
try {
|
cancelled = true
|
||||||
if (cancelledResult.get()) {
|
handleCancellation()
|
||||||
cancelled = true
|
|
||||||
// TODO: Should the UI be closed somehow??
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} catch (e: InterruptedException) {
|
|
||||||
// Interrupted means cancelled???
|
|
||||||
ui.handleExceptionAndExit(e)
|
|
||||||
} catch (e: ExecutionException) {
|
|
||||||
ui.handleExceptionAndExit(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ui.disableOptionsButton()
|
// TODO: keep this enabled? then apply changes after download process?
|
||||||
|
ui.disableOptionsButton(optionTasks.isNotEmpty())
|
||||||
|
|
||||||
// TODO: different thread pool type?
|
// TODO: different thread pool type?
|
||||||
val threadPool = Executors.newFixedThreadPool(10)
|
val threadPool = Executors.newFixedThreadPool(10)
|
||||||
@@ -397,18 +379,15 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (i in tasks.indices) {
|
for (i in tasks.indices) {
|
||||||
var task: DownloadTask?
|
val task: DownloadTask = try {
|
||||||
task = try {
|
|
||||||
completionService.take().get()
|
completionService.take().get()
|
||||||
} catch (e: InterruptedException) {
|
} catch (e: InterruptedException) {
|
||||||
ui.handleException(e)
|
ui.showErrorAndExit("Interrupted when consuming download tasks", e)
|
||||||
null
|
|
||||||
} catch (e: ExecutionException) {
|
} catch (e: ExecutionException) {
|
||||||
ui.handleException(e)
|
ui.showErrorAndExit("Failed to execute download task", e)
|
||||||
null
|
|
||||||
}
|
}
|
||||||
// Update manifest - If there were no errors cachedFile has already been modified in place (good old pass by reference)
|
// Update manifest - If there were no errors cachedFile has already been modified in place (good old pass by reference)
|
||||||
task?.cachedFile?.let { file ->
|
task.cachedFile?.let { file ->
|
||||||
if (task.failed()) {
|
if (task.failed()) {
|
||||||
val oldFile = file.revert
|
val oldFile = file.revert
|
||||||
if (oldFile != null) {
|
if (oldFile != null) {
|
||||||
@@ -419,21 +398,15 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var progress: String
|
val exDetails = task.exceptionDetails
|
||||||
if (task != null) {
|
val progress = if (exDetails != null) {
|
||||||
val exDetails = task.exceptionDetails
|
"Failed to download ${exDetails.name}: ${exDetails.exception.message}"
|
||||||
if (exDetails != null) {
|
|
||||||
progress = "Failed to download ${exDetails.name}: ${exDetails.exception.message}"
|
|
||||||
exDetails.exception.printStackTrace()
|
|
||||||
} else {
|
|
||||||
progress = "Downloaded ${task.name}"
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
progress = "Failed to download, unknown reason"
|
"Downloaded ${task.name}"
|
||||||
}
|
}
|
||||||
ui.submitProgress(InstallProgress(progress, i + 1, tasks.size))
|
ui.submitProgress(InstallProgress(progress, i + 1, tasks.size))
|
||||||
|
|
||||||
if (stateHandler.cancelButton) { // Stop all tasks, don't launch the game (it's in an invalid state!)
|
if (ui.cancelButtonPressed) { // Stop all tasks, don't launch the game (it's in an invalid state!)
|
||||||
threadPool.shutdown()
|
threadPool.shutdown()
|
||||||
cancelled = true
|
cancelled = true
|
||||||
return
|
return
|
||||||
@@ -446,18 +419,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
|||||||
val failedTasks2ElectricBoogaloo = nonFailedFirstTasks.asSequence().map(DownloadTask::exceptionDetails).filterNotNull().toList()
|
val failedTasks2ElectricBoogaloo = nonFailedFirstTasks.asSequence().map(DownloadTask::exceptionDetails).filterNotNull().toList()
|
||||||
if (failedTasks2ElectricBoogaloo.isNotEmpty()) {
|
if (failedTasks2ElectricBoogaloo.isNotEmpty()) {
|
||||||
errorsOccurred = true
|
errorsOccurred = true
|
||||||
val exceptionListResult: ExceptionListResult
|
when (ui.showExceptions(failedTasks2ElectricBoogaloo, tasks.size, false)) {
|
||||||
exceptionListResult = try {
|
|
||||||
ui.showExceptions(failedTasks2ElectricBoogaloo, tasks.size, false).get()
|
|
||||||
} catch (e: InterruptedException) {
|
|
||||||
// Interrupted means cancelled???
|
|
||||||
ui.handleExceptionAndExit(e)
|
|
||||||
return
|
|
||||||
} catch (e: ExecutionException) {
|
|
||||||
ui.handleExceptionAndExit(e)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
when (exceptionListResult) {
|
|
||||||
ExceptionListResult.CONTINUE -> {}
|
ExceptionListResult.CONTINUE -> {}
|
||||||
ExceptionListResult.CANCEL -> cancelled = true
|
ExceptionListResult.CANCEL -> cancelled = true
|
||||||
ExceptionListResult.IGNORE -> cancelledStartGame = true
|
ExceptionListResult.IGNORE -> cancelledStartGame = true
|
||||||
@@ -466,23 +428,13 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun showCancellationDialog() {
|
private fun showCancellationDialog() {
|
||||||
val cancellationResult: CancellationResult
|
when (ui.showCancellationDialog()) {
|
||||||
cancellationResult = try {
|
|
||||||
ui.showCancellationDialog().get()
|
|
||||||
} catch (e: InterruptedException) {
|
|
||||||
// Interrupted means cancelled???
|
|
||||||
ui.handleExceptionAndExit(e)
|
|
||||||
return
|
|
||||||
} catch (e: ExecutionException) {
|
|
||||||
ui.handleExceptionAndExit(e)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
when (cancellationResult) {
|
|
||||||
CancellationResult.QUIT -> cancelled = true
|
CancellationResult.QUIT -> cancelled = true
|
||||||
CancellationResult.CONTINUE -> cancelledStartGame = true
|
CancellationResult.CONTINUE -> cancelledStartGame = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: move to UI?
|
||||||
private fun handleCancellation() {
|
private fun handleCancellation() {
|
||||||
if (cancelled) {
|
if (cancelled) {
|
||||||
println("Update cancelled by user!")
|
println("Update cancelled by user!")
|
||||||
|
@@ -78,9 +78,9 @@ class IndexFile {
|
|||||||
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"
|
file?.run { Paths.get(path ?: return "Invalid file").fileName.toString() } ?: "Invalid file"
|
||||||
}
|
}
|
||||||
return file?.run { Paths.get(path).fileName.toString() } ?: "Invalid file"
|
return file?.run { Paths.get(path ?: return "Invalid file").fileName.toString() } ?: "Invalid file"
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: URIs are bad
|
// TODO: URIs are bad
|
||||||
|
@@ -32,7 +32,7 @@ class SpaceSafeURI : Comparable<SpaceSafeURI>, Serializable {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val path: String get() = u.path.replace("%20", " ")
|
val path: String? get() = u.path?.replace("%20", " ")
|
||||||
|
|
||||||
override fun toString(): String = u.toString().replace("%20", " ")
|
override fun toString(): String = u.toString().replace("%20", " ")
|
||||||
|
|
||||||
@@ -52,9 +52,9 @@ class SpaceSafeURI : Comparable<SpaceSafeURI>, Serializable {
|
|||||||
|
|
||||||
override fun compareTo(other: SpaceSafeURI): Int = u.compareTo(other.u)
|
override fun compareTo(other: SpaceSafeURI): Int = u.compareTo(other.u)
|
||||||
|
|
||||||
val scheme: String get() = u.scheme
|
val scheme: String? get() = u.scheme
|
||||||
val authority: String get() = u.authority
|
val authority: String? get() = u.authority
|
||||||
val host: String get() = u.host
|
val host: String? get() = u.host
|
||||||
|
|
||||||
@Throws(MalformedURLException::class)
|
@Throws(MalformedURLException::class)
|
||||||
fun toURL(): URL = u.toURL()
|
fun toURL(): URL = u.toURL()
|
||||||
|
@@ -4,7 +4,8 @@ object HashUtils {
|
|||||||
private val hashTypeConversion: Map<String, IHasher> = mapOf(
|
private val hashTypeConversion: Map<String, IHasher> = mapOf(
|
||||||
"sha256" to HashingSourceHasher("sha256"),
|
"sha256" to HashingSourceHasher("sha256"),
|
||||||
"sha512" to HashingSourceHasher("sha512"),
|
"sha512" to HashingSourceHasher("sha512"),
|
||||||
"murmur2" to Murmur2Hasher()
|
"murmur2" to Murmur2Hasher(),
|
||||||
|
"sha1" to HashingSourceHasher("sha1")
|
||||||
)
|
)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@@ -5,7 +5,7 @@ import okio.Source
|
|||||||
|
|
||||||
class HashingSourceHasher internal constructor(private val type: String) : IHasher {
|
class HashingSourceHasher internal constructor(private val type: String) : IHasher {
|
||||||
// i love naming things
|
// i love naming things
|
||||||
private inner class HashingSourceGeneralHashingSource internal constructor(val delegateHashing: HashingSource) : GeneralHashingSource(delegateHashing) {
|
private inner class HashingSourceGeneralHashingSource(val delegateHashing: HashingSource) : GeneralHashingSource(delegateHashing) {
|
||||||
override val hash: Hash by lazy(LazyThreadSafetyMode.NONE) {
|
override val hash: Hash by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
HashingSourceHash(delegateHashing.hash.hex())
|
HashingSourceHash(delegateHashing.hash.hex())
|
||||||
}
|
}
|
||||||
@@ -35,6 +35,7 @@ class HashingSourceHasher internal constructor(private val type: String) : IHash
|
|||||||
"md5" -> return HashingSourceGeneralHashingSource(HashingSource.md5(delegate))
|
"md5" -> return HashingSourceGeneralHashingSource(HashingSource.md5(delegate))
|
||||||
"sha256" -> return HashingSourceGeneralHashingSource(HashingSource.sha256(delegate))
|
"sha256" -> return HashingSourceGeneralHashingSource(HashingSource.sha256(delegate))
|
||||||
"sha512" -> return HashingSourceGeneralHashingSource(HashingSource.sha512(delegate))
|
"sha512" -> return HashingSourceGeneralHashingSource(HashingSource.sha512(delegate))
|
||||||
|
"sha1" -> return HashingSourceGeneralHashingSource(HashingSource.sha1(delegate))
|
||||||
}
|
}
|
||||||
throw RuntimeException("Invalid hash type provided")
|
throw RuntimeException("Invalid hash type provided")
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,7 @@ object HandlerManager {
|
|||||||
RequestHandlerFile()
|
RequestHandlerFile()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: get rid of nullable stuff here
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getNewLoc(base: SpaceSafeURI?, loc: SpaceSafeURI?): SpaceSafeURI? {
|
fun getNewLoc(base: SpaceSafeURI?, loc: SpaceSafeURI?): SpaceSafeURI? {
|
||||||
if (loc == null) {
|
if (loc == null) {
|
||||||
@@ -32,6 +33,8 @@ object HandlerManager {
|
|||||||
// Zip handler discards once read, requesting multiple times on other handlers would cause multiple downloads
|
// Zip handler discards once read, requesting multiple times on other handlers would cause multiple downloads
|
||||||
// Caching system? Copy from already downloaded files?
|
// Caching system? Copy from already downloaded files?
|
||||||
|
|
||||||
|
// TODO: change to use something more idiomatic than exceptions?
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun getFileSource(loc: SpaceSafeURI): Source {
|
fun getFileSource(loc: SpaceSafeURI): Source {
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
package link.infra.packwiz.installer.request.handlers
|
package link.infra.packwiz.installer.request.handlers
|
||||||
|
|
||||||
import link.infra.packwiz.installer.metadata.SpaceSafeURI
|
import link.infra.packwiz.installer.metadata.SpaceSafeURI
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
import kotlin.concurrent.read
|
import kotlin.concurrent.read
|
||||||
@@ -21,7 +20,7 @@ class RequestHandlerGithub : RequestHandlerZip(true) {
|
|||||||
private val zipUriMap: MutableMap<String, SpaceSafeURI> = HashMap()
|
private val zipUriMap: MutableMap<String, SpaceSafeURI> = HashMap()
|
||||||
private val zipUriLock = ReentrantReadWriteLock()
|
private val zipUriLock = ReentrantReadWriteLock()
|
||||||
private fun getRepoName(loc: SpaceSafeURI): String? {
|
private fun getRepoName(loc: SpaceSafeURI): String? {
|
||||||
val matcher = repoMatcherPattern.matcher(loc.path)
|
val matcher = repoMatcherPattern.matcher(loc.path ?: return null)
|
||||||
return if (matcher.matches()) {
|
return if (matcher.matches()) {
|
||||||
matcher.group(1)
|
matcher.group(1)
|
||||||
} else {
|
} else {
|
||||||
@@ -47,7 +46,7 @@ class RequestHandlerGithub : RequestHandlerZip(true) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getBranch(loc: SpaceSafeURI): String? {
|
private fun getBranch(loc: SpaceSafeURI): String? {
|
||||||
val matcher = branchMatcherPattern.matcher(loc.path)
|
val matcher = branchMatcherPattern.matcher(loc.path ?: return null)
|
||||||
return if (matcher.matches()) {
|
return if (matcher.matches()) {
|
||||||
matcher.group(1)
|
matcher.group(1)
|
||||||
} else {
|
} else {
|
||||||
@@ -66,6 +65,6 @@ class RequestHandlerGithub : RequestHandlerZip(true) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// TODO: more match testing?
|
// TODO: more match testing?
|
||||||
return "github.com" == loc.host && branchMatcherPattern.matcher(loc.path).matches()
|
return "github.com" == loc.host && branchMatcherPattern.matcher(loc.path ?: return false).matches()
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -25,7 +25,7 @@ abstract class RequestHandlerZip(private val modeHasFolder: Boolean) : RequestHa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class ZipReader internal constructor(zip: Source) {
|
private inner class ZipReader(zip: Source) {
|
||||||
private val zis = ZipInputStream(zip.buffer().inputStream())
|
private val zis = ZipInputStream(zip.buffer().inputStream())
|
||||||
private val readFiles: MutableMap<SpaceSafeURI, Buffer> = HashMap()
|
private val readFiles: MutableMap<SpaceSafeURI, Buffer> = HashMap()
|
||||||
// Write lock implies access to ZipInputStream - only 1 thread must read at a time!
|
// Write lock implies access to ZipInputStream - only 1 thread must read at a time!
|
||||||
|
@@ -1,47 +0,0 @@
|
|||||||
package link.infra.packwiz.installer.ui
|
|
||||||
|
|
||||||
import link.infra.packwiz.installer.ui.IUserInterface.ExceptionListResult
|
|
||||||
import java.util.concurrent.CompletableFuture
|
|
||||||
import java.util.concurrent.Future
|
|
||||||
|
|
||||||
class CLIHandler : IUserInterface {
|
|
||||||
override fun handleException(e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun show(handler: InputStateHandler) {}
|
|
||||||
override fun submitProgress(progress: InstallProgress) {
|
|
||||||
val sb = StringBuilder()
|
|
||||||
if (progress.hasProgress) {
|
|
||||||
sb.append('(')
|
|
||||||
sb.append(progress.progress)
|
|
||||||
sb.append('/')
|
|
||||||
sb.append(progress.progressTotal)
|
|
||||||
sb.append(") ")
|
|
||||||
}
|
|
||||||
sb.append(progress.message)
|
|
||||||
println(sb.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun executeManager(task: () -> Unit) {
|
|
||||||
task()
|
|
||||||
println("Finished successfully!")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showOptions(options: List<IOptionDetails>): Future<Boolean> {
|
|
||||||
for (opt in options) {
|
|
||||||
opt.optionValue = true
|
|
||||||
// TODO: implement option choice in the CLI?
|
|
||||||
println("Warning: accepting option " + opt.name + " as option choosing is not implemented in the CLI")
|
|
||||||
}
|
|
||||||
return CompletableFuture<Boolean>().apply {
|
|
||||||
complete(false) // Can't be cancelled!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showExceptions(exceptions: List<ExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): Future<ExceptionListResult> {
|
|
||||||
val future = CompletableFuture<ExceptionListResult>()
|
|
||||||
future.complete(ExceptionListResult.CANCEL)
|
|
||||||
return future
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,35 +1,27 @@
|
|||||||
package link.infra.packwiz.installer.ui
|
package link.infra.packwiz.installer.ui
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture
|
import link.infra.packwiz.installer.ui.data.ExceptionDetails
|
||||||
import java.util.concurrent.Future
|
import link.infra.packwiz.installer.ui.data.IOptionDetails
|
||||||
import kotlin.system.exitProcess
|
import link.infra.packwiz.installer.ui.data.InstallProgress
|
||||||
|
|
||||||
interface IUserInterface {
|
interface IUserInterface {
|
||||||
fun show(handler: InputStateHandler)
|
fun show()
|
||||||
fun handleException(e: Exception)
|
fun dispose()
|
||||||
@JvmDefault
|
|
||||||
fun handleExceptionAndExit(e: Exception) {
|
|
||||||
handleException(e)
|
|
||||||
exitProcess(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmDefault
|
fun showErrorAndExit(message: String): Nothing {
|
||||||
fun setTitle(title: String) {}
|
showErrorAndExit(message, null)
|
||||||
|
}
|
||||||
|
fun showErrorAndExit(message: String, e: Exception?): Nothing
|
||||||
|
|
||||||
|
var title: String
|
||||||
fun submitProgress(progress: InstallProgress)
|
fun submitProgress(progress: InstallProgress)
|
||||||
fun executeManager(task: () -> Unit)
|
|
||||||
// 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>): Boolean
|
||||||
|
|
||||||
fun showExceptions(exceptions: List<ExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): Future<ExceptionListResult>
|
fun showExceptions(exceptions: List<ExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): ExceptionListResult
|
||||||
@JvmDefault
|
fun disableOptionsButton(hasOptions: Boolean) {}
|
||||||
fun disableOptionsButton() {}
|
|
||||||
|
|
||||||
@JvmDefault
|
fun showCancellationDialog(): CancellationResult = CancellationResult.QUIT
|
||||||
fun showCancellationDialog(): Future<CancellationResult> {
|
|
||||||
return CompletableFuture<CancellationResult>().apply {
|
|
||||||
complete(CancellationResult.QUIT)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class ExceptionListResult {
|
enum class ExceptionListResult {
|
||||||
CONTINUE, CANCEL, IGNORE
|
CONTINUE, CANCEL, IGNORE
|
||||||
@@ -38,4 +30,9 @@ interface IUserInterface {
|
|||||||
enum class CancellationResult {
|
enum class CancellationResult {
|
||||||
QUIT, CONTINUE
|
QUIT, CONTINUE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var optionsButtonPressed: Boolean
|
||||||
|
var cancelButtonPressed: Boolean
|
||||||
|
|
||||||
|
var firstInstall: Boolean
|
||||||
}
|
}
|
@@ -1,21 +0,0 @@
|
|||||||
package link.infra.packwiz.installer.ui
|
|
||||||
|
|
||||||
class InputStateHandler {
|
|
||||||
// TODO: convert to coroutines/locks?
|
|
||||||
@get:Synchronized
|
|
||||||
var optionsButton = false
|
|
||||||
private set
|
|
||||||
@get:Synchronized
|
|
||||||
var cancelButton = false
|
|
||||||
private set
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun pressCancelButton() {
|
|
||||||
cancelButton = true
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun pressOptionsButton() {
|
|
||||||
optionsButton = true
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,228 +0,0 @@
|
|||||||
package link.infra.packwiz.installer.ui
|
|
||||||
|
|
||||||
import link.infra.packwiz.installer.ui.IUserInterface.ExceptionListResult
|
|
||||||
import java.awt.*
|
|
||||||
import java.util.concurrent.CompletableFuture
|
|
||||||
import java.util.concurrent.Future
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
import javax.swing.*
|
|
||||||
import javax.swing.border.EmptyBorder
|
|
||||||
import kotlin.system.exitProcess
|
|
||||||
|
|
||||||
class InstallWindow : IUserInterface {
|
|
||||||
private lateinit var frmPackwizlauncher: JFrame
|
|
||||||
private lateinit var lblProgresslabel: JLabel
|
|
||||||
private lateinit var progressBar: JProgressBar
|
|
||||||
private lateinit var btnOptions: JButton
|
|
||||||
|
|
||||||
private var inputStateHandler: InputStateHandler? = null
|
|
||||||
private var title = "Updating modpack..."
|
|
||||||
private var worker: SwingWorkerButWithPublicPublish<Unit, InstallProgress>? = null
|
|
||||||
private val aboutToCrash = AtomicBoolean()
|
|
||||||
|
|
||||||
// TODO: separate JFrame junk from IUserInterface junk?
|
|
||||||
|
|
||||||
init {
|
|
||||||
EventQueue.invokeAndWait {
|
|
||||||
frmPackwizlauncher = JFrame().apply {
|
|
||||||
title = this@InstallWindow.title
|
|
||||||
setBounds(100, 100, 493, 95)
|
|
||||||
defaultCloseOperation = JFrame.EXIT_ON_CLOSE
|
|
||||||
setLocationRelativeTo(null)
|
|
||||||
|
|
||||||
// Progress bar and loading text
|
|
||||||
add(JPanel().apply {
|
|
||||||
border = EmptyBorder(10, 10, 10, 10)
|
|
||||||
layout = BorderLayout(0, 0)
|
|
||||||
|
|
||||||
progressBar = JProgressBar().apply {
|
|
||||||
isIndeterminate = true
|
|
||||||
}
|
|
||||||
add(progressBar, BorderLayout.CENTER)
|
|
||||||
|
|
||||||
lblProgresslabel = JLabel("Loading...")
|
|
||||||
add(lblProgresslabel, BorderLayout.SOUTH)
|
|
||||||
}, BorderLayout.CENTER)
|
|
||||||
|
|
||||||
// Buttons
|
|
||||||
add(JPanel().apply {
|
|
||||||
border = EmptyBorder(0, 5, 0, 5)
|
|
||||||
layout = GridBagLayout()
|
|
||||||
|
|
||||||
btnOptions = JButton("Optional mods...").apply {
|
|
||||||
alignmentX = Component.CENTER_ALIGNMENT
|
|
||||||
|
|
||||||
addActionListener {
|
|
||||||
text = "Loading..."
|
|
||||||
isEnabled = false
|
|
||||||
inputStateHandler?.pressOptionsButton()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
add(btnOptions, GridBagConstraints().apply {
|
|
||||||
gridx = 0
|
|
||||||
gridy = 0
|
|
||||||
})
|
|
||||||
|
|
||||||
add(JButton("Cancel").apply {
|
|
||||||
addActionListener {
|
|
||||||
isEnabled = false
|
|
||||||
inputStateHandler?.pressCancelButton()
|
|
||||||
}
|
|
||||||
}, GridBagConstraints().apply {
|
|
||||||
gridx = 0
|
|
||||||
gridy = 1
|
|
||||||
})
|
|
||||||
}, BorderLayout.EAST)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun show(handler: InputStateHandler) {
|
|
||||||
inputStateHandler = handler
|
|
||||||
EventQueue.invokeLater {
|
|
||||||
try {
|
|
||||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
|
|
||||||
frmPackwizlauncher.isVisible = true
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun handleException(e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
EventQueue.invokeLater {
|
|
||||||
JOptionPane.showMessageDialog(null,
|
|
||||||
"An error occurred: \n" + e.javaClass.canonicalName + ": " + e.message,
|
|
||||||
title, JOptionPane.ERROR_MESSAGE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun handleExceptionAndExit(e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
// TODO: Fix this mess
|
|
||||||
// Used to prevent the done() handler of SwingWorker executing if the invokeLater hasn't happened yet
|
|
||||||
aboutToCrash.set(true)
|
|
||||||
EventQueue.invokeLater {
|
|
||||||
JOptionPane.showMessageDialog(null,
|
|
||||||
"A fatal error occurred: \n" + e.javaClass.canonicalName + ": " + e.message,
|
|
||||||
title, JOptionPane.ERROR_MESSAGE)
|
|
||||||
exitProcess(1)
|
|
||||||
}
|
|
||||||
// Pause forever, so it blocks while we wait for System.exit to take effect
|
|
||||||
try {
|
|
||||||
Thread.currentThread().join()
|
|
||||||
} catch (ex: InterruptedException) { // no u
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setTitle(title: String) {
|
|
||||||
this.title = title
|
|
||||||
frmPackwizlauncher.let { frame ->
|
|
||||||
EventQueue.invokeLater { frame.title = title }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun submitProgress(progress: InstallProgress) {
|
|
||||||
val sb = StringBuilder()
|
|
||||||
if (progress.hasProgress) {
|
|
||||||
sb.append('(')
|
|
||||||
sb.append(progress.progress)
|
|
||||||
sb.append('/')
|
|
||||||
sb.append(progress.progressTotal)
|
|
||||||
sb.append(") ")
|
|
||||||
}
|
|
||||||
sb.append(progress.message)
|
|
||||||
// TODO: better logging library?
|
|
||||||
println(sb.toString())
|
|
||||||
worker?.publishPublic(progress)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun executeManager(task: Function0<Unit>) {
|
|
||||||
EventQueue.invokeLater {
|
|
||||||
// TODO: rewrite this stupidity to use channels??!!!
|
|
||||||
worker = object : SwingWorkerButWithPublicPublish<Unit, InstallProgress>() {
|
|
||||||
override fun doInBackground() {
|
|
||||||
task.invoke()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun process(chunks: List<InstallProgress>) {
|
|
||||||
// Only process last chunk
|
|
||||||
if (chunks.isNotEmpty()) {
|
|
||||||
val (message, hasProgress, progress, progressTotal) = chunks[chunks.size - 1]
|
|
||||||
if (hasProgress) {
|
|
||||||
progressBar.isIndeterminate = false
|
|
||||||
progressBar.value = progress
|
|
||||||
progressBar.maximum = progressTotal
|
|
||||||
} else {
|
|
||||||
progressBar.isIndeterminate = true
|
|
||||||
progressBar.value = 0
|
|
||||||
}
|
|
||||||
lblProgresslabel.text = message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun done() {
|
|
||||||
if (aboutToCrash.get()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// TODO: a better way to do this?
|
|
||||||
frmPackwizlauncher.dispose()
|
|
||||||
println("Finished successfully!")
|
|
||||||
exitProcess(0)
|
|
||||||
}
|
|
||||||
}.also {
|
|
||||||
it.execute()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showOptions(options: List<IOptionDetails>): Future<Boolean> {
|
|
||||||
val future = CompletableFuture<Boolean>()
|
|
||||||
EventQueue.invokeLater {
|
|
||||||
if (options.isEmpty()) {
|
|
||||||
JOptionPane.showMessageDialog(null,
|
|
||||||
"This modpack has no optional mods!",
|
|
||||||
"Optional mods", JOptionPane.INFORMATION_MESSAGE)
|
|
||||||
future.complete(false)
|
|
||||||
} else {
|
|
||||||
OptionsSelectWindow(options, future, frmPackwizlauncher).apply {
|
|
||||||
defaultCloseOperation = JDialog.DISPOSE_ON_CLOSE
|
|
||||||
isVisible = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return future
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showExceptions(exceptions: List<ExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): Future<ExceptionListResult> {
|
|
||||||
val future = CompletableFuture<ExceptionListResult>()
|
|
||||||
EventQueue.invokeLater {
|
|
||||||
ExceptionListWindow(exceptions, future, numTotal, allowsIgnore, frmPackwizlauncher).apply {
|
|
||||||
defaultCloseOperation = JDialog.DISPOSE_ON_CLOSE
|
|
||||||
isVisible = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return future
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun disableOptionsButton() {
|
|
||||||
btnOptions.apply {
|
|
||||||
text = "No optional mods"
|
|
||||||
isEnabled = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showCancellationDialog(): Future<IUserInterface.CancellationResult> {
|
|
||||||
val future = CompletableFuture<IUserInterface.CancellationResult>()
|
|
||||||
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) IUserInterface.CancellationResult.QUIT else IUserInterface.CancellationResult.CONTINUE)
|
|
||||||
}
|
|
||||||
return future
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,13 +0,0 @@
|
|||||||
package link.infra.packwiz.installer.ui
|
|
||||||
|
|
||||||
import javax.swing.SwingWorker
|
|
||||||
|
|
||||||
// Q: AAA WHAT HAVE YOU DONE THIS IS DISGUSTING
|
|
||||||
// A: it just makes things easier, so i can easily have one interface for CLI/GUI
|
|
||||||
// if someone has a better way to do this please PR it
|
|
||||||
abstract class SwingWorkerButWithPublicPublish<T, V> : SwingWorker<T, V>() {
|
|
||||||
@SafeVarargs
|
|
||||||
fun publishPublic(vararg chunks: V) {
|
|
||||||
publish(*chunks)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,62 @@
|
|||||||
|
package link.infra.packwiz.installer.ui.cli
|
||||||
|
|
||||||
|
import link.infra.packwiz.installer.ui.IUserInterface
|
||||||
|
import link.infra.packwiz.installer.ui.IUserInterface.ExceptionListResult
|
||||||
|
import link.infra.packwiz.installer.ui.data.ExceptionDetails
|
||||||
|
import link.infra.packwiz.installer.ui.data.IOptionDetails
|
||||||
|
import link.infra.packwiz.installer.ui.data.InstallProgress
|
||||||
|
import link.infra.packwiz.installer.util.Log
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
class CLIHandler : IUserInterface {
|
||||||
|
@Volatile
|
||||||
|
override var optionsButtonPressed = false
|
||||||
|
@Volatile
|
||||||
|
override var cancelButtonPressed = false
|
||||||
|
@Volatile
|
||||||
|
override var firstInstall = false
|
||||||
|
|
||||||
|
override var title: String = ""
|
||||||
|
|
||||||
|
override fun showErrorAndExit(message: String, e: Exception?): Nothing {
|
||||||
|
if (e != null) {
|
||||||
|
Log.fatal(message, e)
|
||||||
|
} else {
|
||||||
|
Log.fatal(message)
|
||||||
|
}
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun show() {}
|
||||||
|
override fun dispose() {}
|
||||||
|
override fun submitProgress(progress: InstallProgress) {
|
||||||
|
val sb = StringBuilder()
|
||||||
|
if (progress.hasProgress) {
|
||||||
|
sb.append('(')
|
||||||
|
sb.append(progress.progress)
|
||||||
|
sb.append('/')
|
||||||
|
sb.append(progress.progressTotal)
|
||||||
|
sb.append(") ")
|
||||||
|
}
|
||||||
|
sb.append(progress.message)
|
||||||
|
println(sb.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showOptions(options: List<IOptionDetails>): Boolean {
|
||||||
|
for (opt in options) {
|
||||||
|
opt.optionValue = true
|
||||||
|
// TODO: implement option choice in the CLI?
|
||||||
|
Log.warn("Accepting option ${opt.name} as option choosing is not implemented in the CLI")
|
||||||
|
}
|
||||||
|
return false // Can't be cancelled!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showExceptions(exceptions: List<ExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): ExceptionListResult {
|
||||||
|
println("Failed to download modpack, the following errors were encountered:")
|
||||||
|
for (ex in exceptions) {
|
||||||
|
print(ex.name + ": ")
|
||||||
|
ex.exception.printStackTrace()
|
||||||
|
}
|
||||||
|
return ExceptionListResult.CANCEL
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package link.infra.packwiz.installer.ui
|
package link.infra.packwiz.installer.ui.data
|
||||||
|
|
||||||
data class ExceptionDetails(
|
data class ExceptionDetails(
|
||||||
val name: String,
|
val name: String,
|
@@ -1,4 +1,4 @@
|
|||||||
package link.infra.packwiz.installer.ui
|
package link.infra.packwiz.installer.ui.data
|
||||||
|
|
||||||
interface IOptionDetails {
|
interface IOptionDetails {
|
||||||
val name: String
|
val name: String
|
@@ -1,4 +1,4 @@
|
|||||||
package link.infra.packwiz.installer.ui
|
package link.infra.packwiz.installer.ui.data
|
||||||
|
|
||||||
data class InstallProgress(
|
data class InstallProgress(
|
||||||
val message: String,
|
val message: String,
|
@@ -1,5 +1,7 @@
|
|||||||
package link.infra.packwiz.installer.ui
|
package link.infra.packwiz.installer.ui.gui
|
||||||
|
|
||||||
|
import link.infra.packwiz.installer.ui.IUserInterface
|
||||||
|
import link.infra.packwiz.installer.ui.data.ExceptionDetails
|
||||||
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
|
||||||
@@ -16,7 +18,7 @@ import javax.swing.border.EmptyBorder
|
|||||||
class ExceptionListWindow(eList: List<ExceptionDetails>, future: CompletableFuture<IUserInterface.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<ExceptionDetails>) : AbstractListModel<String>() {
|
private class ExceptionListModel(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
|
@@ -0,0 +1,150 @@
|
|||||||
|
package link.infra.packwiz.installer.ui.gui
|
||||||
|
|
||||||
|
import link.infra.packwiz.installer.ui.IUserInterface
|
||||||
|
import link.infra.packwiz.installer.ui.IUserInterface.ExceptionListResult
|
||||||
|
import link.infra.packwiz.installer.ui.data.ExceptionDetails
|
||||||
|
import link.infra.packwiz.installer.ui.data.IOptionDetails
|
||||||
|
import link.infra.packwiz.installer.ui.data.InstallProgress
|
||||||
|
import link.infra.packwiz.installer.util.Log
|
||||||
|
import java.awt.EventQueue
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import javax.swing.JDialog
|
||||||
|
import javax.swing.JOptionPane
|
||||||
|
import javax.swing.UIManager
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
class GUIHandler : IUserInterface {
|
||||||
|
private lateinit var frmPackwizlauncher: InstallWindow
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
override var optionsButtonPressed = false
|
||||||
|
@Volatile
|
||||||
|
override var cancelButtonPressed = false
|
||||||
|
@Volatile
|
||||||
|
override var firstInstall = false
|
||||||
|
|
||||||
|
override var title = "packwiz-installer"
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
EventQueue.invokeLater { frmPackwizlauncher.title = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
EventQueue.invokeAndWait {
|
||||||
|
try {
|
||||||
|
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.warn("Failed to set look and feel", e)
|
||||||
|
}
|
||||||
|
frmPackwizlauncher = InstallWindow(this).apply {
|
||||||
|
title = this@GUIHandler.title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun show() = EventQueue.invokeLater {
|
||||||
|
frmPackwizlauncher.isVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() = EventQueue.invokeAndWait {
|
||||||
|
frmPackwizlauncher.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showErrorAndExit(message: String, e: Exception?): Nothing {
|
||||||
|
val buttons = arrayOf("Quit", if (firstInstall) "Continue without installing" else "Continue without updating")
|
||||||
|
if (e != null) {
|
||||||
|
Log.fatal(message, e)
|
||||||
|
EventQueue.invokeAndWait {
|
||||||
|
val result = JOptionPane.showOptionDialog(frmPackwizlauncher,
|
||||||
|
"$message: $e",
|
||||||
|
title,
|
||||||
|
JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE, null, buttons, buttons[0])
|
||||||
|
if (result == 1) {
|
||||||
|
Log.info("User selected to continue without installing/updating, exiting with code 0...")
|
||||||
|
exitProcess(0)
|
||||||
|
} else {
|
||||||
|
Log.info("User selected to quit, exiting with code 1...")
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.fatal(message)
|
||||||
|
EventQueue.invokeAndWait {
|
||||||
|
val result = JOptionPane.showOptionDialog(frmPackwizlauncher,
|
||||||
|
message,
|
||||||
|
title,
|
||||||
|
JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE, null, buttons, buttons[0])
|
||||||
|
if (result == 1) {
|
||||||
|
Log.info("User selected to continue without installing/updating, exiting with code 0...")
|
||||||
|
exitProcess(0)
|
||||||
|
} else {
|
||||||
|
Log.info("User selected to quit, exiting with code 1...")
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun submitProgress(progress: InstallProgress) {
|
||||||
|
val sb = StringBuilder()
|
||||||
|
if (progress.hasProgress) {
|
||||||
|
sb.append('(')
|
||||||
|
sb.append(progress.progress)
|
||||||
|
sb.append('/')
|
||||||
|
sb.append(progress.progressTotal)
|
||||||
|
sb.append(") ")
|
||||||
|
}
|
||||||
|
sb.append(progress.message)
|
||||||
|
Log.info(sb.toString())
|
||||||
|
EventQueue.invokeLater {
|
||||||
|
frmPackwizlauncher.displayProgress(progress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showOptions(options: List<IOptionDetails>): Boolean {
|
||||||
|
val future = CompletableFuture<Boolean>()
|
||||||
|
EventQueue.invokeAndWait {
|
||||||
|
if (options.isEmpty()) {
|
||||||
|
JOptionPane.showMessageDialog(null,
|
||||||
|
"This modpack has no optional mods!",
|
||||||
|
"Optional mods", JOptionPane.INFORMATION_MESSAGE)
|
||||||
|
future.complete(false)
|
||||||
|
} else {
|
||||||
|
OptionsSelectWindow(options, future, frmPackwizlauncher).apply {
|
||||||
|
defaultCloseOperation = JDialog.DISPOSE_ON_CLOSE
|
||||||
|
isVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return future.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showExceptions(exceptions: List<ExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): ExceptionListResult {
|
||||||
|
val future = CompletableFuture<ExceptionListResult>()
|
||||||
|
EventQueue.invokeLater {
|
||||||
|
ExceptionListWindow(exceptions, future, numTotal, allowsIgnore, frmPackwizlauncher).apply {
|
||||||
|
defaultCloseOperation = JDialog.DISPOSE_ON_CLOSE
|
||||||
|
isVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return future.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun disableOptionsButton(hasOptions: Boolean) = EventQueue.invokeLater {
|
||||||
|
frmPackwizlauncher.disableOptionsButton(hasOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showCancellationDialog(): IUserInterface.CancellationResult {
|
||||||
|
val future = CompletableFuture<IUserInterface.CancellationResult>()
|
||||||
|
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) IUserInterface.CancellationResult.QUIT else IUserInterface.CancellationResult.CONTINUE)
|
||||||
|
}
|
||||||
|
return future.get()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,86 @@
|
|||||||
|
package link.infra.packwiz.installer.ui.gui
|
||||||
|
|
||||||
|
import link.infra.packwiz.installer.ui.data.InstallProgress
|
||||||
|
import java.awt.BorderLayout
|
||||||
|
import java.awt.Component
|
||||||
|
import java.awt.GridBagConstraints
|
||||||
|
import java.awt.GridBagLayout
|
||||||
|
import javax.swing.*
|
||||||
|
import javax.swing.border.EmptyBorder
|
||||||
|
|
||||||
|
class InstallWindow(private val handler: GUIHandler) : JFrame() {
|
||||||
|
private var lblProgresslabel: JLabel
|
||||||
|
private var progressBar: JProgressBar
|
||||||
|
private var btnOptions: JButton
|
||||||
|
|
||||||
|
init {
|
||||||
|
setBounds(100, 100, 493, 95)
|
||||||
|
// Works better with tiling window managers - there isn't any reason to change window size currently anyway
|
||||||
|
isResizable = false
|
||||||
|
defaultCloseOperation = EXIT_ON_CLOSE
|
||||||
|
setLocationRelativeTo(null)
|
||||||
|
|
||||||
|
// Progress bar and loading text
|
||||||
|
add(JPanel().apply {
|
||||||
|
border = EmptyBorder(10, 10, 10, 10)
|
||||||
|
layout = BorderLayout(0, 0)
|
||||||
|
|
||||||
|
progressBar = JProgressBar().apply {
|
||||||
|
isIndeterminate = true
|
||||||
|
}
|
||||||
|
add(progressBar, BorderLayout.CENTER)
|
||||||
|
|
||||||
|
lblProgresslabel = JLabel("Loading...")
|
||||||
|
add(lblProgresslabel, BorderLayout.SOUTH)
|
||||||
|
}, BorderLayout.CENTER)
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
add(JPanel().apply {
|
||||||
|
border = EmptyBorder(0, 5, 0, 5)
|
||||||
|
layout = GridBagLayout()
|
||||||
|
|
||||||
|
btnOptions = JButton("Optional mods...").apply {
|
||||||
|
alignmentX = Component.CENTER_ALIGNMENT
|
||||||
|
|
||||||
|
addActionListener {
|
||||||
|
text = "Loading..."
|
||||||
|
isEnabled = false
|
||||||
|
handler.optionsButtonPressed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add(btnOptions, GridBagConstraints().apply {
|
||||||
|
gridx = 0
|
||||||
|
gridy = 0
|
||||||
|
})
|
||||||
|
|
||||||
|
add(JButton("Cancel").apply {
|
||||||
|
addActionListener {
|
||||||
|
isEnabled = false
|
||||||
|
handler.cancelButtonPressed = true
|
||||||
|
}
|
||||||
|
}, GridBagConstraints().apply {
|
||||||
|
gridx = 0
|
||||||
|
gridy = 1
|
||||||
|
})
|
||||||
|
}, BorderLayout.EAST)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun displayProgress(progress: InstallProgress) {
|
||||||
|
if (progress.hasProgress) {
|
||||||
|
progressBar.isIndeterminate = false
|
||||||
|
progressBar.value = progress.progress
|
||||||
|
progressBar.maximum = progress.progressTotal
|
||||||
|
} else {
|
||||||
|
progressBar.isIndeterminate = true
|
||||||
|
progressBar.value = 0
|
||||||
|
}
|
||||||
|
lblProgresslabel.text = progress.message
|
||||||
|
}
|
||||||
|
|
||||||
|
fun disableOptionsButton(hasOptions: Boolean) {
|
||||||
|
btnOptions.apply {
|
||||||
|
text = if (hasOptions) { "Optional mods..." } else { "No optional mods" }
|
||||||
|
isEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,6 @@
|
|||||||
package link.infra.packwiz.installer.ui
|
package link.infra.packwiz.installer.ui.gui
|
||||||
|
|
||||||
|
import link.infra.packwiz.installer.ui.data.IOptionDetails
|
||||||
|
|
||||||
// Serves as a proxy for IOptionDetails, so that setOptionValue isn't called until OK is clicked
|
// Serves as a proxy for IOptionDetails, so that setOptionValue isn't called until OK is clicked
|
||||||
internal class OptionTempHandler(private val opt: IOptionDetails) : IOptionDetails {
|
internal class OptionTempHandler(private val opt: IOptionDetails) : IOptionDetails {
|
@@ -1,5 +1,6 @@
|
|||||||
package link.infra.packwiz.installer.ui
|
package link.infra.packwiz.installer.ui.gui
|
||||||
|
|
||||||
|
import link.infra.packwiz.installer.ui.data.IOptionDetails
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
import java.awt.FlowLayout
|
import java.awt.FlowLayout
|
||||||
import java.awt.event.ActionEvent
|
import java.awt.event.ActionEvent
|
||||||
@@ -18,7 +19,7 @@ class OptionsSelectWindow internal constructor(optList: List<IOptionDetails>, fu
|
|||||||
private val tableModel: OptionTableModel
|
private val tableModel: OptionTableModel
|
||||||
private val future: CompletableFuture<Boolean>
|
private val future: CompletableFuture<Boolean>
|
||||||
|
|
||||||
private class OptionTableModel internal constructor(givenOpts: List<IOptionDetails>) : TableModel {
|
private class OptionTableModel(givenOpts: List<IOptionDetails>) : TableModel {
|
||||||
private val opts: List<OptionTempHandler>
|
private val opts: List<OptionTempHandler>
|
||||||
|
|
||||||
init {
|
init {
|
38
src/main/kotlin/link/infra/packwiz/installer/util/Exts.kt
Normal file
38
src/main/kotlin/link/infra/packwiz/installer/util/Exts.kt
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package link.infra.packwiz.installer.util
|
||||||
|
|
||||||
|
import link.infra.packwiz.installer.ui.IUserInterface
|
||||||
|
|
||||||
|
inline fun <T> iflet(value: T?, whenNotNull: (T) -> Unit) {
|
||||||
|
if (value != null) {
|
||||||
|
whenNotNull(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T, U> IUserInterface.ifletOrErr(value: T?, message: String, whenNotNull: (T) -> U): U =
|
||||||
|
if (value != null) {
|
||||||
|
whenNotNull(value)
|
||||||
|
} else {
|
||||||
|
this.showErrorAndExit(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T, U, V> IUserInterface.ifletOrErr(value: T?, value2: U?, message: String, whenNotNull: (T, U) -> V): V =
|
||||||
|
if (value != null && value2 != null) {
|
||||||
|
whenNotNull(value, value2)
|
||||||
|
} else {
|
||||||
|
this.showErrorAndExit(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T> ifletOrWarn(value: T?, message: String, whenNotNull: (T) -> Unit) {
|
||||||
|
if (value != null) {
|
||||||
|
whenNotNull(value)
|
||||||
|
} else {
|
||||||
|
Log.warn(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T, U> iflet(value: T?, whenNotNull: (T) -> U, whenNull: () -> U): U =
|
||||||
|
if (value != null) {
|
||||||
|
whenNotNull(value)
|
||||||
|
} else {
|
||||||
|
whenNull()
|
||||||
|
}
|
16
src/main/kotlin/link/infra/packwiz/installer/util/Log.kt
Normal file
16
src/main/kotlin/link/infra/packwiz/installer/util/Log.kt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package link.infra.packwiz.installer.util
|
||||||
|
|
||||||
|
object Log {
|
||||||
|
fun info(message: String) = println(message)
|
||||||
|
|
||||||
|
fun warn(message: String) = println("[Warning] $message")
|
||||||
|
fun warn(message: String, exception: Exception) = println("[Warning] $message: $exception")
|
||||||
|
|
||||||
|
fun fatal(message: String) {
|
||||||
|
println("[FATAL] $message")
|
||||||
|
}
|
||||||
|
fun fatal(message: String, exception: Exception) {
|
||||||
|
println("[FATAL] $message: ")
|
||||||
|
exception.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
250
src/main/resources/META-INF/LICENSES.md
Normal file
250
src/main/resources/META-INF/LICENSES.md
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
# Licenses
|
||||||
|
|
||||||
|
packwiz-installer itself is under the MIT license, except for Murmur2Lib and bundled dependencies as follows:
|
||||||
|
|
||||||
|
- Murmur2Lib: Apache 2.0 ([Source](https://github.com/prasanthj/hasher/blob/master/src/main/java/hasher/Murmur2.java))
|
||||||
|
- Google Gson 2.8.1: Apache 2.0 ([Source](https://github.com/google/gson))
|
||||||
|
- Okio 2.9.0: Apache 2.0 ([Source](https://github.com/square/okio/))
|
||||||
|
- Commons CLI 1.4: Apache 2.0 ([Source](http://commons.apache.org/proper/commons-cli/))
|
||||||
|
- Jetbrains Annotations 13.0: Apache 2.0 ([Source](https://github.com/JetBrains/java-annotations))
|
||||||
|
- Kotlin Standard Library 1.4.21: Apache 2.0 ([Source](https://github.com/JetBrains/kotlin))
|
||||||
|
- toml4j 0.7.2: MIT ([Source](https://github.com/mwanji/toml4j))
|
||||||
|
|
||||||
|
## Associated notices
|
||||||
|
|
||||||
|
### Commons CLI
|
||||||
|
Apache Commons CLI
|
||||||
|
Copyright 2001-2017 The Apache Software Foundation
|
||||||
|
|
||||||
|
This product includes software developed at
|
||||||
|
The Apache Software Foundation (http://www.apache.org/).
|
||||||
|
|
||||||
|
## Full license texts
|
||||||
|
|
||||||
|
### MIT
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
### Apache 2.0
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
Reference in New Issue
Block a user