mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-05-06 05:16:24 -05:00
Android: Convert GameFileCacheManager to Kotlin
This commit is contained in:
parent
5abf0e3556
commit
301ca5fcb8
|
|
@ -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();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user