diff options
Diffstat (limited to 'Source/Android/app/src/main/java/org')
8 files changed, 98 insertions, 48 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt index dbcfdd42e2..e7ea8e5519 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt @@ -61,13 +61,14 @@ class GameAdapter : RecyclerView.Adapter<GameViewHolder>(), val gameFile = mGameFiles[position] holder.apply { + val title = gameFile.getTitle(GameFileCacheManager.getTitleDatabase()) if (BooleanSetting.MAIN_SHOW_GAME_TITLES.boolean) { - binding.textGameTitle.text = gameFile.getTitle() + binding.textGameTitle.text = title binding.textGameTitle.visibility = View.VISIBLE binding.textGameTitleInner.visibility = View.GONE binding.textGameCaption.visibility = View.VISIBLE } else { - binding.textGameTitleInner.text = gameFile.getTitle() + binding.textGameTitleInner.text = title binding.textGameTitleInner.visibility = View.VISIBLE binding.textGameTitle.visibility = View.GONE binding.textGameCaption.visibility = View.GONE diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt index 4f43601e68..7cbb3000e2 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt @@ -48,7 +48,7 @@ class GameRowPresenter : Presenter() { holder.apply { imageScreenshot.setImageDrawable(null) - cardParent.titleText = gameFile.getTitle() + cardParent.titleText = gameFile.getTitle(GameFileCacheManager.getTitleDatabase()) holder.gameFile = gameFile // Set the background color of the card diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt index 0384da89c0..b97fd987e6 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt @@ -51,7 +51,7 @@ class GameDetailsDialog : DialogFragment() { textTimePlayed.visibility = View.GONE } - textGameTitle.text = gameFile.getTitle() + textGameTitle.text = gameFile.getTitle(GameFileCacheManager.getTitleDatabase()) textDescription.text = gameFile.getDescription() if (gameFile.getDescription().isEmpty()) { textDescription.visibility = View.GONE @@ -118,7 +118,7 @@ class GameDetailsDialog : DialogFragment() { textTimePlayed.visibility = View.GONE } - textGameTitle.text = gameFile.getTitle() + textGameTitle.text = gameFile.getTitle(GameFileCacheManager.getTitleDatabase()) textDescription.text = gameFile.getDescription() if (gameFile.getDescription().isEmpty()) { tvBinding.textDescription.visibility = View.GONE diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt index c3602facf4..97f456c7a6 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt @@ -112,6 +112,7 @@ import org.dolphinemu.dolphinemu.features.netplay.model.Player import org.dolphinemu.dolphinemu.features.netplay.model.SaveTransferProgress import org.dolphinemu.dolphinemu.features.netplay.model.TraversalState import org.dolphinemu.dolphinemu.model.GameFile +import org.dolphinemu.dolphinemu.services.GameFileCacheManager import org.dolphinemu.dolphinemu.ui.theme.DolphinScaffold import org.dolphinemu.dolphinemu.ui.theme.DolphinTheme import org.dolphinemu.dolphinemu.ui.theme.MenuSpacer @@ -855,6 +856,7 @@ private fun GameGridItem( gameFile: GameFile, onClick: () -> Unit, ) { + val titleDatabase = GameFileCacheManager.getTitleDatabase() Card( onClick = onClick, ) { @@ -864,7 +866,7 @@ private fun GameGridItem( .data(gameFile) .error(R.drawable.no_banner) .build(), - contentDescription = gameFile.getTitle(), + contentDescription = gameFile.getTitle(titleDatabase), contentScale = ContentScale.Crop, imageLoader = CoilUtils.imageLoader, modifier = Modifier @@ -872,7 +874,7 @@ private fun GameGridItem( .aspectRatio(0.7f) ) Text( - text = gameFile.getTitle(), + text = gameFile.getTitle(titleDatabase), style = MaterialTheme.typography.bodySmall, maxLines = 2, minLines = 2, diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.kt index 26da174285..4794f5c1b7 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.kt @@ -12,7 +12,7 @@ class GameFile private constructor(private val pointer: Long) { external fun getPlatform(): Int - external fun getTitle(): String + external fun getTitle(titleDatabase: TitleDatabase?): String external fun getDescription(): String diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TitleDatabase.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TitleDatabase.kt new file mode 100644 index 0000000000..e5089673b8 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TitleDatabase.kt @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.model + +import androidx.annotation.Keep + +@Keep +class TitleDatabase private constructor(private val pointer: Long) { + external fun finalize() + + external fun userTitleMapEquals(other: TitleDatabase?): Boolean + + companion object { + @JvmStatic + external suspend fun load(): TitleDatabase + } +} 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 index 2ba2c246b3..a5e54aa0de 100644 --- 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 @@ -2,32 +2,36 @@ package org.dolphinemu.dolphinemu.services -import android.os.Handler -import android.os.Looper import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asFlow +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext 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.model.TitleDatabase 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 +import java.util.concurrent.atomic.AtomicReference /** - * Loads game list data on a separate thread. + * Loads game list data in the background. */ object GameFileCacheManager { private var gameFileCache: GameFileCache? = null private val gameFiles = MutableLiveData(emptyArray<GameFile>()) + private var titleDatabase = AtomicReference<TitleDatabase>(null) 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) @@ -88,6 +92,8 @@ object GameFileCacheManager { } } + fun getTitleDatabase(): TitleDatabase? = titleDatabase.get() + /** * Returns true if in the process of loading the cache for the first time. */ @@ -120,7 +126,9 @@ object GameFileCacheManager { if (!loadInProgress.value!!) { loadInProgress.value = true - AfterDirectoryInitializationRunner().runWithoutLifecycle { executor.execute(::load) } + AfterDirectoryInitializationRunner().runWithoutLifecycle { + MainScope().launch { load() } + } } } @@ -136,7 +144,9 @@ object GameFileCacheManager { if (!rescanInProgress.value!!) { rescanInProgress.value = true - AfterDirectoryInitializationRunner().runWithoutLifecycle { executor.execute(::rescan) } + AfterDirectoryInitializationRunner().runWithoutLifecycle { + MainScope().launch { rescan() } + } } } @@ -144,9 +154,9 @@ object GameFileCacheManager { fun addOrGet(gamePath: String?): GameFile { val nonNullGamePath = gamePath!! - // Common case: The game is in the cache, so just grab it from there. (GameFileCache.addOrGet + // Common case: The game is in the cache, so we 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.) + // because rescan() may hold a lock on gameFileCache for extended periods of time.) val allGames = gameFiles.value!! for (game in allGames) { if (game.getPath() == nonNullGamePath) { @@ -165,28 +175,28 @@ object GameFileCacheManager { * 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() { + private suspend fun load() { if (!firstLoadDone) { - firstLoadDone = true - setUpAutomaticRescan() - gameFileCache!!.load() - if (gameFileCache!!.getSize() != 0) { - updateGameFileArray() + val titleDatabaseJob = MainScope().async(Dispatchers.IO) { + TitleDatabase.load() } - } - 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) - } + withContext(Dispatchers.IO) { + setUpAutomaticRescan() + + gameFileCache!!.load() + + titleDatabase.set(titleDatabaseJob.await()) - loadInProgress.postValue(false) + if (gameFileCache!!.getSize() != 0) { + updateGameFileArray() + } + } - if (runRescanAfterLoad) { - runRescanAfterLoad = false - rescan() + firstLoadDone = true } + + loadInProgress.value = false } /** @@ -195,19 +205,35 @@ object GameFileCacheManager { * 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() + private suspend fun rescan() { + while (!firstLoadDone) { + // Wait until loadInProgress becomes false + loadInProgress.asFlow().first { !it } + } + val titleDatabaseJob = MainScope().async(Dispatchers.IO) { + val newTitleDatabase = TitleDatabase.load() + val titleDatabaseChanged = + !newTitleDatabase.userTitleMapEquals(titleDatabase.get()) + if (titleDatabaseChanged) newTitleDatabase else null + } + + withContext(Dispatchers.IO) { + val gamePaths = GameFileCache.getAllGamePaths() val changed = gameFileCache!!.update(gamePaths) if (changed) { updateGameFileArray() } val additionalMetadataChanged = gameFileCache!!.updateAdditionalMetadata() - if (additionalMetadataChanged) { + + val newTitleDatabase = titleDatabaseJob.await() + val titleDatabaseChanged = newTitleDatabase != null + if (titleDatabaseChanged) { + titleDatabase.set(newTitleDatabase) + } + + if (additionalMetadataChanged || titleDatabaseChanged) { updateGameFileArray() } @@ -216,13 +242,14 @@ object GameFileCacheManager { } } - rescanInProgress.postValue(false) + rescanInProgress.value = false } private fun updateGameFileArray() { val gameFilesTemp = gameFileCache!!.getAllGames() + val titleDatabase = titleDatabase.get() Arrays.sort(gameFilesTemp) { lhs, rhs -> - lhs.getTitle().compareTo(rhs.getTitle(), ignoreCase = true) + lhs.getTitle(titleDatabase).compareTo(rhs.getTitle(titleDatabase), ignoreCase = true) } gameFiles.postValue(gameFilesTemp) } @@ -239,10 +266,10 @@ object GameFileCacheManager { 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 + MainScope().launch { + val newRecursiveScanEnabled = BooleanSetting.MAIN_RECURSIVE_ISO_PATHS.boolean + if (recursiveScanEnabled != newRecursiveScanEnabled) { + recursiveScanEnabled = newRecursiveScanEnabled startRescan() } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.kt index 2d9ad354fb..fd13ff3175 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.kt @@ -138,7 +138,10 @@ class SyncProgramsJobService : JobService() { TvContractCompat.PreviewPrograms.COLUMN_TYPE, TvContractCompat.PreviewProgramColumns.TYPE_GAME ) - values.put(TvContract.Programs.COLUMN_TITLE, game.getTitle()) + values.put( + TvContract.Programs.COLUMN_TITLE, + game.getTitle(GameFileCacheManager.getTitleDatabase()) + ) values.put(TvContract.Programs.COLUMN_SHORT_DESCRIPTION, game.getDescription()) values.put(TvContract.Programs.COLUMN_POSTER_ART_URI, banner.toString()) values.put( |
