Compare commits

...

8 Commits

Author SHA1 Message Date
comp500
758385c225 Fix onlyOtherSide state not updating properly when side changes (fixes #60) 2023-05-20 05:09:25 +01:00
comp500
304fb802ed Write packwiz.json with UTF8 instead of system charset 2023-04-15 02:03:05 +01:00
comp500
cc063773d8 Only run build on PR (no publishing) 2023-04-12 23:52:54 +01:00
comp500
1deed7dd0d Fix and simplify side matching code (fixes #54) 2023-01-17 05:21:20 +00:00
comp500
ad951b9b44 Download mods for both sides with --side both (fixes #14) 2023-01-17 02:33:07 +00:00
Eric Richter
4e415c1e1a Add option to automatically close after a user-supplied amount of time (#42)
* Add option to automatically close after a user-specific amount of time

Add the -t/--timeout command line option to specific a number of seconds to
wait before automatically closing if there is no update or user-interactivity
required.

* Also use timeout setting when prompting about optional mods after/during an update

Additionally, clears the TODO comment.

* Change default timeout to 10 seconds
2023-01-17 02:30:05 +00:00
comp500
84bbbe0770 Tweak timeout strategy (see #52) 2023-01-15 02:23:18 +00:00
comp500
fa9fe18215 Fix manual link resolution for files not visible in CF API (fixes #48) 2023-01-02 21:15:48 +00:00
12 changed files with 100 additions and 44 deletions

27
.github/workflows/pr.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Java Gradle Build
on:
pull_request
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up JDK 8
uses: actions/setup-java@v2
with:
java-version: '8'
distribution: 'temurin'
cache: gradle
- name: Build with Gradle
run: ./gradlew build
- name: Cleanup Gradle Cache
# Remove some files from the Gradle cache, so they aren't cached by GitHub Actions.
# Restoring these files from a GitHub Actions cache might cause problems for future builds.
run: |
rm -f ~/.gradle/caches/modules-2/modules-2.lock
rm -f ~/.gradle/caches/modules-2/gc.properties

View File

@@ -1,6 +1,9 @@
name: Java Gradle Snapshot
on: ["push", "pull_request"]
on:
push:
branches:
- 'main'
jobs:
build:

View File

@@ -37,7 +37,7 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, va
fun isNewOptional() = isOptional && newOptional
fun correctSide() = metadata.linkedFile?.side?.hasSide(downloadSide) ?: true
fun correctSide() = metadata.linkedFile?.side?.let { downloadSide.hasSide(it) } ?: true
override val name get() = metadata.name
@@ -109,9 +109,9 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, va
cachedFile.optionValue = linkedFile.option.defaultValue
}
}
cachedFile.isOptional = isOptional
cachedFile.onlyOtherSide = !correctSide()
}
cachedFile.isOptional = isOptional
cachedFile.onlyOtherSide = !correctSide()
}
}
}

View File

@@ -103,10 +103,13 @@ class Main(args: Array<String>) {
val manifestFile = ui.wrap("Invalid manifest file path") {
packFolder / (cmd.getOptionValue("meta-file") ?: "packwiz.json")
}
val timeout = ui.wrap("Invalid timeout value") {
cmd.getOptionValue("timeout")?.toLong() ?: 10
}
// Start update process!
try {
UpdateManager(UpdateManager.Options(packFile, manifestFile, packFolder, multimcFolder, side), ui)
UpdateManager(UpdateManager.Options(packFile, manifestFile, packFolder, multimcFolder, side, timeout), ui)
} catch (e: Exception) {
ui.showErrorAndExit("Update process failed", e)
}
@@ -123,6 +126,7 @@ class Main(args: Array<String>) {
options.addOption(null, "pack-folder", true, "Folder to install the pack to (defaults to the JAR directory)")
options.addOption(null, "multimc-folder", true, "The MultiMC pack folder (defaults to the parent of the pack directory)")
options.addOption(null, "meta-file", true, "JSON file to store pack metadata, relative to the pack folder (defaults to packwiz.json)")
options.addOption("t", "timeout", true, "Seconds to wait before automatically launching when asking about optional mods (defaults to 10)")
}
// TODO: link these somehow so they're only defined once?

View File

@@ -23,7 +23,6 @@ import link.infra.packwiz.installer.ui.IUserInterface.ExceptionListResult
import link.infra.packwiz.installer.ui.data.InstallProgress
import link.infra.packwiz.installer.util.Log
import okio.buffer
import java.io.FileWriter
import java.io.IOException
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
@@ -48,7 +47,8 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
val manifestFile: PackwizFilePath,
val packFolder: PackwizFilePath,
val multimcFolder: PackwizFilePath,
val side: Side
val side: Side,
val timeout: Long,
)
// TODO: make this return a value based on results?
@@ -157,7 +157,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
// todo: --force?
ui.submitProgress(InstallProgress("Modpack is already up to date!", 1, 1))
if (manifest.cachedFiles.any { it.value.isOptional }) {
ui.awaitOptionalButton(false)
ui.awaitOptionalButton(false, opts.timeout)
}
if (!ui.optionsButtonPressed) {
return
@@ -196,7 +196,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
manifest.cachedSide = opts.side
try {
FileWriter(opts.manifestFile.nioPath.toFile()).use { writer -> gson.toJson(manifest, writer) }
Files.newBufferedWriter(opts.manifestFile.nioPath, StandardCharsets.UTF_8).use { writer -> gson.toJson(manifest, writer) }
} catch (e: IOException) {
ui.showErrorAndExit("Failed to save local manifest file", e)
}
@@ -206,7 +206,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
if (manifest.indexFileHash == indexHash && invalidatedFiles.isEmpty()) {
ui.submitProgress(InstallProgress("Modpack files are already up to date!", 1, 1))
if (manifest.cachedFiles.any { it.value.isOptional }) {
ui.awaitOptionalButton(false)
ui.awaitOptionalButton(false, opts.timeout)
}
if (!ui.optionsButtonPressed) {
return
@@ -338,7 +338,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
if (!ui.optionsButtonPressed) {
// TODO: this is so ugly
ui.submitProgress(InstallProgress("Reconfigure optional mods?", 0,1))
ui.awaitOptionalButton(true)
ui.awaitOptionalButton(true, opts.timeout)
if (ui.cancelButtonPressed) {
showCancellationDialog()
return

View File

@@ -97,6 +97,16 @@ fun resolveCfMetadata(mods: List<IndexFile.File>, packFolder: PackwizFilePath, c
}
}
// Some file types don't show up in the API at all! (e.g. shaderpacks)
// Add unresolved files to manualDownloadMods
for ((fileId, file) in fileIdMap) {
if (file.linkedFile != null) {
if (file.linkedFile!!.resolvedUpdateData["curseforge"] == null) {
manualDownloadMods[(file.linkedFile!!.update["curseforge"] as CurseForgeUpdateData).projectId] = Pair(file, fileId)
}
}
}
if (manualDownloadMods.isNotEmpty()) {
val reqModsData = GetModsRequest(manualDownloadMods.keys.toList())
val reqMods = Request.Builder()

View File

@@ -8,9 +8,12 @@ import java.net.SocketTimeoutException
import java.util.concurrent.TimeUnit
class ClientHolder {
// Tries 10s timeouts (default), then 15s timeouts, then 60s timeouts
private val retryTimes = arrayOf(15, 60)
// TODO: a button to increase timeouts temporarily when retrying? manual retry button?
val okHttpClient by lazy { OkHttpClient.Builder()
// Retry requests up to 3 times, increasing the timeouts slightly if it failed
// Retry requests according to retryTimes list
.addInterceptor {
val req = it.request()
@@ -24,20 +27,20 @@ class ClientHolder {
}
var tryCount = 0
while (res == null && tryCount < 3) {
tryCount++
Log.info("OkHttp connection to ${req.url} timed out; retrying... ($tryCount/3)")
while (res == null && tryCount < retryTimes.size) {
Log.info("OkHttp connection to ${req.url} timed out; retrying... (${tryCount + 1}/${retryTimes.size})")
val longerTimeoutChain = it
.withConnectTimeout(10 * tryCount, TimeUnit.SECONDS)
.withReadTimeout(10 * tryCount, TimeUnit.SECONDS)
.withWriteTimeout(10 * tryCount, TimeUnit.SECONDS)
.withConnectTimeout(retryTimes[tryCount], TimeUnit.SECONDS)
.withReadTimeout(retryTimes[tryCount], TimeUnit.SECONDS)
.withWriteTimeout(retryTimes[tryCount], TimeUnit.SECONDS)
try {
res = longerTimeoutChain.proceed(req)
} catch (e: SocketTimeoutException) {
lastException = e
}
tryCount++
}
res ?: throw lastException!!

View File

@@ -4,42 +4,29 @@ import cc.ekblad.toml.model.TomlValue
import cc.ekblad.toml.tomlMapper
import com.google.gson.annotations.SerializedName
enum class Side {
enum class Side(sideName: String) {
@SerializedName("client")
CLIENT("client"),
@SerializedName("server")
SERVER("server"),
@SerializedName("both")
@Suppress("unused")
BOTH("both", arrayOf(CLIENT, SERVER));
BOTH("both") {
override fun hasSide(tSide: Side): Boolean {
return true
}
};
private val sideName: String
private val depSides: Array<Side>?
constructor(sideName: String) {
init {
this.sideName = sideName.lowercase()
depSides = null
}
constructor(sideName: String, depSides: Array<Side>) {
this.sideName = sideName.lowercase()
this.depSides = depSides
}
override fun toString() = sideName
fun hasSide(tSide: Side): Boolean {
if (this == tSide) {
return true
}
if (depSides != null) {
for (depSide in depSides) {
if (depSide == tSide) {
return true
}
}
}
return false
open fun hasSide(tSide: Side): Boolean {
return this == tSide || tSide == BOTH
}
companion object {

View File

@@ -25,7 +25,7 @@ interface IUserInterface {
fun showUpdateConfirmationDialog(oldVersions: List<Pair<String, String?>>, newVersions: List<Pair<String, String?>>): UpdateConfirmationResult = UpdateConfirmationResult.CANCELLED
fun awaitOptionalButton(showCancel: Boolean)
fun awaitOptionalButton(showCancel: Boolean, timeout: Long)
enum class ExceptionListResult {
CONTINUE, CANCEL, IGNORE

View File

@@ -63,7 +63,7 @@ class CLIHandler : IUserInterface {
return ExceptionListResult.CANCEL
}
override fun awaitOptionalButton(showCancel: Boolean) {
override fun awaitOptionalButton(showCancel: Boolean, timeout: Long) {
// Do nothing
}
}

View File

@@ -7,11 +7,13 @@ 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.Timer
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CountDownLatch
import javax.swing.JDialog
import javax.swing.JOptionPane
import javax.swing.UIManager
import kotlin.concurrent.timer
import kotlin.system.exitProcess
class GUIHandler : IUserInterface {
@@ -220,12 +222,28 @@ class GUIHandler : IUserInterface {
return future.get()
}
override fun awaitOptionalButton(showCancel: Boolean) {
override fun awaitOptionalButton(showCancel: Boolean, timeout: Long) {
EventQueue.invokeAndWait {
frmPackwizlauncher.showOk(!showCancel)
}
visibleCountdownLatch.await()
var closeTimer: Timer? = null
if (timeout >= 0) {
var count = 0
closeTimer = timer("timeout", true, 0, 1000) {
if (count >= timeout) {
optionalSelectedLatch.countDown()
cancel()
} else {
frmPackwizlauncher.timeoutOk(timeout - count)
count += 1
}
};
}
optionalSelectedLatch.await()
closeTimer?.cancel()
EventQueue.invokeLater {
frmPackwizlauncher.hideOk()
}

View File

@@ -121,4 +121,8 @@ class InstallWindow(private val handler: GUIHandler) : JFrame() {
}
buttonsPanel.revalidate()
}
fun timeoutOk(remaining: Long) {
btnOk.text = "Continue ($remaining)"
}
}