Compare commits

..

No commits in common. "main" and "v0.5.7" have entirely different histories.
main ... v0.5.7

9 changed files with 97 additions and 198 deletions

View File

@ -1,27 +0,0 @@
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,9 +1,6 @@
name: Java Gradle Snapshot name: Java Gradle Snapshot
on: on: ["push", "pull_request"]
push:
branches:
- 'main'
jobs: jobs:
build: build:

View File

@ -21,7 +21,6 @@ import java.nio.file.StandardCopyOption
internal class DownloadTask private constructor(val metadata: IndexFile.File, val index: IndexFile, private val downloadSide: Side) : IOptionDetails { internal class DownloadTask private constructor(val metadata: IndexFile.File, val index: IndexFile, private val downloadSide: Side) : IOptionDetails {
var cachedFile: ManifestFile.File? = null var cachedFile: ManifestFile.File? = null
private set
private var err: Exception? = null private var err: Exception? = null
val exceptionDetails get() = err?.let { e -> ExceptionDetails(name, e) } val exceptionDetails get() = err?.let { e -> ExceptionDetails(name, e) }
@ -29,24 +28,10 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, va
fun failed() = err != null fun failed() = err != null
var alreadyUpToDate = false var alreadyUpToDate = false
private set
private var metadataRequired = true private var metadataRequired = true
private var invalidated = false private var invalidated = false
// If file is new or isOptional changed to true, the option needs to be presented again // If file is new or isOptional changed to true, the option needs to be presented again
private var newOptional = true private var newOptional = true
var completionStatus = CompletionStatus.INCOMPLETE
private set
enum class CompletionStatus {
INCOMPLETE,
DOWNLOADED,
ALREADY_EXISTS_CACHED,
ALREADY_EXISTS_VALIDATED,
SKIPPED_DISABLED,
SKIPPED_WRONG_SIDE,
DELETED_DISABLED,
DELETED_WRONG_SIDE;
}
val isOptional get() = metadata.linkedFile?.option?.optional ?: false val isOptional get() = metadata.linkedFile?.option?.optional ?: false
@ -91,7 +76,6 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, va
if (currHash == cachedFile.hash) { // Already up to date if (currHash == cachedFile.hash) { // Already up to date
alreadyUpToDate = true alreadyUpToDate = true
metadataRequired = false metadataRequired = false
completionStatus = CompletionStatus.ALREADY_EXISTS_CACHED
} }
} }
if (cachedFile.isOptional) { if (cachedFile.isOptional) {
@ -125,12 +109,12 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, va
cachedFile.optionValue = linkedFile.option.defaultValue cachedFile.optionValue = linkedFile.option.defaultValue
} }
} }
}
cachedFile.isOptional = isOptional cachedFile.isOptional = isOptional
cachedFile.onlyOtherSide = !correctSide() cachedFile.onlyOtherSide = !correctSide()
} }
} }
} }
}
/** /**
* Check if the file in the destination location is already valid * Check if the file in the destination location is already valid
@ -159,7 +143,6 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, va
fileSource.buffer().readAll(blackholeSink()) fileSource.buffer().readAll(blackholeSink())
if (hash == fileSource.hash) { if (hash == fileSource.hash) {
alreadyUpToDate = true alreadyUpToDate = true
completionStatus = CompletionStatus.ALREADY_EXISTS_VALIDATED
// Update the manifest file // Update the manifest file
cachedFile = (cachedFile ?: ManifestFile.File()).also { cachedFile = (cachedFile ?: ManifestFile.File()).also {
@ -198,18 +181,10 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, va
if (it.cachedLocation != null) { if (it.cachedLocation != null) {
// Ensure wrong-side or optional false files are removed // Ensure wrong-side or optional false files are removed
try { try {
completionStatus = if (Files.deleteIfExists(it.cachedLocation!!.nioPath)) { Files.deleteIfExists(it.cachedLocation!!.nioPath)
if (correctSide()) { CompletionStatus.DELETED_DISABLED } else { CompletionStatus.DELETED_WRONG_SIDE }
} else {
if (correctSide()) { CompletionStatus.SKIPPED_DISABLED } else { CompletionStatus.SKIPPED_WRONG_SIDE }
}
} catch (e: IOException) { } catch (e: IOException) {
Log.warn("Failed to delete file", e) Log.warn("Failed to delete file", e)
} }
} else {
completionStatus =
if (correctSide()) { CompletionStatus.SKIPPED_DISABLED }
else { CompletionStatus.SKIPPED_WRONG_SIDE }
} }
it.cachedLocation = null it.cachedLocation = null
return return
@ -309,8 +284,6 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, va
} }
} }
} }
completionStatus = CompletionStatus.DOWNLOADED
} }
companion object { companion object {

View File

@ -48,7 +48,6 @@ class LauncherUtils internal constructor(private val opts: UpdateManager.Options
val modLoaders = hashMapOf( val modLoaders = hashMapOf(
"net.minecraft" to "minecraft", "net.minecraft" to "minecraft",
"net.minecraftforge" to "forge", "net.minecraftforge" to "forge",
"net.neoforged" to "neoforge",
"net.fabricmc.fabric-loader" to "fabric", "net.fabricmc.fabric-loader" to "fabric",
"org.quiltmc.quilt-loader" to "quilt", "org.quiltmc.quilt-loader" to "quilt",
"com.mumfrey.liteloader" to "liteloader" "com.mumfrey.liteloader" to "liteloader"
@ -60,7 +59,6 @@ class LauncherUtils internal constructor(private val opts: UpdateManager.Options
"org.lwjgl" to -1, "org.lwjgl" to -1,
"org.lwjgl3" to -1, "org.lwjgl3" to -1,
"net.minecraftforge" to 5, "net.minecraftforge" to 5,
"net.neoforged" to 5,
"net.fabricmc.fabric-loader" to 10, "net.fabricmc.fabric-loader" to 10,
"org.quiltmc.quilt-loader" to 10, "org.quiltmc.quilt-loader" to 10,
"com.mumfrey.liteloader" to 10, "com.mumfrey.liteloader" to 10,

View File

@ -23,6 +23,7 @@ import link.infra.packwiz.installer.ui.IUserInterface.ExceptionListResult
import link.infra.packwiz.installer.ui.data.InstallProgress import link.infra.packwiz.installer.ui.data.InstallProgress
import link.infra.packwiz.installer.util.Log import link.infra.packwiz.installer.util.Log
import okio.buffer import okio.buffer
import java.io.FileWriter
import java.io.IOException import java.io.IOException
import java.io.InputStreamReader import java.io.InputStreamReader
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
@ -127,11 +128,8 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
ui.submitProgress(InstallProgress("Checking local files...")) ui.submitProgress(InstallProgress("Checking local files..."))
// If the side changes, invalidate EVERYTHING (even when the index hasn't changed)
val invalidateAll = opts.side != manifest.cachedSide
val invalidatedUris: MutableList<PackwizFilePath> = ArrayList()
if (!invalidateAll) {
// Invalidation checking must be done here, as it must happen before pack/index hashes are checked // Invalidation checking must be done here, as it must happen before pack/index hashes are checked
val invalidatedUris: MutableList<PackwizFilePath> = ArrayList()
for ((fileUri, file) in manifest.cachedFiles) { for ((fileUri, file) in manifest.cachedFiles) {
// ignore onlyOtherSide files // ignore onlyOtherSide files
if (file.onlyOtherSide) { if (file.onlyOtherSide) {
@ -166,7 +164,6 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
return return
} }
} }
}
Log.info("Modpack name: ${pf.name}") Log.info("Modpack name: ${pf.name}")
@ -181,7 +178,6 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
pf.index.hashFormat, pf.index.hashFormat,
manifest, manifest,
invalidatedUris, invalidatedUris,
invalidateAll,
clientHolder clientHolder
) )
} catch (e1: Exception) { } catch (e1: Exception) {
@ -201,14 +197,13 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
manifest.cachedSide = opts.side manifest.cachedSide = opts.side
try { try {
Files.newBufferedWriter(opts.manifestFile.nioPath, StandardCharsets.UTF_8).use { writer -> gson.toJson(manifest, writer) } FileWriter(opts.manifestFile.nioPath.toFile()).use { writer -> gson.toJson(manifest, writer) }
} catch (e: IOException) { } catch (e: IOException) {
ui.showErrorAndExit("Failed to save local manifest file", e) ui.showErrorAndExit("Failed to save local manifest file", e)
} }
} }
private fun processIndex(indexUri: PackwizPath<*>, indexHash: Hash<*>, hashFormat: HashFormat<*>, manifest: ManifestFile, invalidatedFiles: List<PackwizFilePath>, invalidateAll: Boolean, clientHolder: ClientHolder) { private fun processIndex(indexUri: PackwizPath<*>, indexHash: Hash<*>, hashFormat: HashFormat<*>, manifest: ManifestFile, invalidatedFiles: List<PackwizFilePath>, clientHolder: ClientHolder) {
if (!invalidateAll) {
if (manifest.indexFileHash == indexHash && invalidatedFiles.isEmpty()) { if (manifest.indexFileHash == indexHash && invalidatedFiles.isEmpty()) {
ui.submitProgress(InstallProgress("Modpack files are already up to date!", 1, 1)) ui.submitProgress(InstallProgress("Modpack files are already up to date!", 1, 1))
if (manifest.cachedFiles.any { it.value.isOptional }) { if (manifest.cachedFiles.any { it.value.isOptional }) {
@ -222,7 +217,6 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
return return
} }
} }
}
manifest.indexFileHash = indexHash manifest.indexFileHash = indexHash
val indexFileSource = try { val indexFileSource = try {
@ -247,17 +241,31 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
} }
ui.submitProgress(InstallProgress("Checking local files...")) ui.submitProgress(InstallProgress("Checking local files..."))
// TODO: use kotlin filtering/FP rather than an iterator?
val it: MutableIterator<Map.Entry<PackwizFilePath, ManifestFile.File>> = manifest.cachedFiles.entries.iterator() val it: MutableIterator<Map.Entry<PackwizFilePath, ManifestFile.File>> = manifest.cachedFiles.entries.iterator()
while (it.hasNext()) { while (it.hasNext()) {
val (uri, file) = it.next() val (uri, file) = it.next()
if (file.cachedLocation != null) { if (file.cachedLocation != null) {
var alreadyDeleted = false
// Delete if option value has been set to false
if (file.isOptional && !file.optionValue) {
try {
Files.deleteIfExists(file.cachedLocation!!.nioPath)
} catch (e: IOException) {
Log.warn("Failed to delete optional disabled file", e)
}
// Set to null, as it doesn't exist anymore
file.cachedLocation = null
alreadyDeleted = true
}
if (indexFile.files.none { it.file.rebase(opts.packFolder) == uri }) { // File has been removed from the index if (indexFile.files.none { it.file.rebase(opts.packFolder) == uri }) { // File has been removed from the index
if (!alreadyDeleted) {
try { try {
Files.deleteIfExists(file.cachedLocation!!.nioPath) Files.deleteIfExists(file.cachedLocation!!.nioPath)
} catch (e: IOException) { } catch (e: IOException) {
Log.warn("Failed to delete file removed from index", e) Log.warn("Failed to delete file removed from index", e)
} }
Log.info("Deleted ${file.cachedLocation!!.filename} (removed from pack)") }
it.remove() it.remove()
} }
} }
@ -274,6 +282,9 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
Log.warn("Index is empty!") Log.warn("Index is empty!")
} }
val tasks = createTasksFromIndex(indexFile, opts.side) val tasks = createTasksFromIndex(indexFile, 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) { if (invalidateAll) {
Log.info("Side changed, invalidating all mods") Log.info("Side changed, invalidating all mods")
} }
@ -387,16 +398,7 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
val progress = if (exDetails != null) { val progress = if (exDetails != null) {
"Failed to download ${exDetails.name}: ${exDetails.exception.message}" "Failed to download ${exDetails.name}: ${exDetails.exception.message}"
} else { } else {
when (task.completionStatus) { "Downloaded ${task.name}"
DownloadTask.CompletionStatus.INCOMPLETE -> "${task.name} pending (you should never see this...)"
DownloadTask.CompletionStatus.DOWNLOADED -> "Downloaded ${task.name}"
DownloadTask.CompletionStatus.ALREADY_EXISTS_CACHED -> "${task.name} already exists (cached)"
DownloadTask.CompletionStatus.ALREADY_EXISTS_VALIDATED -> "${task.name} already exists (validated)"
DownloadTask.CompletionStatus.SKIPPED_DISABLED -> "Skipped ${task.name} (disabled)"
DownloadTask.CompletionStatus.SKIPPED_WRONG_SIDE -> "Skipped ${task.name} (wrong side)"
DownloadTask.CompletionStatus.DELETED_DISABLED -> "Deleted ${task.name} (disabled)"
DownloadTask.CompletionStatus.DELETED_WRONG_SIDE -> "Deleted ${task.name} (wrong side)"
}
} }
ui.submitProgress(InstallProgress(progress, i + 1, tasks.size)) ui.submitProgress(InstallProgress(progress, i + 1, tasks.size))

View File

@ -49,15 +49,14 @@ private val APIKey = "JDJhJDEwJHNBWVhqblU1N0EzSmpzcmJYM3JVdk92UWk2NHBLS3BnQ2VpbG
@Throws(JsonSyntaxException::class, JsonIOException::class) @Throws(JsonSyntaxException::class, JsonIOException::class)
fun resolveCfMetadata(mods: List<IndexFile.File>, packFolder: PackwizFilePath, clientHolder: ClientHolder): List<ExceptionDetails> { fun resolveCfMetadata(mods: List<IndexFile.File>, packFolder: PackwizFilePath, clientHolder: ClientHolder): List<ExceptionDetails> {
val failures = mutableListOf<ExceptionDetails>() val failures = mutableListOf<ExceptionDetails>()
val fileIdMap = mutableMapOf<Int, List<IndexFile.File>>() val fileIdMap = mutableMapOf<Int, IndexFile.File>()
for (mod in mods) { for (mod in mods) {
if (!mod.linkedFile!!.update.contains("curseforge")) { if (!mod.linkedFile!!.update.contains("curseforge")) {
failures.add(ExceptionDetails(mod.linkedFile!!.name, Exception("Failed to resolve CurseForge metadata: no CurseForge update section"))) failures.add(ExceptionDetails(mod.linkedFile!!.name, Exception("Failed to resolve CurseForge metadata: no CurseForge update section")))
continue continue
} }
val fileId = (mod.linkedFile!!.update["curseforge"] as CurseForgeUpdateData).fileId fileIdMap[(mod.linkedFile!!.update["curseforge"] as CurseForgeUpdateData).fileId] = mod
fileIdMap[fileId] = (fileIdMap[fileId] ?: listOf()) + mod
} }
val reqData = GetFilesRequest(fileIdMap.keys.toList()) val reqData = GetFilesRequest(fileIdMap.keys.toList())
@ -78,7 +77,7 @@ fun resolveCfMetadata(mods: List<IndexFile.File>, packFolder: PackwizFilePath, c
val resData = Gson().fromJson(res.body!!.charStream(), GetFilesResponse::class.java) val resData = Gson().fromJson(res.body!!.charStream(), GetFilesResponse::class.java)
res.closeQuietly() res.closeQuietly()
val manualDownloadMods = mutableMapOf<Int, List<Int>>() val manualDownloadMods = mutableMapOf<Int, Pair<IndexFile.File, Int>>()
for (file in resData.data) { for (file in resData.data) {
if (!fileIdMap.contains(file.id)) { if (!fileIdMap.contains(file.id)) {
failures.add(ExceptionDetails(file.id.toString(), failures.add(ExceptionDetails(file.id.toString(),
@ -86,14 +85,12 @@ fun resolveCfMetadata(mods: List<IndexFile.File>, packFolder: PackwizFilePath, c
continue continue
} }
if (file.downloadUrl == null) { if (file.downloadUrl == null) {
manualDownloadMods[file.modId] = (manualDownloadMods[file.modId] ?: listOf()) + file.id manualDownloadMods[file.modId] = Pair(fileIdMap[file.id]!!, file.id)
continue continue
} }
try { try {
for (indexFile in fileIdMap[file.id]!!) { fileIdMap[file.id]!!.linkedFile!!.resolvedUpdateData["curseforge"] =
indexFile.linkedFile!!.resolvedUpdateData["curseforge"] =
HttpUrlPath(file.downloadUrl!!.toHttpUrl()) HttpUrlPath(file.downloadUrl!!.toHttpUrl())
}
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
failures.add(ExceptionDetails(file.id.toString(), failures.add(ExceptionDetails(file.id.toString(),
Exception("Failed to parse URL: ${file.downloadUrl} for ID ${file.id}, Project ID ${file.modId}", e))) Exception("Failed to parse URL: ${file.downloadUrl} for ID ${file.id}, Project ID ${file.modId}", e)))
@ -102,13 +99,10 @@ fun resolveCfMetadata(mods: List<IndexFile.File>, packFolder: PackwizFilePath, c
// Some file types don't show up in the API at all! (e.g. shaderpacks) // Some file types don't show up in the API at all! (e.g. shaderpacks)
// Add unresolved files to manualDownloadMods // Add unresolved files to manualDownloadMods
for ((fileId, indexFiles) in fileIdMap) { for ((fileId, file) in fileIdMap) {
for (file in indexFiles) {
if (file.linkedFile != null) { if (file.linkedFile != null) {
if (file.linkedFile!!.resolvedUpdateData["curseforge"] == null) { if (file.linkedFile!!.resolvedUpdateData["curseforge"] == null) {
val projectId = (file.linkedFile!!.update["curseforge"] as CurseForgeUpdateData).projectId manualDownloadMods[(file.linkedFile!!.update["curseforge"] as CurseForgeUpdateData).projectId] = Pair(file, fileId)
manualDownloadMods[projectId] = (manualDownloadMods[projectId] ?: listOf()) + fileId
}
} }
} }
} }
@ -139,19 +133,9 @@ fun resolveCfMetadata(mods: List<IndexFile.File>, packFolder: PackwizFilePath, c
continue continue
} }
for (fileId in manualDownloadMods[mod.id]!!) { val modFile = manualDownloadMods[mod.id]!!
if (!fileIdMap.contains(fileId)) { failures.add(ExceptionDetails(mod.name, Exception("This mod is excluded from the CurseForge API and must be downloaded manually.\n" +
failures.add(ExceptionDetails(mod.name, "Please go to ${mod.links?.websiteUrl}/files/${modFile.second} and save this file to ${modFile.first.destURI.rebase(packFolder).nioPath.absolute()}")))
Exception("Failed to find file from result: file ID $fileId")))
continue
}
for (indexFile in fileIdMap[fileId]!!) {
var modUrl = "${mod.links?.websiteUrl}/files/${fileId}"
failures.add(ExceptionDetails(indexFile.name, Exception("This mod is excluded from the CurseForge API and must be downloaded manually.\n" +
"Please go to ${modUrl} and save this file to ${indexFile.destURI.rebase(packFolder).nioPath.absolute()}"), modUrl))
}
}
} }
} }

View File

@ -23,10 +23,7 @@ interface IUserInterface {
fun showCancellationDialog(): CancellationResult = CancellationResult.QUIT fun showCancellationDialog(): CancellationResult = CancellationResult.QUIT
fun showUpdateConfirmationDialog(oldVersions: List<Pair<String, String?>>, newVersions: List<Pair<String, String?>>): UpdateConfirmationResult { fun showUpdateConfirmationDialog(oldVersions: List<Pair<String, String?>>, newVersions: List<Pair<String, String?>>): UpdateConfirmationResult = UpdateConfirmationResult.CANCELLED
// Always update metadata when using the CLI
return UpdateConfirmationResult.UPDATE
}
fun awaitOptionalButton(showCancel: Boolean, timeout: Long) fun awaitOptionalButton(showCancel: Boolean, timeout: Long)

View File

@ -2,6 +2,5 @@ package link.infra.packwiz.installer.ui.data
data class ExceptionDetails( data class ExceptionDetails(
val name: String, val name: String,
val exception: Exception, val exception: Exception
val modUrl: String? = null
) )

View File

@ -1,6 +1,5 @@
package link.infra.packwiz.installer.ui.gui package link.infra.packwiz.installer.ui.gui
import link.infra.packwiz.installer.util.Log
import link.infra.packwiz.installer.ui.IUserInterface import link.infra.packwiz.installer.ui.IUserInterface
import link.infra.packwiz.installer.ui.data.ExceptionDetails import link.infra.packwiz.installer.ui.data.ExceptionDetails
import java.awt.BorderLayout import java.awt.BorderLayout
@ -25,24 +24,6 @@ class ExceptionListWindow(eList: List<ExceptionDetails>, future: CompletableFutu
fun getExceptionAt(index: Int) = details[index].exception fun getExceptionAt(index: Int) = details[index].exception
} }
private fun openUrl(url: String) {
try {
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
Desktop.getDesktop().browse(URI(url))
} else {
val process = Runtime.getRuntime().exec(arrayOf("xdg-open", url));
val exitValue = process.waitFor()
if (exitValue > 0) {
Log.warn("Failed to open $url: xdg-open exited with code $exitValue")
}
}
} catch (e: IOException) {
Log.warn("Failed to open $url", e)
} catch (e: URISyntaxException) {
Log.warn("Failed to open $url", e)
}
}
/** /**
* Create the dialog. * Create the dialog.
*/ */
@ -131,19 +112,6 @@ class ExceptionListWindow(eList: List<ExceptionDetails>, future: CompletableFutu
this@ExceptionListWindow.dispose() this@ExceptionListWindow.dispose()
} }
}) })
val missingMods = eList.filter { it.modUrl != null }.map { it.modUrl!! }.toSet()
if (!missingMods.isEmpty()) {
add(JButton("Open missing mods").apply {
toolTipText = "Open missing mods in your browser"
addActionListener {
missingMods.forEach {
openUrl(it)
}
}
})
}
}, BorderLayout.EAST) }, BorderLayout.EAST)
// Errored label // Errored label
@ -154,8 +122,16 @@ class ExceptionListWindow(eList: List<ExceptionDetails>, future: CompletableFutu
// Left buttons // Left buttons
add(JPanel().apply { add(JPanel().apply {
add(JButton("Report issue").apply { add(JButton("Report issue").apply {
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
addActionListener { addActionListener {
openUrl("https://github.com/packwiz/packwiz-installer/issues/new") try {
Desktop.getDesktop().browse(URI("https://github.com/packwiz/packwiz-installer/issues/new"))
} catch (e: IOException) {
// lol the button just won't work i guess
} catch (e: URISyntaxException) {}
}
} else {
isEnabled = false
} }
}) })
}, BorderLayout.WEST) }, BorderLayout.WEST)