mirror of
https://github.com/packwiz/packwiz-installer.git
synced 2025-10-16 16:04:32 +02:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
b143f67acd | ||
|
03b0f1b09b | ||
|
6c6a0100fd | ||
|
6d47c0d61f | ||
|
226e754547 | ||
|
2c02703101 | ||
|
81a60cc759 | ||
|
92afa93fd7 | ||
|
0858c90079 | ||
|
1d4c94f5b6 | ||
|
74ddca5d54 | ||
|
0df48d19a9 | ||
|
f5b22f37a4 | ||
|
f52cd19ad4 | ||
|
60887a4312 | ||
|
a368268038 | ||
|
8beded7b41 | ||
|
91060dcd54 | ||
|
e06ee21f3b |
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019
|
||||
Copyright (c) 2021 comp500
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
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 {
|
||||
java
|
||||
application
|
||||
id("com.github.johnrengelman.shadow") version "5.0.0"
|
||||
id("com.palantir.git-version") version "0.11.0"
|
||||
id("com.github.breadmoirai.github-release") version "2.2.9"
|
||||
kotlin("jvm") version "1.3.61"
|
||||
id("com.github.johnrengelman.shadow") version "6.1.0"
|
||||
id("com.palantir.git-version") version "0.12.3"
|
||||
id("com.github.breadmoirai.github-release") version "2.2.12"
|
||||
kotlin("jvm") version "1.4.21"
|
||||
id("com.github.jk1.dependency-license-report") version "1.16"
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
val shrinkClasspath: Configuration by configurations.creating
|
||||
|
||||
dependencies {
|
||||
implementation("commons-cli:commons-cli:1.4")
|
||||
shrinkClasspath("commons-cli:commons-cli:1.4")
|
||||
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.squareup.okio:okio:2.2.2")
|
||||
implementation("com.squareup.okio:okio:2.9.0")
|
||||
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
|
||||
tasks.shadowJar {
|
||||
dependencies {
|
||||
exclude(dependency("commons-cli:commons-cli:1.4"))
|
||||
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
|
||||
tasks.register<Copy>("copyJar") {
|
||||
from(tasks.shadowJar)
|
||||
from(tasks.named("shrinkJar"))
|
||||
rename("packwiz-installer-(.*)\\.jar", "packwiz-installer.jar")
|
||||
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
|
||||
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
|
||||
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.HashUtils.getHash
|
||||
import link.infra.packwiz.installer.metadata.hash.HashUtils.getHasher
|
||||
import link.infra.packwiz.installer.ui.ExceptionDetails
|
||||
import link.infra.packwiz.installer.ui.IOptionDetails
|
||||
import link.infra.packwiz.installer.ui.data.ExceptionDetails
|
||||
import link.infra.packwiz.installer.ui.data.IOptionDetails
|
||||
import link.infra.packwiz.installer.util.Log
|
||||
import okio.Buffer
|
||||
import okio.HashingSink
|
||||
import okio.buffer
|
||||
@@ -125,6 +126,7 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, de
|
||||
fun download(packFolder: String, indexUri: SpaceSafeURI) {
|
||||
if (err != null) return
|
||||
|
||||
// TODO: is this necessary if we overwrite?
|
||||
// Ensure it is removed
|
||||
cachedFile?.let {
|
||||
if (!it.optionValue || !correctSide()) {
|
||||
@@ -133,8 +135,7 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, de
|
||||
try {
|
||||
Files.deleteIfExists(Paths.get(packFolder, it.cachedLocation))
|
||||
} catch (e: IOException) {
|
||||
// TODO: how much of a problem is this? use log4j/other log library to show warning?
|
||||
e.printStackTrace()
|
||||
Log.warn("Failed to delete file before downloading", e)
|
||||
}
|
||||
it.cachedLocation = null
|
||||
}
|
||||
@@ -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 {
|
||||
val hash: Hash
|
||||
val fileHashFormat: String
|
||||
@@ -176,11 +180,18 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, de
|
||||
}
|
||||
|
||||
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)
|
||||
data.clear()
|
||||
} else {
|
||||
// TODO: no more PRINTLN!!!!!!!!!
|
||||
// TODO: move println to something visible in the error window
|
||||
println("Invalid hash for " + metadata.destURI.toString())
|
||||
println("Calculated: " + fileSource.hash)
|
||||
println("Expected: $hash")
|
||||
|
@@ -3,9 +3,9 @@
|
||||
package link.infra.packwiz.installer
|
||||
|
||||
import link.infra.packwiz.installer.metadata.SpaceSafeURI
|
||||
import link.infra.packwiz.installer.ui.CLIHandler
|
||||
import link.infra.packwiz.installer.ui.InputStateHandler
|
||||
import link.infra.packwiz.installer.ui.InstallWindow
|
||||
import link.infra.packwiz.installer.ui.cli.CLIHandler
|
||||
import link.infra.packwiz.installer.ui.gui.GUIHandler
|
||||
import link.infra.packwiz.installer.util.Log
|
||||
import org.apache.commons.cli.DefaultParser
|
||||
import org.apache.commons.cli.Options
|
||||
import org.apache.commons.cli.ParseException
|
||||
@@ -19,7 +19,7 @@ import kotlin.system.exitProcess
|
||||
@Suppress("unused")
|
||||
class Main(args: Array<String>) {
|
||||
// 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>) {
|
||||
val options = Options()
|
||||
@@ -30,7 +30,7 @@ class Main(args: Array<String>) {
|
||||
val cmd = try {
|
||||
parser.parse(options, args)
|
||||
} catch (e: ParseException) {
|
||||
e.printStackTrace()
|
||||
Log.fatal("Failed to parse command line arguments", e)
|
||||
if (guiEnabled) {
|
||||
EventQueue.invokeAndWait {
|
||||
try {
|
||||
@@ -38,7 +38,8 @@ class Main(args: Array<String>) {
|
||||
} catch (ignored: Exception) {
|
||||
// Ignore the exceptions, just continue using the ugly L&F
|
||||
}
|
||||
JOptionPane.showMessageDialog(null, e.message, "packwiz-installer", JOptionPane.ERROR_MESSAGE)
|
||||
JOptionPane.showMessageDialog(null, "Failed to parse command line arguments: $e",
|
||||
"packwiz-installer", JOptionPane.ERROR_MESSAGE)
|
||||
}
|
||||
}
|
||||
exitProcess(1)
|
||||
@@ -48,46 +49,41 @@ class Main(args: Array<String>) {
|
||||
guiEnabled = false
|
||||
}
|
||||
|
||||
val ui = if (guiEnabled) InstallWindow() else CLIHandler()
|
||||
val ui = if (guiEnabled) GUIHandler() else CLIHandler()
|
||||
|
||||
val unparsedArgs = cmd.args
|
||||
if (unparsedArgs.size > 1) {
|
||||
ui.handleExceptionAndExit(RuntimeException("Too many arguments specified!"))
|
||||
ui.showErrorAndExit("Too many arguments specified!")
|
||||
} else if (unparsedArgs.isEmpty()) {
|
||||
ui.handleExceptionAndExit(RuntimeException("URI to install from must be specified!"))
|
||||
ui.showErrorAndExit("pack.toml URI to install from must be specified!")
|
||||
}
|
||||
|
||||
cmd.getOptionValue("title")?.also(ui::setTitle)
|
||||
|
||||
val inputStateHandler = InputStateHandler()
|
||||
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
|
||||
val title = cmd.getOptionValue("title")
|
||||
if (title != null) {
|
||||
ui.title = title
|
||||
}
|
||||
|
||||
try {
|
||||
uOptions.downloadURI = SpaceSafeURI(unparsedArgs[0])
|
||||
ui.show()
|
||||
|
||||
val uOptions = try {
|
||||
UpdateManager.Options.construct(
|
||||
downloadURI = SpaceSafeURI(unparsedArgs[0]),
|
||||
side = cmd.getOptionValue("side")?.let((UpdateManager.Options.Side)::from),
|
||||
packFolder = cmd.getOptionValue("pack-folder"),
|
||||
manifestFile = cmd.getOptionValue("meta-file")
|
||||
)
|
||||
} catch (e: URISyntaxException) {
|
||||
// TODO: better error message?
|
||||
ui.handleExceptionAndExit(e)
|
||||
ui.showErrorAndExit("Failed to read pack.toml URI", e)
|
||||
}
|
||||
|
||||
// Start update process!
|
||||
// TODO: start in SwingWorker?
|
||||
try {
|
||||
ui.executeManager {
|
||||
try {
|
||||
UpdateManager(uOptions, ui, inputStateHandler)
|
||||
} catch (e: Exception) { // TODO: better error message?
|
||||
ui.handleExceptionAndExit(e)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) { // TODO: better error message?
|
||||
ui.handleExceptionAndExit(e)
|
||||
UpdateManager(uOptions, ui)
|
||||
} catch (e: Exception) {
|
||||
ui.showErrorAndExit("Update process failed", e)
|
||||
}
|
||||
println("Finished successfully!")
|
||||
ui.dispose()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -118,17 +114,17 @@ class Main(args: Array<String>) {
|
||||
try {
|
||||
startup(args)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.fatal("Error from main", e)
|
||||
if (guiEnabled) {
|
||||
EventQueue.invokeLater {
|
||||
JOptionPane.showMessageDialog(null,
|
||||
"A fatal error occurred: \n" + e.javaClass.canonicalName + ": " + e.message,
|
||||
"A fatal error occurred: \n$e",
|
||||
"packwiz-installer", JOptionPane.ERROR_MESSAGE)
|
||||
exitProcess(1)
|
||||
}
|
||||
// In case the EventQueue is broken, exit after 1 minute
|
||||
Thread.sleep(60 * 1000.toLong())
|
||||
}
|
||||
// In case the EventQueue is broken, exit after 1 minute
|
||||
Thread.sleep(60 * 1000.toLong())
|
||||
exitProcess(1)
|
||||
}
|
||||
}
|
||||
|
@@ -18,8 +18,10 @@ import link.infra.packwiz.installer.request.HandlerManager.getNewLoc
|
||||
import link.infra.packwiz.installer.ui.IUserInterface
|
||||
import link.infra.packwiz.installer.ui.IUserInterface.CancellationResult
|
||||
import link.infra.packwiz.installer.ui.IUserInterface.ExceptionListResult
|
||||
import link.infra.packwiz.installer.ui.InputStateHandler
|
||||
import link.infra.packwiz.installer.ui.InstallProgress
|
||||
import link.infra.packwiz.installer.ui.data.InstallProgress
|
||||
import link.infra.packwiz.installer.util.Log
|
||||
import link.infra.packwiz.installer.util.ifletOrErr
|
||||
import link.infra.packwiz.installer.util.ifletOrWarn
|
||||
import okio.buffer
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.FileReader
|
||||
@@ -34,7 +36,7 @@ import java.util.concurrent.ExecutorCompletionService
|
||||
import java.util.concurrent.Executors
|
||||
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 cancelledStartGame = false
|
||||
private var errorsOccurred = false
|
||||
@@ -44,11 +46,17 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
||||
}
|
||||
|
||||
data class Options(
|
||||
var downloadURI: SpaceSafeURI? = null,
|
||||
var manifestFile: String = "packwiz.json", // TODO: make configurable
|
||||
var packFolder: String = ".",
|
||||
var side: Side = Side.CLIENT
|
||||
val downloadURI: SpaceSafeURI,
|
||||
val manifestFile: String,
|
||||
val packFolder: String,
|
||||
val side: Side
|
||||
) {
|
||||
// Horrible workaround for default params not working cleanly with nullable values
|
||||
companion object {
|
||||
fun construct(downloadURI: SpaceSafeURI, manifestFile: String?, packFolder: String?, side: Side?) =
|
||||
Options(downloadURI, manifestFile ?: "packwiz.json", packFolder ?: ".", side ?: Side.CLIENT)
|
||||
}
|
||||
|
||||
enum class Side {
|
||||
@SerializedName("client")
|
||||
CLIENT("client"),
|
||||
@@ -110,39 +118,35 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
||||
gson.fromJson(FileReader(Paths.get(opts.packFolder, opts.manifestFile).toString()),
|
||||
ManifestFile::class.java)
|
||||
} catch (e: FileNotFoundException) {
|
||||
ui.firstInstall = true
|
||||
ManifestFile()
|
||||
} catch (e: JsonSyntaxException) {
|
||||
ui.handleExceptionAndExit(e)
|
||||
return
|
||||
ui.showErrorAndExit("Invalid local manifest file, try deleting ${opts.manifestFile}", e)
|
||||
} catch (e: JsonIOException) {
|
||||
ui.handleExceptionAndExit(e)
|
||||
return
|
||||
ui.showErrorAndExit("Failed to read local manifest file, try deleting ${opts.manifestFile}", e)
|
||||
}
|
||||
|
||||
if (stateHandler.cancelButton) {
|
||||
if (ui.cancelButtonPressed) {
|
||||
showCancellationDialog()
|
||||
handleCancellation()
|
||||
}
|
||||
|
||||
ui.submitProgress(InstallProgress("Loading pack file..."))
|
||||
val packFileSource = try {
|
||||
val src = getFileSource(opts.downloadURI!!)
|
||||
val src = getFileSource(opts.downloadURI)
|
||||
getHasher("sha256").getHashingSource(src)
|
||||
} catch (e: Exception) {
|
||||
// TODO: run cancellation window?
|
||||
ui.handleExceptionAndExit(e)
|
||||
return
|
||||
ui.showErrorAndExit("Failed to download pack.toml", e)
|
||||
}
|
||||
val pf = packFileSource.buffer().use {
|
||||
try {
|
||||
Toml().read(it.inputStream()).to(PackFile::class.java)
|
||||
} catch (e: IllegalStateException) {
|
||||
ui.handleExceptionAndExit(e)
|
||||
return
|
||||
ui.showErrorAndExit("Failed to parse pack.toml", e)
|
||||
}
|
||||
}
|
||||
|
||||
if (stateHandler.cancelButton) {
|
||||
if (ui.cancelButtonPressed) {
|
||||
showCancellationDialog()
|
||||
handleCancellation()
|
||||
}
|
||||
@@ -170,40 +174,41 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
||||
}
|
||||
}
|
||||
if (invalid) {
|
||||
println("File $fileUri invalidated, marked for redownloading")
|
||||
Log.info("File $fileUri invalidated, marked for redownloading")
|
||||
invalidatedUris.add(fileUri)
|
||||
}
|
||||
}
|
||||
|
||||
if (manifest.packFileHash?.let { packFileSource.hashIsEqual(it) } == true && invalidatedUris.isEmpty()) {
|
||||
println("Modpack is already up to date!")
|
||||
Log.info("Modpack is already up to date!")
|
||||
// todo: --force?
|
||||
if (!stateHandler.optionsButton) {
|
||||
if (!ui.optionsButtonPressed) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
println("Modpack name: " + pf.name)
|
||||
Log.info("Modpack name: ${pf.name}")
|
||||
|
||||
if (stateHandler.cancelButton) {
|
||||
if (ui.cancelButtonPressed) {
|
||||
showCancellationDialog()
|
||||
handleCancellation()
|
||||
}
|
||||
try {
|
||||
val index = pf.index!!
|
||||
getNewLoc(opts.downloadURI, index.file)?.let { newLoc ->
|
||||
index.hashFormat?.let { hashFormat ->
|
||||
processIndex(
|
||||
newLoc,
|
||||
getHash(index.hashFormat!!, index.hash!!),
|
||||
hashFormat,
|
||||
manifest,
|
||||
invalidatedUris
|
||||
)
|
||||
ifletOrWarn(pf.index, "No index file found") { index ->
|
||||
ui.ifletOrErr(index.hashFormat, index.hash, "Pack has no hash or hashFormat for index") { hashFormat, hash ->
|
||||
ui.ifletOrErr(getNewLoc(opts.downloadURI, index.file), "Pack has invalid index file: " + index.file) { newLoc ->
|
||||
processIndex(
|
||||
newLoc,
|
||||
getHash(hashFormat, hash),
|
||||
hashFormat,
|
||||
manifest,
|
||||
invalidatedUris
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e1: Exception) {
|
||||
ui.handleExceptionAndExit(e1)
|
||||
ui.showErrorAndExit("Failed to process index file", e1)
|
||||
}
|
||||
|
||||
handleCancellation()
|
||||
@@ -222,8 +227,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
||||
try {
|
||||
FileWriter(Paths.get(opts.packFolder, opts.manifestFile).toString()).use { writer -> gson.toJson(manifest, writer) }
|
||||
} catch (e: IOException) {
|
||||
// TODO: add message?
|
||||
ui.handleException(e)
|
||||
ui.showErrorAndExit("Failed to save local manifest file", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,8 +237,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>) {
|
||||
if (manifest.indexFileHash == indexHash && invalidatedUris.isEmpty()) {
|
||||
println("Modpack files are already up to date!")
|
||||
if (!stateHandler.optionsButton) {
|
||||
Log.info("Modpack files are already up to date!")
|
||||
if (!ui.optionsButtonPressed) {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -244,22 +248,19 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
||||
val src = getFileSource(indexUri)
|
||||
getHasher(hashFormat).getHashingSource(src)
|
||||
} catch (e: Exception) {
|
||||
// TODO: run cancellation window?
|
||||
ui.handleExceptionAndExit(e)
|
||||
return
|
||||
ui.showErrorAndExit("Failed to download index file", e)
|
||||
}
|
||||
|
||||
val indexFile = try {
|
||||
Toml().read(indexFileSource.buffer().inputStream()).to(IndexFile::class.java)
|
||||
} catch (e: IllegalStateException) {
|
||||
ui.handleExceptionAndExit(e)
|
||||
return
|
||||
ui.showErrorAndExit("Failed to parse index file", e)
|
||||
}
|
||||
if (!indexFileSource.hashIsEqual(indexHash)) {
|
||||
// TODO: throw exception
|
||||
println("I was meant to put an error message here but I'll do that later")
|
||||
return
|
||||
ui.showErrorAndExit("Your index file hash is invalid! The pack developer should packwiz refresh on the pack again")
|
||||
}
|
||||
if (stateHandler.cancelButton) {
|
||||
|
||||
if (ui.cancelButtonPressed) {
|
||||
showCancellationDialog()
|
||||
return
|
||||
}
|
||||
@@ -276,8 +277,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
||||
try {
|
||||
Files.deleteIfExists(Paths.get(opts.packFolder, file.cachedLocation))
|
||||
} catch (e: IOException) {
|
||||
// TODO: should this be shown to the user in some way?
|
||||
e.printStackTrace()
|
||||
Log.warn("Failed to delete optional disabled file", e)
|
||||
}
|
||||
// Set to null, as it doesn't exist anymore
|
||||
file.cachedLocation = null
|
||||
@@ -287,8 +287,8 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
||||
if (!alreadyDeleted) {
|
||||
try {
|
||||
Files.deleteIfExists(Paths.get(opts.packFolder, file.cachedLocation))
|
||||
} catch (e: IOException) { // TODO: should this be shown to the user in some way?
|
||||
e.printStackTrace()
|
||||
} catch (e: IOException) {
|
||||
Log.warn("Failed to delete file removed from index", e)
|
||||
}
|
||||
}
|
||||
it.remove()
|
||||
@@ -296,7 +296,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
||||
}
|
||||
}
|
||||
|
||||
if (stateHandler.cancelButton) {
|
||||
if (ui.cancelButtonPressed) {
|
||||
showCancellationDialog()
|
||||
return
|
||||
}
|
||||
@@ -304,14 +304,14 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
||||
|
||||
// TODO: progress bar?
|
||||
if (indexFile.files.isEmpty()) {
|
||||
println("Warning: Index is empty!")
|
||||
Log.warn("Index is empty!")
|
||||
}
|
||||
val tasks = createTasksFromIndex(indexFile, indexFile.hashFormat, opts.side)
|
||||
// If the side changes, invalidate EVERYTHING just in case
|
||||
// Might not be needed, but done just to be safe
|
||||
val invalidateAll = opts.side != manifest.cachedSide
|
||||
if (invalidateAll) {
|
||||
println("Side changed, invalidating all mods")
|
||||
Log.info("Side changed, invalidating all mods")
|
||||
}
|
||||
tasks.forEach{ f ->
|
||||
// TODO: should linkedfile be checked as well? should this be done in the download section?
|
||||
@@ -327,7 +327,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
||||
f.updateFromCache(file)
|
||||
}
|
||||
|
||||
if (stateHandler.cancelButton) {
|
||||
if (ui.cancelButtonPressed) {
|
||||
showCancellationDialog()
|
||||
return
|
||||
}
|
||||
@@ -338,17 +338,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
||||
val failedTaskDetails = tasks.asSequence().map(DownloadTask::exceptionDetails).filterNotNull().toList()
|
||||
if (failedTaskDetails.isNotEmpty()) {
|
||||
errorsOccurred = true
|
||||
val exceptionListResult: ExceptionListResult
|
||||
exceptionListResult = try {
|
||||
ui.showExceptions(failedTaskDetails, tasks.size, true).get()
|
||||
} catch (e: InterruptedException) { // Interrupted means cancelled???
|
||||
ui.handleExceptionAndExit(e)
|
||||
return
|
||||
} catch (e: ExecutionException) {
|
||||
ui.handleExceptionAndExit(e)
|
||||
return
|
||||
}
|
||||
when (exceptionListResult) {
|
||||
when (ui.showExceptions(failedTaskDetails, tasks.size, true)) {
|
||||
ExceptionListResult.CONTINUE -> {}
|
||||
ExceptionListResult.CANCEL -> {
|
||||
cancelled = true
|
||||
@@ -361,7 +351,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
||||
}
|
||||
}
|
||||
|
||||
if (stateHandler.cancelButton) {
|
||||
if (ui.cancelButtonPressed) {
|
||||
showCancellationDialog()
|
||||
return
|
||||
}
|
||||
@@ -370,23 +360,15 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
||||
val nonFailedFirstTasks = tasks.filter { t -> !t.failed() }.toList()
|
||||
val optionTasks = nonFailedFirstTasks.filter(DownloadTask::correctSide).filter(DownloadTask::isOptional).toList()
|
||||
// If options changed, present all options again
|
||||
if (stateHandler.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
|
||||
val cancelledResult = ui.showOptions(ArrayList(optionTasks))
|
||||
try {
|
||||
if (cancelledResult.get()) {
|
||||
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)
|
||||
if (ui.showOptions(ArrayList(optionTasks))) {
|
||||
cancelled = true
|
||||
handleCancellation()
|
||||
}
|
||||
}
|
||||
ui.disableOptionsButton()
|
||||
// TODO: keep this enabled? then apply changes after download process?
|
||||
ui.disableOptionsButton(optionTasks.isNotEmpty())
|
||||
|
||||
// TODO: different thread pool type?
|
||||
val threadPool = Executors.newFixedThreadPool(10)
|
||||
@@ -398,18 +380,15 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
||||
}
|
||||
}
|
||||
for (i in tasks.indices) {
|
||||
var task: DownloadTask?
|
||||
task = try {
|
||||
val task: DownloadTask = try {
|
||||
completionService.take().get()
|
||||
} catch (e: InterruptedException) {
|
||||
ui.handleException(e)
|
||||
null
|
||||
ui.showErrorAndExit("Interrupted when consuming download tasks", e)
|
||||
} catch (e: ExecutionException) {
|
||||
ui.handleException(e)
|
||||
null
|
||||
ui.showErrorAndExit("Failed to execute download task", e)
|
||||
}
|
||||
// Update manifest - If there were no errors cachedFile has already been modified in place (good old pass by reference)
|
||||
task?.cachedFile?.let { file ->
|
||||
task.cachedFile?.let { file ->
|
||||
if (task.failed()) {
|
||||
val oldFile = file.revert
|
||||
if (oldFile != null) {
|
||||
@@ -420,21 +399,15 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
||||
}
|
||||
}
|
||||
|
||||
var progress: String
|
||||
if (task != null) {
|
||||
val exDetails = task.exceptionDetails
|
||||
if (exDetails != null) {
|
||||
progress = "Failed to download ${exDetails.name}: ${exDetails.exception.message}"
|
||||
exDetails.exception.printStackTrace()
|
||||
} else {
|
||||
progress = "Downloaded ${task.name}"
|
||||
}
|
||||
val exDetails = task.exceptionDetails
|
||||
val progress = if (exDetails != null) {
|
||||
"Failed to download ${exDetails.name}: ${exDetails.exception.message}"
|
||||
} else {
|
||||
progress = "Failed to download, unknown reason"
|
||||
"Downloaded ${task.name}"
|
||||
}
|
||||
ui.submitProgress(InstallProgress(progress, i + 1, tasks.size))
|
||||
|
||||
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()
|
||||
cancelled = true
|
||||
return
|
||||
@@ -447,18 +420,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
||||
val failedTasks2ElectricBoogaloo = nonFailedFirstTasks.asSequence().map(DownloadTask::exceptionDetails).filterNotNull().toList()
|
||||
if (failedTasks2ElectricBoogaloo.isNotEmpty()) {
|
||||
errorsOccurred = true
|
||||
val exceptionListResult: ExceptionListResult
|
||||
exceptionListResult = try {
|
||||
ui.showExceptions(failedTasks2ElectricBoogaloo, tasks.size, false).get()
|
||||
} catch (e: InterruptedException) {
|
||||
// Interrupted means cancelled???
|
||||
ui.handleExceptionAndExit(e)
|
||||
return
|
||||
} catch (e: ExecutionException) {
|
||||
ui.handleExceptionAndExit(e)
|
||||
return
|
||||
}
|
||||
when (exceptionListResult) {
|
||||
when (ui.showExceptions(failedTasks2ElectricBoogaloo, tasks.size, false)) {
|
||||
ExceptionListResult.CONTINUE -> {}
|
||||
ExceptionListResult.CANCEL -> cancelled = true
|
||||
ExceptionListResult.IGNORE -> cancelledStartGame = true
|
||||
@@ -467,23 +429,13 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
|
||||
}
|
||||
|
||||
private fun showCancellationDialog() {
|
||||
val cancellationResult: CancellationResult
|
||||
cancellationResult = try {
|
||||
ui.showCancellationDialog().get()
|
||||
} catch (e: InterruptedException) {
|
||||
// Interrupted means cancelled???
|
||||
ui.handleExceptionAndExit(e)
|
||||
return
|
||||
} catch (e: ExecutionException) {
|
||||
ui.handleExceptionAndExit(e)
|
||||
return
|
||||
}
|
||||
when (cancellationResult) {
|
||||
when (ui.showCancellationDialog()) {
|
||||
CancellationResult.QUIT -> cancelled = true
|
||||
CancellationResult.CONTINUE -> cancelledStartGame = true
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move to UI?
|
||||
private fun handleCancellation() {
|
||||
if (cancelled) {
|
||||
println("Update cancelled by user!")
|
||||
|
@@ -4,7 +4,8 @@ object HashUtils {
|
||||
private val hashTypeConversion: Map<String, IHasher> = mapOf(
|
||||
"sha256" to HashingSourceHasher("sha256"),
|
||||
"sha512" to HashingSourceHasher("sha512"),
|
||||
"murmur2" to Murmur2Hasher()
|
||||
"murmur2" to Murmur2Hasher(),
|
||||
"sha1" to HashingSourceHasher("sha1")
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
|
@@ -5,7 +5,7 @@ import okio.Source
|
||||
|
||||
class HashingSourceHasher internal constructor(private val type: String) : IHasher {
|
||||
// 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) {
|
||||
HashingSourceHash(delegateHashing.hash.hex())
|
||||
}
|
||||
@@ -35,6 +35,7 @@ class HashingSourceHasher internal constructor(private val type: String) : IHash
|
||||
"md5" -> return HashingSourceGeneralHashingSource(HashingSource.md5(delegate))
|
||||
"sha256" -> return HashingSourceGeneralHashingSource(HashingSource.sha256(delegate))
|
||||
"sha512" -> return HashingSourceGeneralHashingSource(HashingSource.sha512(delegate))
|
||||
"sha1" -> return HashingSourceGeneralHashingSource(HashingSource.sha1(delegate))
|
||||
}
|
||||
throw RuntimeException("Invalid hash type provided")
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@ object HandlerManager {
|
||||
RequestHandlerFile()
|
||||
)
|
||||
|
||||
// TODO: get rid of nullable stuff here
|
||||
@JvmStatic
|
||||
fun getNewLoc(base: SpaceSafeURI?, loc: SpaceSafeURI?): SpaceSafeURI? {
|
||||
if (loc == null) {
|
||||
@@ -32,6 +33,8 @@ object HandlerManager {
|
||||
// Zip handler discards once read, requesting multiple times on other handlers would cause multiple downloads
|
||||
// Caching system? Copy from already downloaded files?
|
||||
|
||||
// TODO: change to use something more idiomatic than exceptions?
|
||||
|
||||
@JvmStatic
|
||||
@Throws(Exception::class)
|
||||
fun getFileSource(loc: SpaceSafeURI): Source {
|
||||
|
@@ -4,6 +4,7 @@ import link.infra.packwiz.installer.metadata.SpaceSafeURI
|
||||
import link.infra.packwiz.installer.request.IRequestHandler
|
||||
import okio.Source
|
||||
import okio.source
|
||||
import java.net.HttpURLConnection
|
||||
|
||||
open class RequestHandlerHTTP : IRequestHandler {
|
||||
override fun matchesHandler(loc: SpaceSafeURI): Boolean {
|
||||
@@ -12,14 +13,17 @@ open class RequestHandlerHTTP : IRequestHandler {
|
||||
}
|
||||
|
||||
override fun getFileSource(loc: SpaceSafeURI): Source? {
|
||||
val conn = loc.toURL().openConnection()
|
||||
val conn = loc.toURL().openConnection() as HttpURLConnection
|
||||
// TODO: when do we send specific headers??? should there be a way to signal this?
|
||||
// github *sometimes* requires it, sometimes not!
|
||||
//conn.addRequestProperty("Accept", "application/octet-stream");
|
||||
conn.addRequestProperty("Accept", "application/octet-stream")
|
||||
// TODO: include version?
|
||||
conn.addRequestProperty("User-Agent", "packwiz-installer")
|
||||
|
||||
conn.apply {
|
||||
// 30 second read timeout
|
||||
readTimeout = 30 * 1000
|
||||
requestMethod = "GET"
|
||||
}
|
||||
return conn.getInputStream().source()
|
||||
return conn.inputStream.source()
|
||||
}
|
||||
}
|
@@ -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 readFiles: MutableMap<SpaceSafeURI, Buffer> = HashMap()
|
||||
// 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
|
||||
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.Future
|
||||
import kotlin.system.exitProcess
|
||||
import link.infra.packwiz.installer.ui.data.ExceptionDetails
|
||||
import link.infra.packwiz.installer.ui.data.IOptionDetails
|
||||
import link.infra.packwiz.installer.ui.data.InstallProgress
|
||||
|
||||
interface IUserInterface {
|
||||
fun show(handler: InputStateHandler)
|
||||
fun handleException(e: Exception)
|
||||
@JvmDefault
|
||||
fun handleExceptionAndExit(e: Exception) {
|
||||
handleException(e)
|
||||
exitProcess(1)
|
||||
}
|
||||
fun show()
|
||||
fun dispose()
|
||||
|
||||
@JvmDefault
|
||||
fun setTitle(title: String) {}
|
||||
fun showErrorAndExit(message: String): Nothing {
|
||||
showErrorAndExit(message, null)
|
||||
}
|
||||
fun showErrorAndExit(message: String, e: Exception?): Nothing
|
||||
|
||||
var title: String
|
||||
fun submitProgress(progress: InstallProgress)
|
||||
fun executeManager(task: () -> Unit)
|
||||
// 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>
|
||||
@JvmDefault
|
||||
fun disableOptionsButton() {}
|
||||
fun showExceptions(exceptions: List<ExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): ExceptionListResult
|
||||
fun disableOptionsButton(hasOptions: Boolean) {}
|
||||
|
||||
@JvmDefault
|
||||
fun showCancellationDialog(): Future<CancellationResult> {
|
||||
return CompletableFuture<CancellationResult>().apply {
|
||||
complete(CancellationResult.QUIT)
|
||||
}
|
||||
}
|
||||
fun showCancellationDialog(): CancellationResult = CancellationResult.QUIT
|
||||
|
||||
enum class ExceptionListResult {
|
||||
CONTINUE, CANCEL, IGNORE
|
||||
@@ -38,4 +30,9 @@ interface IUserInterface {
|
||||
enum class CancellationResult {
|
||||
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,221 +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 {
|
||||
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 = "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(
|
||||
val name: String,
|
@@ -1,4 +1,4 @@
|
||||
package link.infra.packwiz.installer.ui
|
||||
package link.infra.packwiz.installer.ui.data
|
||||
|
||||
interface IOptionDetails {
|
||||
val name: String
|
@@ -1,4 +1,4 @@
|
||||
package link.infra.packwiz.installer.ui
|
||||
package link.infra.packwiz.installer.ui.data
|
||||
|
||||
data class InstallProgress(
|
||||
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.Desktop
|
||||
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) {
|
||||
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 getElementAt(index: Int) = details[index].name
|
||||
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
|
||||
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.FlowLayout
|
||||
import java.awt.event.ActionEvent
|
||||
@@ -18,7 +19,7 @@ class OptionsSelectWindow internal constructor(optList: List<IOptionDetails>, fu
|
||||
private val tableModel: OptionTableModel
|
||||
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>
|
||||
|
||||
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