Compare commits

..

11 Commits

Author SHA1 Message Date
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
14 changed files with 151 additions and 173 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

@@ -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

@@ -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

@@ -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
} }
} }