Compare commits

...

16 Commits

Author SHA1 Message Date
comp500
f52cd19ad4 Show download exceptions properly in CLI 2020-12-11 18:18:10 +00:00
comp500
60887a4312 Whoops 2020-12-07 17:42:52 +00:00
comp500
a368268038 Fix support for symlinked directories 2020-12-07 17:38:21 +00:00
comp500
8beded7b41 Improve UX when there are no optional mods 2020-12-06 19:05:56 +00:00
comp500
91060dcd54 Put an error message there. Later is now! 2020-11-30 00:24:47 +00:00
comp500
e06ee21f3b Add User-Agent to download requests 2020-10-22 20:53:36 +01:00
comp500
b3370739a5 Fix Swing multithreading issue, clean up slightly 2020-09-29 02:14:56 +01:00
comp500
ecc6f0440a Remove IntelliJ metadata from repo 2020-09-29 02:14:05 +01:00
comp500
92b44352b3 Fix RequestHandlerGithub heuristics, so that Github Releases files work properly 2020-06-20 03:15:18 +01:00
comp500
1d5a787b02 Add JvmStatic to fix --help command (bootstrapper calls these) 2020-06-16 04:05:02 +01:00
comp500
b5983800e8 Update README.md 2020-05-11 17:48:41 +01:00
comp500
4b3c279e71 Add support for loading from file:// URIs 2020-05-08 22:57:03 +01:00
comp500
b413371306 Fix --help command 2020-05-08 18:08:53 +01:00
comp500
1d2ec61232 Fix disgusting getNewLoc call (!! already checks null!!) 2020-02-07 03:12:52 +00:00
comp500
a0da889a02 Optimise memory usage while computing Murmur2 2019-12-23 16:31:36 +00:00
comp500
432bb4e25f Fix Murmur2 hash implementation 2019-12-23 16:20:38 +00:00
18 changed files with 199 additions and 192 deletions

30
.gitignore vendored
View File

@@ -1,9 +1,9 @@
# Created by https://www.gitignore.io/api/java,gradle,intellij # Created by https://www.toptal.com/developers/gitignore/api/java,gradle,intellij+all
# Edit at https://www.gitignore.io/?templates=java,gradle,intellij # Edit at https://www.toptal.com/developers/gitignore?templates=java,gradle,intellij+all
### Intellij ### ### Intellij+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff # User-specific stuff
@@ -33,6 +33,9 @@
# When using Gradle or Maven with auto-import, you should exclude module files, # When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using # since they will be recreated, and may cause churn. Uncomment if using
# auto-import. # auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml # .idea/modules.xml
# .idea/*.iml # .idea/*.iml
# .idea/modules # .idea/modules
@@ -72,13 +75,18 @@ fabric.properties
# Android studio 3.1+ serialized cache file # Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser .idea/caches/build_file_checksums.ser
### Intellij Patch ### ### Intellij+all Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 # Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
# *.iml .idea/
# modules.xml
# .idea/misc.xml # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
# *.ipr
*.iml
modules.xml
.idea/misc.xml
*.ipr
# Sonarlint plugin # Sonarlint plugin
.idea/sonarlint .idea/sonarlint
@@ -127,4 +135,4 @@ gradle-app.setting
### Gradle Patch ### ### Gradle Patch ###
**/build/ **/build/
# End of https://www.gitignore.io/api/java,gradle,intellij # End of https://www.toptal.com/developers/gitignore/api/java,gradle,intellij+all

View File

@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

View File

@@ -1,36 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="JavaDoc" enabled="true" level="WARNING" enabled_by_default="true">
<option name="TOP_LEVEL_CLASS_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="INNER_CLASS_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="METHOD_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="@return@param@throws or @exception" />
</value>
</option>
<option name="FIELD_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="IGNORE_DEPRECATED" value="false" />
<option name="IGNORE_JAVADOC_PERIOD" value="true" />
<option name="IGNORE_DUPLICATED_THROWS" value="false" />
<option name="IGNORE_POINT_TO_ITSELF" value="false" />
<option name="myAdditionalJavadocTags" value="wbp.parser.entryPoint" />
</inspection_tool>
</profile>
</component>

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
</component>
</project>

10
.idea/misc.xml generated
View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<list size="1">
<item index="0" class="java.lang.String" itemvalue="com.google.gson.annotations.SerializedName" />
</list>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="11" project-jdk-type="JavaSDK" />
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -1,2 +1,2 @@
# packwiz-installer # packwiz-installer
An installer for launching packwiz modpacks with MultiMC. An installer for launching packwiz modpacks with MultiMC. You'll need [the bootstrapper](https://github.com/comp500/packwiz-installer-bootstrap/releases) to actually use this.

View File

@@ -74,13 +74,13 @@ public class Murmur2Lib {
int left = length - len_m; int left = length - len_m;
if (left != 0) { if (left != 0) {
if (left >= 3) { if (left >= 3) {
h ^= (int) data[length - 3] << 16; h ^= (int) data[length - (left - 2)] << 16;
} }
if (left >= 2) { if (left >= 2) {
h ^= (int) data[length - 2] << 8; h ^= (int) data[length - (left - 1)] << 8;
} }
if (left >= 1) { if (left >= 1) {
h ^= (int) data[length - 1]; h ^= data[length - left];
} }
h *= M_32; h *= M_32;
@@ -152,7 +152,7 @@ public class Murmur2Lib {
case 2: case 2:
h ^= (long) (data[tailStart + 1] & 0xff) << 8; h ^= (long) (data[tailStart + 1] & 0xff) << 8;
case 1: case 1:
h ^= (long) (data[tailStart] & 0xff); h ^= data[tailStart] & 0xff;
h *= M_64; h *= M_64;
} }

View File

@@ -152,6 +152,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,7 +179,14 @@ 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: 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 {

View File

@@ -1,3 +1,5 @@
@file:JvmName("Main")
package link.infra.packwiz.installer package link.infra.packwiz.installer
import link.infra.packwiz.installer.metadata.SpaceSafeURI import link.infra.packwiz.installer.metadata.SpaceSafeURI
@@ -16,6 +18,9 @@ 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
var guiEnabled = !GraphicsEnvironment.isHeadless()
private fun startup(args: Array<String>) { private fun startup(args: Array<String>) {
val options = Options() val options = Options()
addNonBootstrapOptions(options) addNonBootstrapOptions(options)
@@ -26,19 +31,24 @@ class Main(args: Array<String>) {
parser.parse(options, args) parser.parse(options, args)
} catch (e: ParseException) { } catch (e: ParseException) {
e.printStackTrace() e.printStackTrace()
try { if (guiEnabled) {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()) EventQueue.invokeAndWait {
} catch (e1: Exception) { try {
// Ignore the exceptions, just continue using the ugly L&F UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
} 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, e.message, "packwiz-installer", JOptionPane.ERROR_MESSAGE)
exitProcess(1) exitProcess(1)
} }
// if "headless", GUI creation will fail anyway! if (guiEnabled && cmd.hasOption("no-gui")) {
val ui = if (cmd.hasOption("no-gui") || GraphicsEnvironment.isHeadless()) { guiEnabled = false
CLIHandler() }
} else InstallWindow()
val ui = if (guiEnabled) InstallWindow() else CLIHandler()
val unparsedArgs = cmd.args val unparsedArgs = cmd.args
if (unparsedArgs.size > 1) { if (unparsedArgs.size > 1) {
@@ -47,15 +57,13 @@ class Main(args: Array<String>) {
ui.handleExceptionAndExit(RuntimeException("URI to install from must be specified!")) ui.handleExceptionAndExit(RuntimeException("URI to install from must be specified!"))
} }
cmd.getOptionValue("title")?.also { cmd.getOptionValue("title")?.also(ui::setTitle)
ui.setTitle(it)
}
val inputStateHandler = InputStateHandler() val inputStateHandler = InputStateHandler()
ui.show(inputStateHandler) ui.show(inputStateHandler)
val uOptions = UpdateManager.Options().apply { val uOptions = UpdateManager.Options().apply {
side = cmd.getOptionValue("side")?.let { UpdateManager.Options.Side.from(it) } ?: side side = cmd.getOptionValue("side")?.let((UpdateManager.Options.Side)::from) ?: side
packFolder = cmd.getOptionValue("pack-folder") ?: packFolder packFolder = cmd.getOptionValue("pack-folder") ?: packFolder
manifestFile = cmd.getOptionValue("meta-file") ?: manifestFile manifestFile = cmd.getOptionValue("meta-file") ?: manifestFile
} }
@@ -84,6 +92,7 @@ class Main(args: Array<String>) {
companion object { companion object {
// Called by packwiz-installer-bootstrap to set up the help command // Called by packwiz-installer-bootstrap to set up the help command
@JvmStatic
fun addNonBootstrapOptions(options: Options) { fun addNonBootstrapOptions(options: Options) {
options.addOption("s", "side", true, "Side to install mods from (client/server, defaults to client)") options.addOption("s", "side", true, "Side to install mods from (client/server, defaults to client)")
options.addOption(null, "title", true, "Title of the installer window") options.addOption(null, "title", true, "Title of the installer window")
@@ -92,6 +101,7 @@ class Main(args: Array<String>) {
} }
// TODO: link these somehow so they're only defined once? // TODO: link these somehow so they're only defined once?
@JvmStatic
private fun addBootstrapOptions(options: Options) { private fun addBootstrapOptions(options: Options) {
options.addOption(null, "bootstrap-update-url", true, "Github API URL for checking for updates") options.addOption(null, "bootstrap-update-url", true, "Github API URL for checking for updates")
options.addOption(null, "bootstrap-update-token", true, "Github API Access Token, for private repositories") options.addOption(null, "bootstrap-update-token", true, "Github API Access Token, for private repositories")
@@ -109,11 +119,13 @@ class Main(args: Array<String>) {
startup(args) startup(args)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
EventQueue.invokeLater { if (guiEnabled) {
JOptionPane.showMessageDialog(null, EventQueue.invokeLater {
JOptionPane.showMessageDialog(null,
"A fatal error occurred: \n" + e.javaClass.canonicalName + ": " + e.message, "A fatal error occurred: \n" + e.javaClass.canonicalName + ": " + e.message,
"packwiz-installer", JOptionPane.ERROR_MESSAGE) "packwiz-installer", JOptionPane.ERROR_MESSAGE)
exitProcess(1) exitProcess(1)
}
} }
// In case the EventQueue is broken, exit after 1 minute // In case the EventQueue is broken, exit after 1 minute
Thread.sleep(60 * 1000.toLong()) Thread.sleep(60 * 1000.toLong())

View File

@@ -126,7 +126,6 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
ui.submitProgress(InstallProgress("Loading pack file...")) ui.submitProgress(InstallProgress("Loading pack file..."))
val packFileSource = try { val packFileSource = try {
Objects.requireNonNull(opts.downloadURI)
val src = getFileSource(opts.downloadURI!!) val src = getFileSource(opts.downloadURI!!)
getHasher("sha256").getHashingSource(src) getHasher("sha256").getHashingSource(src)
} catch (e: Exception) { } catch (e: Exception) {
@@ -191,12 +190,16 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
handleCancellation() handleCancellation()
} }
try { try {
// This is badly written, I'll probably heavily refactor it at some point val index = pf.index!!
// The port to Kotlin made this REALLY messy!!!! getNewLoc(opts.downloadURI, index.file)?.let { newLoc ->
getNewLoc(opts.downloadURI, Objects.requireNonNull(pf.index)!!.file)?.let { index.hashFormat?.let { hashFormat ->
pf.index!!.hashFormat?.let { it1 -> processIndex(
processIndex(it, newLoc,
getHash(Objects.requireNonNull(pf.index!!.hashFormat)!!, Objects.requireNonNull(pf.index!!.hash)!!), it1, manifest, invalidatedUris) getHash(index.hashFormat!!, index.hash!!),
hashFormat,
manifest,
invalidatedUris
)
} }
} }
} catch (e1: Exception) { } catch (e1: Exception) {
@@ -252,8 +255,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
return return
} }
if (!indexFileSource.hashIsEqual(indexHash)) { if (!indexFileSource.hashIsEqual(indexHash)) {
// TODO: throw exception ui.handleExceptionAndExit(RuntimeException("Your index hash is invalid! Please run packwiz refresh on the pack again"))
println("I was meant to put an error message here but I'll do that later")
return return
} }
if (stateHandler.cancelButton) { if (stateHandler.cancelButton) {

View File

@@ -10,7 +10,7 @@ class Murmur2Hasher : IHasher {
val tempBuffer = Buffer() val tempBuffer = Buffer()
override val hash: Hash by lazy(LazyThreadSafetyMode.NONE) { override val hash: Hash by lazy(LazyThreadSafetyMode.NONE) {
val data = computeNormalizedArray(internalBuffer.readByteArray()) val data = internalBuffer.readByteArray()
Murmur2Hash(Murmur2Lib.hash32(data, data.size, 1)) Murmur2Hash(Murmur2Lib.hash32(data, data.size, 1))
} }
@@ -19,27 +19,42 @@ class Murmur2Hasher : IHasher {
val out = delegate.read(tempBuffer, byteCount) val out = delegate.read(tempBuffer, byteCount)
if (out > -1) { if (out > -1) {
sink.write(tempBuffer.clone(), out) sink.write(tempBuffer.clone(), out)
internalBuffer.write(tempBuffer, out) computeNormalizedBufferFaster(tempBuffer, internalBuffer)
} }
return out return out
} }
// Credit to https://github.com/modmuss50/CAV2/blob/master/murmur.go // Credit to https://github.com/modmuss50/CAV2/blob/master/murmur.go
private fun computeNormalizedArray(input: ByteArray): ByteArray { // private fun computeNormalizedArray(input: ByteArray): ByteArray {
val output = ByteArray(input.size) // val output = ByteArray(input.size)
// var index = 0
// for (b in input) {
// when (b) {
// 9.toByte(), 10.toByte(), 13.toByte(), 32.toByte() -> {}
// else -> {
// output[index] = b
// index++
// }
// }
// }
// val outputTrimmed = ByteArray(index)
// System.arraycopy(output, 0, outputTrimmed, 0, index)
// return outputTrimmed
// }
private fun computeNormalizedBufferFaster(input: Buffer, output: Buffer) {
var index = 0 var index = 0
for (b in input) { val arr = input.readByteArray()
when (b.toInt()) { for (b in arr) {
9, 10, 13, 32 -> {} when (b) {
9.toByte(), 10.toByte(), 13.toByte(), 32.toByte() -> {}
else -> { else -> {
output[index] = b arr[index] = b
index++ index++
} }
} }
} }
val outputTrimmed = ByteArray(index) output.write(arr, 0, index)
System.arraycopy(output, 0, outputTrimmed, 0, index)
return outputTrimmed
} }
} }

View File

@@ -1,6 +1,7 @@
package link.infra.packwiz.installer.request package link.infra.packwiz.installer.request
import link.infra.packwiz.installer.metadata.SpaceSafeURI import link.infra.packwiz.installer.metadata.SpaceSafeURI
import link.infra.packwiz.installer.request.handlers.RequestHandlerFile
import link.infra.packwiz.installer.request.handlers.RequestHandlerGithub import link.infra.packwiz.installer.request.handlers.RequestHandlerGithub
import link.infra.packwiz.installer.request.handlers.RequestHandlerHTTP import link.infra.packwiz.installer.request.handlers.RequestHandlerHTTP
import okio.Source import okio.Source
@@ -9,7 +10,8 @@ object HandlerManager {
private val handlers: List<IRequestHandler> = listOf( private val handlers: List<IRequestHandler> = listOf(
RequestHandlerGithub(), RequestHandlerGithub(),
RequestHandlerHTTP() RequestHandlerHTTP(),
RequestHandlerFile()
) )
@JvmStatic @JvmStatic

View File

@@ -0,0 +1,18 @@
package link.infra.packwiz.installer.request.handlers
import link.infra.packwiz.installer.metadata.SpaceSafeURI
import link.infra.packwiz.installer.request.IRequestHandler
import okio.Source
import okio.source
import java.nio.file.Paths
open class RequestHandlerFile : IRequestHandler {
override fun matchesHandler(loc: SpaceSafeURI): Boolean {
return "file" == loc.scheme
}
override fun getFileSource(loc: SpaceSafeURI): Source? {
val path = Paths.get(loc.toURL().toURI())
return path.source()
}
}

View File

@@ -65,7 +65,7 @@ class RequestHandlerGithub : RequestHandlerZip(true) {
if (!("http" == scheme || "https" == scheme)) { if (!("http" == scheme || "https" == scheme)) {
return false return false
} }
return "github.com" == loc.host // TODO: more match testing?
// TODO: sanity checks, support for more github urls return "github.com" == loc.host && branchMatcherPattern.matcher(loc.path).matches()
} }
} }

View File

@@ -4,6 +4,7 @@ import link.infra.packwiz.installer.metadata.SpaceSafeURI
import link.infra.packwiz.installer.request.IRequestHandler import link.infra.packwiz.installer.request.IRequestHandler
import okio.Source import okio.Source
import okio.source import okio.source
import java.net.HttpURLConnection
open class RequestHandlerHTTP : IRequestHandler { open class RequestHandlerHTTP : IRequestHandler {
override fun matchesHandler(loc: SpaceSafeURI): Boolean { override fun matchesHandler(loc: SpaceSafeURI): Boolean {
@@ -12,14 +13,17 @@ open class RequestHandlerHTTP : IRequestHandler {
} }
override fun getFileSource(loc: SpaceSafeURI): Source? { 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? // 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 { conn.apply {
// 30 second read timeout // 30 second read timeout
readTimeout = 30 * 1000 readTimeout = 30 * 1000
requestMethod = "GET"
} }
return conn.getInputStream().source() return conn.inputStream.source()
} }
} }

View File

@@ -3,6 +3,7 @@ package link.infra.packwiz.installer.ui
import link.infra.packwiz.installer.ui.IUserInterface.ExceptionListResult import link.infra.packwiz.installer.ui.IUserInterface.ExceptionListResult
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.Future import java.util.concurrent.Future
import kotlin.system.exitProcess
class CLIHandler : IUserInterface { class CLIHandler : IUserInterface {
override fun handleException(e: Exception) { override fun handleException(e: Exception) {
@@ -40,8 +41,11 @@ class CLIHandler : IUserInterface {
} }
override fun showExceptions(exceptions: List<ExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): Future<ExceptionListResult> { override fun showExceptions(exceptions: List<ExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): Future<ExceptionListResult> {
val future = CompletableFuture<ExceptionListResult>() println("Failed to download modpack, the following errors were encountered:")
future.complete(ExceptionListResult.CANCEL) for (ex in exceptions) {
return future println(ex.name + ": ")
ex.exception.printStackTrace()
}
exitProcess(1)
} }
} }

View File

@@ -10,10 +10,10 @@ import javax.swing.border.EmptyBorder
import kotlin.system.exitProcess import kotlin.system.exitProcess
class InstallWindow : IUserInterface { class InstallWindow : IUserInterface {
private val frmPackwizlauncher: JFrame private lateinit var frmPackwizlauncher: JFrame
private val lblProgresslabel: JLabel private lateinit var lblProgresslabel: JLabel
private val progressBar: JProgressBar private lateinit var progressBar: JProgressBar
private val btnOptions: JButton private lateinit var btnOptions: JButton
private var inputStateHandler: InputStateHandler? = null private var inputStateHandler: InputStateHandler? = null
private var title = "Updating modpack..." private var title = "Updating modpack..."
@@ -23,55 +23,57 @@ class InstallWindow : IUserInterface {
// TODO: separate JFrame junk from IUserInterface junk? // TODO: separate JFrame junk from IUserInterface junk?
init { init {
frmPackwizlauncher = JFrame().apply { EventQueue.invokeAndWait {
title = this@InstallWindow.title frmPackwizlauncher = JFrame().apply {
setBounds(100, 100, 493, 95) title = this@InstallWindow.title
defaultCloseOperation = JFrame.EXIT_ON_CLOSE setBounds(100, 100, 493, 95)
setLocationRelativeTo(null) defaultCloseOperation = JFrame.EXIT_ON_CLOSE
setLocationRelativeTo(null)
// Progress bar and loading text // Progress bar and loading text
add(JPanel().apply { add(JPanel().apply {
border = EmptyBorder(10, 10, 10, 10) border = EmptyBorder(10, 10, 10, 10)
layout = BorderLayout(0, 0) layout = BorderLayout(0, 0)
progressBar = JProgressBar().apply { progressBar = JProgressBar().apply {
isIndeterminate = true 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(progressBar, BorderLayout.CENTER)
add(btnOptions, GridBagConstraints().apply {
gridx = 0
gridy = 0
})
add(JButton("Cancel").apply { lblProgresslabel = JLabel("Loading...")
addActionListener { add(lblProgresslabel, BorderLayout.SOUTH)
isEnabled = false }, BorderLayout.CENTER)
inputStateHandler?.pressCancelButton()
// 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()
}
} }
}, GridBagConstraints().apply { add(btnOptions, GridBagConstraints().apply {
gridx = 0 gridx = 0
gridy = 1 gridy = 0
}) })
}, BorderLayout.EAST)
add(JButton("Cancel").apply {
addActionListener {
isEnabled = false
inputStateHandler?.pressCancelButton()
}
}, GridBagConstraints().apply {
gridx = 0
gridy = 1
})
}, BorderLayout.EAST)
}
} }
} }
@@ -178,9 +180,16 @@ class InstallWindow : IUserInterface {
override fun showOptions(options: List<IOptionDetails>): Future<Boolean> { override fun showOptions(options: List<IOptionDetails>): Future<Boolean> {
val future = CompletableFuture<Boolean>() val future = CompletableFuture<Boolean>()
EventQueue.invokeLater { EventQueue.invokeLater {
OptionsSelectWindow(options, future, frmPackwizlauncher).apply { if (options.isEmpty()) {
defaultCloseOperation = JDialog.DISPOSE_ON_CLOSE JOptionPane.showMessageDialog(null,
isVisible = true "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 return future
@@ -199,7 +208,7 @@ class InstallWindow : IUserInterface {
override fun disableOptionsButton() { override fun disableOptionsButton() {
btnOptions.apply { btnOptions.apply {
text = "Optional mods..." text = "No optional mods"
isEnabled = false isEnabled = false
} }
} }