From 4e94f4731b878383d192bfbf759d3db37dc52d6a Mon Sep 17 00:00:00 2001 From: Jochem Kuipers Date: Mon, 27 Oct 2025 12:37:16 +0100 Subject: [PATCH] feat: Refactor autoloadContent to streamline directory scanning for NSPs --- .../android/viewmodels/HomeViewModel.kt | 140 ++++++++---------- 1 file changed, 65 insertions(+), 75 deletions(-) diff --git a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/viewmodels/HomeViewModel.kt b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/viewmodels/HomeViewModel.kt index 41ce25003..ce4232ec5 100644 --- a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/viewmodels/HomeViewModel.kt +++ b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/viewmodels/HomeViewModel.kt @@ -15,8 +15,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import org.kenjinx.android.KenjinxNative -import org.kenjinx.android.KeyboardMode import org.kenjinx.android.MainActivity +import java.io.File import java.util.Locale import kotlin.concurrent.thread @@ -125,7 +125,7 @@ class HomeViewModel( // Helper function to compare update versions from filenames // Returns true if newPath represents a newer version than currentPath - private fun shouldSelectNewerUpdate(currentPath: String, newPath: String, allPaths: List): Boolean { + private fun shouldSelectNewerUpdate(currentPath: String, newPath: String): Boolean { // Extract version numbers from filenames using regex pattern [vXXXXXX] val versionPattern = Regex("\\[v(\\d+)]") @@ -135,21 +135,13 @@ class HomeViewModel( return newVersion > currentVersion } - // Scans configured directories for NSPs containing DLCs/Updates and associates them to known titles. + // Scans configured directory for NSPs containing DLCs/Updates and associates them to known titles. private fun autoloadContent() { val prefs = sharedPref ?: return - // Prefer a single, explicitly selected updates/DLC folder (updatesFolder). Fallback to legacy autoloadDirs. val updatesFolder = prefs.getString("updatesFolder", "") ?: "" - val dirs: List = if (updatesFolder.isNotBlank()) { - listOf(updatesFolder.trim()) - } else { - // Legacy: semicolon-separated list under key autoloadDirs - val raw = prefs.getString("autoloadDirs", "") ?: "" - if (raw.isBlank()) emptyList() else raw.split(';').map { it.trim() }.filter { it.isNotEmpty() } - } - if (dirs.isEmpty()) return + if (updatesFolder.isEmpty()) return // Build a map of titleId -> helpers val gamesByTitle = loadedCache.mapNotNull { g -> @@ -160,85 +152,83 @@ class HomeViewModel( var updatesAdded = 0 var dlcAdded = 0 - for (dir in dirs) { - val base = java.io.File(dir) - if (!base.exists() || !base.isDirectory) continue + val base = File(updatesFolder) + if (!base.exists() || !base.isDirectory) return - base.walkTopDown().forEach fileLoop@{ f -> - if (!f.isFile) return@fileLoop - val name = f.name.lowercase(Locale.getDefault()) - if (!name.endsWith(".nsp")) return@fileLoop + base.walkTopDown().forEach fileLoop@{ f -> + if (!f.isFile) return@fileLoop + val name = f.name.lowercase(Locale.getDefault()) + if (!name.endsWith(".nsp")) return@fileLoop - // Extract title ID from filename - val tidPattern = Regex("\\[([0-9a-fA-F]{16})]") - val tidMatch = tidPattern.find(name) ?: return@fileLoop - val fileTid = tidMatch.groupValues[1].lowercase(Locale.getDefault()) + // Extract title ID from filename + val tidPattern = Regex("\\[([0-9a-fA-F]{16})]") + val tidMatch = tidPattern.find(name) ?: return@fileLoop + val fileTid = tidMatch.groupValues[1].lowercase(Locale.getDefault()) - // Try to find DLC content for all games - var isDlc = false - try { - for ((_, tidOrig) in gamesByTitle) { - val contents = KenjinxNative.deviceGetDlcContentList(f.absolutePath, tidOrig.toLong(16)) + // Try to find DLC content for all games + var isDlc = false + try { + for ((_, tidOrig) in gamesByTitle) { + val contents = KenjinxNative.deviceGetDlcContentList(f.absolutePath, tidOrig.toLong(16)) - if (contents.isNotEmpty()) { - isDlc = true - val containerPath = f.absolutePath - val vm = DlcViewModel(tidOrig) - val already = vm.data?.any { it.path == containerPath } == true + if (contents.isNotEmpty()) { + isDlc = true + val containerPath = f.absolutePath + val vm = DlcViewModel(tidOrig) + val already = vm.data?.any { it.path == containerPath } == true - if (!already) { - val container = DlcContainerList(containerPath) - for (content in contents) { - container.dlc_nca_list.add( - DlcContainer( - true, - KenjinxNative.deviceGetDlcTitleId(containerPath, content).toLong(16), - content - ) + if (!already) { + val container = DlcContainerList(containerPath) + for (content in contents) { + container.dlc_nca_list.add( + DlcContainer( + true, + KenjinxNative.deviceGetDlcTitleId(containerPath, content).toLong(16), + content ) - } - vm.data?.add(container) - vm.saveChanges() - dlcAdded++ + ) } - break + vm.data?.add(container) + vm.saveChanges() + dlcAdded++ } + break } - } catch (_: Throwable) { } - - if (isDlc) return@fileLoop - - // Treat as Title Update - convert update ID to base ID - // Update title IDs end in 800, base game IDs end in 000 - val baseTid = if (fileTid.endsWith("800")) { - fileTid.substring(0, fileTid.length - 3) + "000" - } else { - fileTid } + } catch (_: Throwable) { } - val originalTid = gamesByTitle[baseTid] - if (originalTid != null) { - val vm = TitleUpdateViewModel(originalTid) - val path = f.absolutePath - val exists = (vm.data?.paths?.contains(path) == true) + if (isDlc) return@fileLoop - if (!exists) { - // Add the new update path - vm.data?.paths?.add(path) + // Treat as Title Update - convert update ID to base ID + // Update title IDs end in 800, base game IDs end in 000 + val baseTid = if (fileTid.endsWith("800")) { + fileTid.substring(0, fileTid.length - 3) + "000" + } else { + fileTid + } - // Auto-select this update if it's newer than the currently selected one - // or if no update is currently selected - val currentSelected = vm.data?.selected ?: "" - val shouldSelect = currentSelected.isEmpty() || - shouldSelectNewerUpdate(currentSelected, path, vm.data?.paths ?: mutableListOf()) + val originalTid = gamesByTitle[baseTid] + if (originalTid != null) { + val vm = TitleUpdateViewModel(originalTid) + val path = f.absolutePath + val exists = (vm.data?.paths?.contains(path) == true) - if (shouldSelect) { - vm.data?.selected = path - } + if (!exists) { + // Add the new update path + vm.data?.paths?.add(path) - vm.saveChanges() - updatesAdded++ + // Auto-select this update if it's newer than the currently selected one + // or if no update is currently selected + val currentSelected = vm.data?.selected ?: "" + val shouldSelect = currentSelected.isEmpty() || + shouldSelectNewerUpdate(currentSelected, path) + + if (shouldSelect) { + vm.data?.selected = path } + + vm.saveChanges() + updatesAdded++ } } }