From 02b01b90d7d65fcfc589bc010d1b967b43acbe7b Mon Sep 17 00:00:00 2001
From: comp500 <comp500@users.noreply.github.com>
Date: Wed, 29 Jun 2022 03:26:50 +0100
Subject: [PATCH] Improvements to MultiMC metadata updating

Reformatted and refactored names to fit Kotlin conventions
Closed manifest file reader properly
Fixed switching between loaders, improved intermediary logic
Improved error messages
---
 .../infra/packwiz/installer/LauncherUtils.kt  | 171 ++++++++++--------
 .../infra/packwiz/installer/UpdateManager.kt  |   5 +-
 .../packwiz/installer/ui/gui/GUIHandler.kt    |  17 +-
 3 files changed, 102 insertions(+), 91 deletions(-)

diff --git a/src/main/kotlin/link/infra/packwiz/installer/LauncherUtils.kt b/src/main/kotlin/link/infra/packwiz/installer/LauncherUtils.kt
index 1e9eb05..fb0c43b 100644
--- a/src/main/kotlin/link/infra/packwiz/installer/LauncherUtils.kt
+++ b/src/main/kotlin/link/infra/packwiz/installer/LauncherUtils.kt
@@ -11,98 +11,109 @@ import java.io.File
 import java.nio.file.Paths
 
 class LauncherUtils internal constructor(private val opts: UpdateManager.Options, val ui: IUserInterface) {
-    enum class LauncherStatus {
-        Succesful,
-        NoChanges,
-        Cancelled,
-        NotFound, // We'll use the NotFound as the neutral return type for now
-    }
+	enum class LauncherStatus {
+		SUCCESSFUL,
+		NO_CHANGES,
+		CANCELLED,
+		NOT_FOUND, // When there is no mmc-pack.json file found (i.e. MultiMC is not being used)
+	}
 
-    fun handleMultiMC(pf: PackFile, gson: Gson): LauncherStatus {
-        // MultiMC MC and loader version checker
-        val manifestPath = Paths.get(opts.multimcFolder, "mmc-pack.json").toString()
-        val manifestFile = File(manifestPath)
+	fun handleMultiMC(pf: PackFile, gson: Gson): LauncherStatus {
+		// MultiMC MC and loader version checker
+		val manifestPath = Paths.get(opts.multimcFolder, "mmc-pack.json").toString()
+		val manifestFile = File(manifestPath)
 
-        if (!manifestFile.exists()) {
-            return LauncherStatus.NotFound
-        }
+		if (!manifestFile.exists()) {
+			return LauncherStatus.NOT_FOUND
+		}
 
-        val multimcManifest = try {
-            JsonParser.parseReader(manifestFile.reader())
-        } catch (e: JsonIOException) {
-            throw Exception("Cannot read the MultiMC pack file", e)
-        } catch (e: JsonSyntaxException) {
-            throw Exception("Invalid MultiMC pack file", e)
-        }.asJsonObject
+		val multimcManifest = manifestFile.reader().use {
+			try {
+				JsonParser.parseReader(it)
+			} catch (e: JsonIOException) {
+				throw Exception("Cannot read the MultiMC pack file", e)
+			} catch (e: JsonSyntaxException) {
+				throw Exception("Invalid MultiMC pack file", e)
+			}.asJsonObject
+		}
 
-        Log.info("Loaded MultiMC config")
+		Log.info("Loaded MultiMC config")
 
-        // We only support format 1, if it gets updated in the future we'll have to handle that
-        // There's only version 1 for now tho, so that's good
-        if (multimcManifest["formatVersion"].asInt != 1) {
-            throw Exception("Invalid MultiMC format version")
-        }
+		// We only support format 1, if it gets updated in the future we'll have to handle that
+		// There's only version 1 for now tho, so that's good
+		if (multimcManifest["formatVersion"]?.asInt != 1) {
+			throw Exception("Unsupported MultiMC format version ${multimcManifest["formatVersion"]}")
+		}
 
-        var manifestModified = false
-        val modLoaders = hashMapOf("net.minecraft" to "minecraft", "net.minecraftforge" to "forge", "net.fabricmc.fabric-loader" to "fabric", "org.quiltmc.quilt-loader" to "quilt", "com.mumfrey.liteloader" to "liteloader")
-        val modLoadersClasses = modLoaders.entries.associate{(k,v)-> v to k}
-        var modLoaderFound = false
-        val modLoadersFound = HashMap<String, String>() // Key: modLoader, Value: Version
-        val components = multimcManifest["components"].asJsonArray
-        for (componentObj in components) {
-            val component = componentObj.asJsonObject
+		var manifestModified = false
+		val modLoaders = hashMapOf(
+			"net.minecraft" to "minecraft",
+			"net.minecraftforge" to "forge",
+			"net.fabricmc.fabric-loader" to "fabric",
+			"org.quiltmc.quilt-loader" to "quilt",
+			"com.mumfrey.liteloader" to "liteloader")
+		val modLoadersClasses = modLoaders.entries.associate{(k,v)-> v to k}
+		val loaderVersionsFound = HashMap<String, String?>()
+		val outdatedLoaders = mutableSetOf<String>()
+		val components = multimcManifest["components"]?.asJsonArray ?: throw Exception("Invalid mmc-pack.json: no components key")
+		components.removeAll {
+			val component = it.asJsonObject
 
-            val version = component["version"].asString
-            // If we find any of the modloaders we support, we save it and check the version
-            if (modLoaders.containsKey(component["uid"].asString)) {
-                val modLoader = modLoaders.getValue(component["uid"].asString)
-                if (modLoader != "minecraft")
-                    modLoaderFound = true // Only set to true if modLoader isn't Minecraft
-                modLoadersFound[modLoader] = version
-                if (version != pf.versions?.get(modLoader)) {
-                    manifestModified = true
-                    component.addProperty("version", pf.versions?.get(modLoader))
-                }
-            }
-        }
+			val version = component["version"]?.asString
+			// If we find any of the modloaders we support, we save it and check the version
+			if (modLoaders.containsKey(component["uid"]?.asString)) {
+				val modLoader = modLoaders.getValue(component["uid"]!!.asString)
+				loaderVersionsFound[modLoader] = version
+				if (version != pf.versions?.get(modLoader)) {
+					outdatedLoaders.add(modLoader)
+					true // Delete component; cached metadata is invalid and will be re-added
+				} else {
+					false // Already up to date; cached metadata is valid
+				}
+			} else { false } // Not a known loader / MC
+		}
 
-        // If we can't find the mod loader in the MultiMC file, we add it
-        if (!modLoaderFound) {
-            // Using this filter and loop to handle multiple handlers
-            for ((_, loader) in modLoaders
-                    .filter { it.value != "minecraft" && !modLoadersFound.containsKey(it.value) && pf.versions?.containsKey(it.value) == true }
-            ) {
-                components.add(gson.toJsonTree(hashMapOf("uid" to modLoadersClasses.get(loader), "version" to pf.versions?.get(loader))))
-            }
-        }
+		for ((_, loader) in modLoaders
+			.filter {
+				(!loaderVersionsFound.containsKey(it.value) || outdatedLoaders.contains(it.value))
+					&& pf.versions?.containsKey(it.value) == true }
+		) {
+			manifestModified = true
+			components.add(gson.toJsonTree(
+				hashMapOf("uid" to modLoadersClasses[loader], "version" to pf.versions?.get(loader)))
+			)
+		}
 
-        // If mc version change detected, and fabric mappings are found, delete them, MultiMC will add and re-dl the correct one
-        if (modLoadersFound["minecraft"] != pf.versions?.getValue("minecraft"))
-            components.find { it.asJsonObject["uid"].asString == "net.fabricmc.intermediary" }?.asJsonObject?.let { components.remove(it) }
+		// If inconsistent Intermediary mappings version is found, delete it - MultiMC will add and re-dl the correct one
+		components.find { it.isJsonObject && it.asJsonObject["uid"]?.asString == "net.fabricmc.intermediary" }?.let {
+			if (it.asJsonObject["version"]?.asString != pf.versions?.get("minecraft")) {
+				components.remove(it)
+				manifestModified = true
+			}
+		}
 
-        if (manifestModified) {
-            // The manifest has been modified, so before saving it we'll ask the user
-            // if they wanna update it, continue without updating it, or exit
-            val oldVers = modLoadersFound.map { Pair(it.key, it.value) }
-            val newVers = pf.versions!!.map { Pair(it.key, it.value) }
+		if (manifestModified) {
+			// The manifest has been modified, so before saving it we'll ask the user
+			// if they wanna update it, continue without updating it, or exit
+			val oldVers = loaderVersionsFound.map { Pair(it.key, it.value) }
+			val newVers = pf.versions!!.map { Pair(it.key, it.value) }
 
+			when (ui.showUpdateConfirmationDialog(oldVers, newVers)) {
+				IUserInterface.UpdateConfirmationResult.CANCELLED -> {
+					return LauncherStatus.CANCELLED
+				}
+				IUserInterface.UpdateConfirmationResult.CONTINUE -> {
+					return LauncherStatus.SUCCESSFUL
+				}
+				else -> {}
+			}
 
-            when (ui.showUpdateConfirmationDialog(oldVers, newVers)) {
-                IUserInterface.UpdateConfirmationResult.CANCELLED -> {
-                    return LauncherStatus.Cancelled
-                }
-                IUserInterface.UpdateConfirmationResult.CONTINUE -> {
-                    return LauncherStatus.Succesful // Returning succesful as... Well, the user is telling us to continue
-                }
-                else -> {} // Compiler is giving warning about "non-exhaustive when", so i'll just add an empty one
-            }
+			manifestFile.writeText(gson.toJson(multimcManifest))
+			Log.info("Successfully updated mmc-pack.json based on version metadata")
 
-            manifestFile.writeText(gson.toJson(multimcManifest))
-            Log.info("Updated modpack Minecrafts and/or the modloaders version")
+			return LauncherStatus.SUCCESSFUL
+		}
 
-            return LauncherStatus.Succesful
-        }
-
-        return LauncherStatus.NoChanges
-    }
+		return LauncherStatus.NO_CHANGES
+	}
 }
\ No newline at end of file
diff --git a/src/main/kotlin/link/infra/packwiz/installer/UpdateManager.kt b/src/main/kotlin/link/infra/packwiz/installer/UpdateManager.kt
index 425e538..4bf9812 100644
--- a/src/main/kotlin/link/infra/packwiz/installer/UpdateManager.kt
+++ b/src/main/kotlin/link/infra/packwiz/installer/UpdateManager.kt
@@ -105,8 +105,9 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
 		ui.submitProgress(InstallProgress("Loading MultiMC pack file..."))
 		try {
 			when (lu.handleMultiMC(pf, gson)) {
-				LauncherUtils.LauncherStatus.Cancelled -> cancelled = true
-				LauncherUtils.LauncherStatus.NotFound -> Log.info("MultiMC not detected")
+				LauncherUtils.LauncherStatus.CANCELLED -> cancelled = true
+				LauncherUtils.LauncherStatus.NOT_FOUND -> Log.info("MultiMC not detected")
+				else -> {}
 			}
 			handleCancellation()
 		} catch (e: Exception) {
diff --git a/src/main/kotlin/link/infra/packwiz/installer/ui/gui/GUIHandler.kt b/src/main/kotlin/link/infra/packwiz/installer/ui/gui/GUIHandler.kt
index 508e3cd..427d520 100644
--- a/src/main/kotlin/link/infra/packwiz/installer/ui/gui/GUIHandler.kt
+++ b/src/main/kotlin/link/infra/packwiz/installer/ui/gui/GUIHandler.kt
@@ -202,17 +202,16 @@ class GUIHandler : IUserInterface {
 
 
 			val options = arrayOf("Cancel", "Continue anyways", "Update")
-			val result = JOptionPane.showOptionDialog(frmPackwizlauncher,
-					message,
+			val result = JOptionPane.showOptionDialog(frmPackwizlauncher, message,
 					"Updating MultiMC versions",
-					JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0])
+					JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[2])
 			future.complete(
-					when (result) {
-						JOptionPane.CLOSED_OPTION, 0 -> IUserInterface.UpdateConfirmationResult.CANCELLED
-						1 -> IUserInterface.UpdateConfirmationResult.CONTINUE
-						2 -> IUserInterface.UpdateConfirmationResult.UPDATE
-						else -> IUserInterface.UpdateConfirmationResult.CANCELLED
-					}
+				when (result) {
+					JOptionPane.CLOSED_OPTION, 0 -> IUserInterface.UpdateConfirmationResult.CANCELLED
+					1 -> IUserInterface.UpdateConfirmationResult.CONTINUE
+					2 -> IUserInterface.UpdateConfirmationResult.UPDATE
+					else -> IUserInterface.UpdateConfirmationResult.CANCELLED
+				}
 			)
 		}
 		return future.get()