Compare commits

..

3 Commits

Author SHA1 Message Date
Katherine
7420866dfc
MultiMC metadata: Support updating NeoForge version (#75) 2024-04-21 12:10:24 +01:00
comp500
1ebb28c3cc Make update progress reporting more descriptive and useful
Removed a deleteIfExists call - this should be handled by DownloadTask instead
2023-10-24 00:43:49 +01:00
comp500
c9543f74ee Fix invalidation issues when changing --side without updating pack 2023-10-23 23:36:17 +01:00
3 changed files with 90 additions and 62 deletions

View File

@ -21,6 +21,7 @@ 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) }
@ -28,10 +29,24 @@ 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
@ -76,6 +91,7 @@ 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) {
@ -143,6 +159,7 @@ 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 {
@ -181,10 +198,18 @@ 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 {
Files.deleteIfExists(it.cachedLocation!!.nioPath) completionStatus = if (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
@ -284,6 +309,8 @@ internal class DownloadTask private constructor(val metadata: IndexFile.File, va
} }
} }
} }
completionStatus = CompletionStatus.DOWNLOADED
} }
companion object { companion object {

View File

@ -48,6 +48,7 @@ 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"
@ -59,6 +60,7 @@ 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

@ -127,8 +127,11 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
ui.submitProgress(InstallProgress("Checking local files...")) ui.submitProgress(InstallProgress("Checking local files..."))
// Invalidation checking must be done here, as it must happen before pack/index hashes are checked // If the side changes, invalidate EVERYTHING (even when the index hasn't changed)
val invalidateAll = opts.side != manifest.cachedSide
val invalidatedUris: MutableList<PackwizFilePath> = ArrayList() val invalidatedUris: MutableList<PackwizFilePath> = ArrayList()
if (!invalidateAll) {
// Invalidation checking must be done here, as it must happen before pack/index hashes are checked
for ((fileUri, file) in manifest.cachedFiles) { for ((fileUri, file) in manifest.cachedFiles) {
// ignore onlyOtherSide files // ignore onlyOtherSide files
if (file.onlyOtherSide) { if (file.onlyOtherSide) {
@ -163,6 +166,7 @@ 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}")
@ -177,6 +181,7 @@ 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) {
@ -202,7 +207,8 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
} }
} }
private fun processIndex(indexUri: PackwizPath<*>, indexHash: Hash<*>, hashFormat: HashFormat<*>, manifest: ManifestFile, invalidatedFiles: List<PackwizFilePath>, clientHolder: ClientHolder) { private fun processIndex(indexUri: PackwizPath<*>, indexHash: Hash<*>, hashFormat: HashFormat<*>, manifest: ManifestFile, invalidatedFiles: List<PackwizFilePath>, invalidateAll: Boolean, 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 }) {
@ -216,6 +222,7 @@ 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 {
@ -240,31 +247,17 @@ 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()
} }
} }
@ -281,9 +274,6 @@ 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")
} }
@ -397,7 +387,16 @@ 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 {
"Downloaded ${task.name}" when (task.completionStatus) {
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))