summaryrefslogtreecommitdiff
path: root/Source/Android
diff options
context:
space:
mode:
authorSimonx22 <simon@oatmealdome.me>2026-04-06 20:05:13 -0400
committerSimonx22 <simon@oatmealdome.me>2026-04-16 16:46:24 -0400
commit301ca5fcb8de212cf3da0a85080e245eec96e569 (patch)
tree03636885a060c1d27bc3ac5f35ed9e293388c03b /Source/Android
parent5abf0e35560b3d2e1be0ad0db825e343c3e2c8fe (diff)
Android: Convert GameFileCacheManager to Kotlin
Diffstat (limited to 'Source/Android')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.java286
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.kt251
2 files changed, 251 insertions, 286 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.java
deleted file mode 100644
index 50d75d8507..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.java
+++ /dev/null
@@ -1,286 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.services;
-
-import android.os.Handler;
-import android.os.Looper;
-
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MutableLiveData;
-
-import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.ConfigChangedCallback;
-import org.dolphinemu.dolphinemu.model.GameFile;
-import org.dolphinemu.dolphinemu.model.GameFileCache;
-import org.dolphinemu.dolphinemu.ui.platform.Platform;
-import org.dolphinemu.dolphinemu.ui.platform.PlatformTab;
-import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-/**
- * Loads game list data on a separate thread.
- */
-public final class GameFileCacheManager
-{
- private static GameFileCache sGameFileCache = null;
- private static final MutableLiveData<GameFile[]> sGameFiles =
- new MutableLiveData<>(new GameFile[]{});
- private static boolean sFirstLoadDone = false;
- private static boolean sRunRescanAfterLoad = false;
- private static boolean sRecursiveScanEnabled;
-
- private static final ExecutorService sExecutor = Executors.newFixedThreadPool(1);
- private static final MutableLiveData<Boolean> sLoadInProgress = new MutableLiveData<>(false);
- private static final MutableLiveData<Boolean> sRescanInProgress = new MutableLiveData<>(false);
-
- private GameFileCacheManager()
- {
- }
-
- public static LiveData<GameFile[]> getGameFiles()
- {
- return sGameFiles;
- }
-
- public static List<GameFile> getGameFilesForPlatformTab(PlatformTab platformTab)
- {
- GameFile[] allGames = sGameFiles.getValue();
- ArrayList<GameFile> platformTabGames = new ArrayList<>();
- for (GameFile game : allGames)
- {
- if (Platform.fromInt(game.getPlatform()).toPlatformTab() == platformTab)
- {
- platformTabGames.add(game);
- }
- }
- return platformTabGames;
- }
-
- public static GameFile getGameFileByGameId(String gameId)
- {
- GameFile[] allGames = sGameFiles.getValue();
- for (GameFile game : allGames)
- {
- if (game.getGameId().equals(gameId))
- {
- return game;
- }
- }
- return null;
- }
-
- public static GameFile findSecondDisc(GameFile game)
- {
- GameFile matchWithoutRevision = null;
-
- GameFile[] allGames = sGameFiles.getValue();
- for (GameFile otherGame : allGames)
- {
- if (game.getGameId().equals(otherGame.getGameId()) &&
- game.getDiscNumber() != otherGame.getDiscNumber())
- {
- if (game.getRevision() == otherGame.getRevision())
- return otherGame;
- else
- matchWithoutRevision = otherGame;
- }
- }
-
- return matchWithoutRevision;
- }
-
- public static String[] findSecondDiscAndGetPaths(GameFile gameFile)
- {
- GameFile secondFile = findSecondDisc(gameFile);
- if (secondFile == null)
- return new String[]{gameFile.getPath()};
- else
- return new String[]{gameFile.getPath(), secondFile.getPath()};
- }
-
- /**
- * Returns true if in the process of loading the cache for the first time.
- */
- public static LiveData<Boolean> isLoading()
- {
- return sLoadInProgress;
- }
-
- /**
- * Returns true if in the process of rescanning.
- */
- public static LiveData<Boolean> isRescanning()
- {
- return sRescanInProgress;
- }
-
- public static boolean isLoadingOrRescanning()
- {
- return sLoadInProgress.getValue() || sRescanInProgress.getValue();
- }
-
- /**
- * Asynchronously loads the game file cache from disk, without checking
- * if the games are still present in the user's configured folders.
- * If this has already been called, calling it again has no effect.
- */
- public static void startLoad()
- {
- createGameFileCacheIfNeeded();
-
- if (!sLoadInProgress.getValue())
- {
- sLoadInProgress.setValue(true);
- new AfterDirectoryInitializationRunner().runWithoutLifecycle(
- () -> sExecutor.execute(GameFileCacheManager::load));
- }
- }
-
- /**
- * Asynchronously scans for games in the user's configured folders,
- * updating the game file cache with the results.
- * If loading the game file cache hasn't started or hasn't finished,
- * the execution of this will be postponed until it finishes.
- */
- public static void startRescan()
- {
- createGameFileCacheIfNeeded();
-
- if (!sRescanInProgress.getValue())
- {
- sRescanInProgress.setValue(true);
- new AfterDirectoryInitializationRunner().runWithoutLifecycle(
- () -> sExecutor.execute(GameFileCacheManager::rescan));
- }
- }
-
- public static GameFile addOrGet(String gamePath)
- {
- // Common case: The game is in the cache, so just grab it from there. (GameFileCache.addOrGet
- // actually already checks for this case, but we want to avoid calling it if possible
- // because the executor thread may hold a lock on sGameFileCache for extended periods of time.)
- GameFile[] allGames = sGameFiles.getValue();
- for (GameFile game : allGames)
- {
- if (game.getPath().equals(gamePath))
- {
- return game;
- }
- }
-
- // Unusual case: The game wasn't found in the cache.
- // Scan the game and add it to the cache so that we can return it.
- createGameFileCacheIfNeeded();
- return sGameFileCache.addOrGet(gamePath);
- }
-
- /**
- * Loads the game file cache from disk, without checking if the
- * games are still present in the user's configured folders.
- * If this has already been called, calling it again has no effect.
- */
- private static void load()
- {
- if (!sFirstLoadDone)
- {
- sFirstLoadDone = true;
- setUpAutomaticRescan();
- sGameFileCache.load();
- if (sGameFileCache.getSize() != 0)
- {
- updateGameFileArray();
- }
- }
-
- if (sRunRescanAfterLoad)
- {
- // Without this, there will be a short blip where the loading indicator in the GUI disappears
- // because neither sLoadInProgress nor sRescanInProgress is true
- sRescanInProgress.postValue(true);
- }
-
- sLoadInProgress.postValue(false);
-
- if (sRunRescanAfterLoad)
- {
- sRunRescanAfterLoad = false;
- rescan();
- }
- }
-
- /**
- * Scans for games in the user's configured folders,
- * updating the game file cache with the results.
- * If load hasn't been called before this, the execution of this
- * will be postponed until after load runs.
- */
- private static void rescan()
- {
- if (!sFirstLoadDone)
- {
- sRunRescanAfterLoad = true;
- }
- else
- {
- String[] gamePaths = GameFileCache.getAllGamePaths();
-
- boolean changed = sGameFileCache.update(gamePaths);
- if (changed)
- {
- updateGameFileArray();
- }
-
- boolean additionalMetadataChanged = sGameFileCache.updateAdditionalMetadata();
- if (additionalMetadataChanged)
- {
- updateGameFileArray();
- }
-
- if (changed || additionalMetadataChanged)
- {
- sGameFileCache.save();
- }
- }
-
- sRescanInProgress.postValue(false);
- }
-
- private static void updateGameFileArray()
- {
- GameFile[] gameFilesTemp = sGameFileCache.getAllGames();
- Arrays.sort(gameFilesTemp, (lhs, rhs) -> lhs.getTitle().compareToIgnoreCase(rhs.getTitle()));
- sGameFiles.postValue(gameFilesTemp);
- }
-
- private static void createGameFileCacheIfNeeded()
- {
- // Creating the GameFileCache in the static initializer may be unsafe, because GameFileCache
- // relies on native code, and the native library isn't loaded right when the app starts.
- // We create it here instead.
-
- if (sGameFileCache == null)
- {
- sGameFileCache = new GameFileCache();
- }
- }
-
- private static void setUpAutomaticRescan()
- {
- sRecursiveScanEnabled = BooleanSetting.MAIN_RECURSIVE_ISO_PATHS.getBoolean();
- new ConfigChangedCallback(() ->
- new Handler(Looper.getMainLooper()).post(() ->
- {
- boolean recursiveScanEnabled = BooleanSetting.MAIN_RECURSIVE_ISO_PATHS.getBoolean();
- if (sRecursiveScanEnabled != recursiveScanEnabled)
- {
- sRecursiveScanEnabled = recursiveScanEnabled;
- startRescan();
- }
- }));
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.kt
new file mode 100644
index 0000000000..2ba2c246b3
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.kt
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.services
+
+import android.os.Handler
+import android.os.Looper
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
+import org.dolphinemu.dolphinemu.features.settings.model.ConfigChangedCallback
+import org.dolphinemu.dolphinemu.model.GameFile
+import org.dolphinemu.dolphinemu.model.GameFileCache
+import org.dolphinemu.dolphinemu.ui.platform.Platform
+import org.dolphinemu.dolphinemu.ui.platform.PlatformTab
+import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner
+import java.util.Arrays
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+
+/**
+ * Loads game list data on a separate thread.
+ */
+object GameFileCacheManager {
+ private var gameFileCache: GameFileCache? = null
+ private val gameFiles = MutableLiveData(emptyArray<GameFile>())
+ private var firstLoadDone = false
+ private var runRescanAfterLoad = false
+ private var recursiveScanEnabled = false
+
+ private val executor: ExecutorService = Executors.newFixedThreadPool(1)
+ private val loadInProgress = MutableLiveData(false)
+ private val rescanInProgress = MutableLiveData(false)
+
+ @JvmStatic
+ fun getGameFiles(): LiveData<Array<GameFile>> {
+ return gameFiles
+ }
+
+ @JvmStatic
+ fun getGameFilesForPlatformTab(platformTab: PlatformTab): List<GameFile> {
+ val allGames = gameFiles.value!!
+ val platformTabGames = ArrayList<GameFile>()
+ for (game in allGames) {
+ if (Platform.fromInt(game.getPlatform()).toPlatformTab() == platformTab) {
+ platformTabGames.add(game)
+ }
+ }
+ return platformTabGames
+ }
+
+ @JvmStatic
+ fun getGameFileByGameId(gameId: String): GameFile? {
+ val allGames = gameFiles.value!!
+ for (game in allGames) {
+ if (game.getGameId() == gameId) {
+ return game
+ }
+ }
+ return null
+ }
+
+ @JvmStatic
+ fun findSecondDisc(game: GameFile): GameFile? {
+ var matchWithoutRevision: GameFile? = null
+
+ val allGames = gameFiles.value!!
+ for (otherGame in allGames) {
+ if (game.getGameId() == otherGame.getGameId() && game.getDiscNumber() != otherGame.getDiscNumber()) {
+ if (game.getRevision() == otherGame.getRevision()) {
+ return otherGame
+ } else {
+ matchWithoutRevision = otherGame
+ }
+ }
+ }
+
+ return matchWithoutRevision
+ }
+
+ @JvmStatic
+ fun findSecondDiscAndGetPaths(gameFile: GameFile?): Array<String> {
+ val nonNullGameFile = gameFile!!
+ val secondFile = findSecondDisc(nonNullGameFile)
+ return if (secondFile == null) {
+ arrayOf(nonNullGameFile.getPath())
+ } else {
+ arrayOf(nonNullGameFile.getPath(), secondFile.getPath())
+ }
+ }
+
+ /**
+ * Returns true if in the process of loading the cache for the first time.
+ */
+ @JvmStatic
+ fun isLoading(): LiveData<Boolean> {
+ return loadInProgress
+ }
+
+ /**
+ * Returns true if in the process of rescanning.
+ */
+ @JvmStatic
+ fun isRescanning(): LiveData<Boolean> {
+ return rescanInProgress
+ }
+
+ @JvmStatic
+ fun isLoadingOrRescanning(): Boolean {
+ return loadInProgress.value!! || rescanInProgress.value!!
+ }
+
+ /**
+ * Asynchronously loads the game file cache from disk, without checking
+ * if the games are still present in the user's configured folders.
+ * If this has already been called, calling it again has no effect.
+ */
+ @JvmStatic
+ fun startLoad() {
+ createGameFileCacheIfNeeded()
+
+ if (!loadInProgress.value!!) {
+ loadInProgress.value = true
+ AfterDirectoryInitializationRunner().runWithoutLifecycle { executor.execute(::load) }
+ }
+ }
+
+ /**
+ * Asynchronously scans for games in the user's configured folders,
+ * updating the game file cache with the results.
+ * If loading the game file cache hasn't started or hasn't finished,
+ * the execution of this will be postponed until it finishes.
+ */
+ @JvmStatic
+ fun startRescan() {
+ createGameFileCacheIfNeeded()
+
+ if (!rescanInProgress.value!!) {
+ rescanInProgress.value = true
+ AfterDirectoryInitializationRunner().runWithoutLifecycle { executor.execute(::rescan) }
+ }
+ }
+
+ @JvmStatic
+ fun addOrGet(gamePath: String?): GameFile {
+ val nonNullGamePath = gamePath!!
+
+ // Common case: The game is in the cache, so just grab it from there. (GameFileCache.addOrGet
+ // actually already checks for this case, but we want to avoid calling it if possible
+ // because the executor thread may hold a lock on gameFileCache for extended periods of time.)
+ val allGames = gameFiles.value!!
+ for (game in allGames) {
+ if (game.getPath() == nonNullGamePath) {
+ return game
+ }
+ }
+
+ // Unusual case: The game wasn't found in the cache.
+ // Scan the game and add it to the cache so that we can return it.
+ createGameFileCacheIfNeeded()
+ return gameFileCache!!.addOrGet(nonNullGamePath)!!
+ }
+
+ /**
+ * Loads the game file cache from disk, without checking if the
+ * games are still present in the user's configured folders.
+ * If this has already been called, calling it again has no effect.
+ */
+ private fun load() {
+ if (!firstLoadDone) {
+ firstLoadDone = true
+ setUpAutomaticRescan()
+ gameFileCache!!.load()
+ if (gameFileCache!!.getSize() != 0) {
+ updateGameFileArray()
+ }
+ }
+
+ if (runRescanAfterLoad) {
+ // Without this, there will be a short blip where the loading indicator in the GUI disappears
+ // because neither loadInProgress nor rescanInProgress is true
+ rescanInProgress.postValue(true)
+ }
+
+ loadInProgress.postValue(false)
+
+ if (runRescanAfterLoad) {
+ runRescanAfterLoad = false
+ rescan()
+ }
+ }
+
+ /**
+ * Scans for games in the user's configured folders,
+ * updating the game file cache with the results.
+ * If load hasn't been called before this, the execution of this
+ * will be postponed until after load runs.
+ */
+ private fun rescan() {
+ if (!firstLoadDone) {
+ runRescanAfterLoad = true
+ } else {
+ val gamePaths = GameFileCache.getAllGamePaths()
+
+ val changed = gameFileCache!!.update(gamePaths)
+ if (changed) {
+ updateGameFileArray()
+ }
+
+ val additionalMetadataChanged = gameFileCache!!.updateAdditionalMetadata()
+ if (additionalMetadataChanged) {
+ updateGameFileArray()
+ }
+
+ if (changed || additionalMetadataChanged) {
+ gameFileCache!!.save()
+ }
+ }
+
+ rescanInProgress.postValue(false)
+ }
+
+ private fun updateGameFileArray() {
+ val gameFilesTemp = gameFileCache!!.getAllGames()
+ Arrays.sort(gameFilesTemp) { lhs, rhs ->
+ lhs.getTitle().compareTo(rhs.getTitle(), ignoreCase = true)
+ }
+ gameFiles.postValue(gameFilesTemp)
+ }
+
+ private fun createGameFileCacheIfNeeded() {
+ // Creating the GameFileCache in the static initializer may be unsafe, because GameFileCache
+ // relies on native code, and the native library isn't loaded right when the app starts.
+ // We create it here instead.
+ if (gameFileCache == null) {
+ gameFileCache = GameFileCache()
+ }
+ }
+
+ private fun setUpAutomaticRescan() {
+ recursiveScanEnabled = BooleanSetting.MAIN_RECURSIVE_ISO_PATHS.boolean
+ ConfigChangedCallback {
+ Handler(Looper.getMainLooper()).post {
+ val recursiveScanEnabled = BooleanSetting.MAIN_RECURSIVE_ISO_PATHS.boolean
+ if (this.recursiveScanEnabled != recursiveScanEnabled) {
+ this.recursiveScanEnabled = recursiveScanEnabled
+ startRescan()
+ }
+ }
+ }
+ }
+}