From a56ee1a62e0781df7eefa4a0408a3d84d5a2b807 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 14:18:27 -0400 Subject: [PATCH 1/4] Android: Convert GameFile to Kotlin --- .../dolphinemu/adapters/GameAdapter.kt | 8 +- .../dolphinemu/adapters/GameRowPresenter.kt | 6 +- .../dolphinemu/dialogs/GameDetailsDialog.kt | 50 ++++++------ .../dolphinemu/fragments/ConvertFragment.kt | 22 ++++-- .../dolphinemu/dolphinemu/model/GameFile.java | 78 ------------------- .../dolphinemu/dolphinemu/model/GameFile.kt | 69 ++++++++++++++++ .../dolphinemu/utils/CoverHelper.kt | 6 +- Source/Android/jni/AndroidCommon/IDCache.cpp | 2 +- 8 files changed, 120 insertions(+), 121 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.kt 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 103ba4a318..520c96d812 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,12 +61,12 @@ class GameAdapter(private val mActivity: FragmentActivity) : RecyclerView.Adapte holder.apply { if (BooleanSetting.MAIN_SHOW_GAME_TITLES.boolean) { - binding.textGameTitle.text = gameFile.title + binding.textGameTitle.text = gameFile.getTitle() binding.textGameTitle.visibility = View.VISIBLE binding.textGameTitleInner.visibility = View.GONE binding.textGameCaption.visibility = View.VISIBLE } else { - binding.textGameTitleInner.text = gameFile.title + binding.textGameTitleInner.text = gameFile.getTitle() binding.textGameTitleInner.visibility = View.VISIBLE binding.textGameTitle.visibility = View.GONE binding.textGameCaption.visibility = View.GONE @@ -94,9 +94,9 @@ class GameAdapter(private val mActivity: FragmentActivity) : RecyclerView.Adapte holder.apply { if (GameFileCacheManager.findSecondDisc(gameFile) != null) { binding.textGameCaption.text = - context.getString(R.string.disc_number, gameFile.discNumber + 1) + context.getString(R.string.disc_number, gameFile.getDiscNumber() + 1) } else { - binding.textGameCaption.text = gameFile.company + binding.textGameCaption.text = gameFile.getCompany() } holder.gameFile = gameFile binding.root.onFocusChangeListener = 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 c23b4b3e14..b355dc8f6d 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(private val mActivity: FragmentActivity) : Presenter() { holder.apply { imageScreenshot.setImageDrawable(null) - cardParent.titleText = gameFile.title + cardParent.titleText = gameFile.getTitle() holder.gameFile = gameFile // Set the background color of the card @@ -64,9 +64,9 @@ class GameRowPresenter(private val mActivity: FragmentActivity) : Presenter() { if (GameFileCacheManager.findSecondDisc(gameFile) != null) { holder.cardParent.contentText = - context.getString(R.string.disc_number, gameFile.discNumber + 1) + context.getString(R.string.disc_number, gameFile.getDiscNumber() + 1) } else { - holder.cardParent.contentText = gameFile.company + holder.cardParent.contentText = gameFile.getCompany() } } 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 83d69ebbb4..53b1467bbc 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 @@ -25,8 +25,8 @@ class GameDetailsDialog : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val gameFile = GameFileCacheManager.addOrGet(requireArguments().getString(ARG_GAME_PATH)) - val country = resources.getStringArray(R.array.countryNames)[gameFile.country] - val fileSize = NativeLibrary.FormatSize(gameFile.fileSize, 2) + val country = resources.getStringArray(R.array.countryNames)[gameFile.getCountry()] + val fileSize = NativeLibrary.FormatSize(gameFile.getFileSize(), 2) // TODO: Remove dialog_game_details_tv if we switch to an AppCompatActivity for leanback val binding: DialogGameDetailsBinding @@ -35,16 +35,16 @@ class GameDetailsDialog : DialogFragment() { if (requireActivity() is AppCompatActivity) { binding = DialogGameDetailsBinding.inflate(layoutInflater) binding.apply { - textGameTitle.text = gameFile.title - textDescription.text = gameFile.description - if (gameFile.description.isEmpty()) { + textGameTitle.text = gameFile.getTitle() + textDescription.text = gameFile.getDescription() + if (gameFile.getDescription().isEmpty()) { textDescription.visibility = View.GONE } textCountry.text = country - textCompany.text = gameFile.company - textGameId.text = gameFile.gameId - textRevision.text = gameFile.revision.toString() + textCompany.text = gameFile.getCompany() + textGameId.text = gameFile.getGameId() + textRevision.text = gameFile.getRevision().toString() if (!gameFile.shouldShowFileFormatDetails()) { labelFileFormat.setText(R.string.game_details_file_size) @@ -55,19 +55,19 @@ class GameDetailsDialog : DialogFragment() { labelBlockSize.visibility = View.GONE textBlockSize.visibility = View.GONE } else { - val blockSize = gameFile.blockSize - val compression = gameFile.compressionMethod + val blockSize = gameFile.getBlockSize() + val compression = gameFile.getCompressionMethod() textFileFormat.text = resources.getString( R.string.game_details_size_and_format, - gameFile.fileFormatName, + gameFile.getFileFormatName(), fileSize ) if (compression.isEmpty()) { textCompression.setText(R.string.game_details_no_compression) } else { - textCompression.text = gameFile.compressionMethod + textCompression.text = gameFile.getCompressionMethod() } if (blockSize > 0) { @@ -87,16 +87,16 @@ class GameDetailsDialog : DialogFragment() { } else { tvBinding = DialogGameDetailsTvBinding.inflate(layoutInflater) tvBinding.apply { - textGameTitle.text = gameFile.title - textDescription.text = gameFile.description - if (gameFile.description.isEmpty()) { + textGameTitle.text = gameFile.getTitle() + textDescription.text = gameFile.getDescription() + if (gameFile.getDescription().isEmpty()) { tvBinding.textDescription.visibility = View.GONE } textCountry.text = country - textCompany.text = gameFile.company - textGameId.text = gameFile.gameId - textRevision.text = gameFile.revision.toString() + textCompany.text = gameFile.getCompany() + textGameId.text = gameFile.getGameId() + textRevision.text = gameFile.getRevision().toString() if (!gameFile.shouldShowFileFormatDetails()) { labelFileFormat.setText(R.string.game_details_file_size) @@ -107,19 +107,19 @@ class GameDetailsDialog : DialogFragment() { labelBlockSize.visibility = View.GONE textBlockSize.visibility = View.GONE } else { - val blockSize = gameFile.blockSize - val compression = gameFile.compressionMethod + val blockSize = gameFile.getBlockSize() + val compression = gameFile.getCompressionMethod() textFileFormat.text = resources.getString( R.string.game_details_size_and_format, - gameFile.fileFormatName, + gameFile.getFileFormatName(), fileSize ) if (compression.isEmpty()) { textCompression.setText(R.string.game_details_no_compression) } else { - textCompression.text = gameFile.compressionMethod + textCompression.text = gameFile.getCompressionMethod() } if (blockSize > 0) { @@ -141,9 +141,9 @@ class GameDetailsDialog : DialogFragment() { } private suspend fun loadGameBanner(imageView: ImageView, gameFile: GameFile) { - val vector = gameFile.banner - val width = gameFile.bannerWidth - val height = gameFile.bannerHeight + val vector = gameFile.getBanner() + val width = gameFile.getBannerWidth() + val height = gameFile.getBannerHeight() imageView.scaleType = ImageView.ScaleType.FIT_CENTER val request = ImageRequest.Builder(imageView.context) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.kt index 01a5d97743..0f5760a210 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.kt @@ -210,7 +210,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { R.array.convertFormatValues, format ) - if (gameFile.blobType == BLOB_TYPE_ISO) { + if (gameFile.getBlobType() == BLOB_TYPE_ISO) { setDropdownSelection( binding.dropdownFormat, format, @@ -240,6 +240,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { binding.dropdownBlockSize.adapter.getItem(0).toString(), false ) } + BLOB_TYPE_WIA -> { populateDropdown( binding.blockSize, @@ -253,6 +254,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { binding.dropdownBlockSize.adapter.getItem(0).toString(), false ) } + BLOB_TYPE_RVZ -> { populateDropdown( binding.blockSize, @@ -266,6 +268,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { binding.dropdownBlockSize.adapter.getItem(2).toString(), false ) } + else -> clearDropdown(binding.blockSize, binding.dropdownBlockSize, blockSize) } } @@ -285,6 +288,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { binding.dropdownCompression.adapter.getItem(0).toString(), false ) } + BLOB_TYPE_WIA -> { populateDropdown( binding.compression, @@ -298,6 +302,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { binding.dropdownCompression.adapter.getItem(0).toString(), false ) } + BLOB_TYPE_RVZ -> { populateDropdown( binding.compression, @@ -311,6 +316,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { binding.dropdownCompression.adapter.getItem(4).toString(), false ) } + else -> clearDropdown( binding.compression, binding.dropdownCompression, @@ -336,6 +342,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { ).toString(), false ) } + COMPRESSION_ZSTD -> { // TODO: Query DiscIO for the supported compression levels, like we do in DolphinQt? populateDropdown( @@ -352,6 +359,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { ).toString(), false ) } + else -> clearDropdown( binding.compressionLevel, binding.dropdownCompressionLevel, @@ -362,7 +370,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { private fun populateRemoveJunkData() { val scrubbingAllowed = format.getValue(requireContext()) != BLOB_TYPE_RVZ && - !gameFile.isDatelDisc + !gameFile.isDatelDisc() binding.switchRemoveJunkData.isEnabled = scrubbingAllowed if (!scrubbingAllowed) binding.switchRemoveJunkData.isChecked = false @@ -374,11 +382,11 @@ class ConvertFragment : Fragment(), View.OnClickListener { var action = Runnable { showSavePrompt() } - if (gameFile.isNKit) { + if (gameFile.isNKit()) { action = addAreYouSureDialog(action, R.string.convert_warning_nkit) } - if (!scrub && format == BLOB_TYPE_GCZ && !gameFile.isDatelDisc && gameFile.platform == Platform.WII.toInt()) { + if (!scrub && format == BLOB_TYPE_GCZ && !gameFile.isDatelDisc() && gameFile.getPlatform() == Platform.WII.toInt()) { action = addAreYouSureDialog(action, R.string.convert_warning_gcz) } @@ -401,7 +409,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { } private fun showSavePrompt() { - val originalPath = gameFile.path + val originalPath = gameFile.getPath() val filename = StringBuilder(File(originalPath).name) val dotIndex = filename.lastIndexOf(".") @@ -449,9 +457,9 @@ class ConvertFragment : Fragment(), View.OnClickListener { thread = Thread { val success = NativeLibrary.ConvertDiscImage( - gameFile.path, + gameFile.getPath(), outPath, - gameFile.platform, + gameFile.getPlatform(), format.getValue(context), blockSize.getValueOr(context, 0), compression.getValueOr(context, 0), diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java deleted file mode 100644 index 26f4c169ce..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.model; - -import androidx.annotation.Keep; - -public class GameFile -{ - public static int REGION_NTSC_J = 0; - public static int REGION_NTSC_U = 1; - public static int REGION_PAL = 2; - public static int REGION_NTSC_K = 4; - - @Keep - private long mPointer; - - @Keep - private GameFile(long pointer) - { - mPointer = pointer; - } - - public native static GameFile parse(String path); - - @Override - public native void finalize(); - - public native int getPlatform(); - - public native String getTitle(); - - public native String getDescription(); - - public native String getCompany(); - - public native int getCountry(); - - public native int getRegion(); - - public native String getPath(); - - public native String getGameId(); - - public native String getGameTdbId(); - - public native int getDiscNumber(); - - public native int getRevision(); - - public native int getBlobType(); - - public native String getFileFormatName(); - - public native long getBlockSize(); - - public native String getCompressionMethod(); - - public native boolean shouldShowFileFormatDetails(); - - public native boolean shouldAllowConversion(); - - public native long getFileSize(); - - public native boolean isDatelDisc(); - - public native boolean isNKit(); - - public native int[] getBanner(); - - public native int getBannerWidth(); - - public native int getBannerHeight(); - - public String getCustomCoverPath() - { - return getPath().substring(0, getPath().lastIndexOf(".")) + ".cover.png"; - } -} 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 new file mode 100644 index 0000000000..347433f552 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.kt @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.model + +import androidx.annotation.Keep + +@Keep +class GameFile private constructor(private val pointer: Long) { + external fun finalize() + + external fun getPlatform(): Int + + external fun getTitle(): String + + external fun getDescription(): String + + external fun getCompany(): String + + external fun getCountry(): Int + + external fun getRegion(): Int + + external fun getPath(): String + + external fun getGameId(): String + + external fun getGameTdbId(): String + + external fun getDiscNumber(): Int + + external fun getRevision(): Int + + external fun getBlobType(): Int + + external fun getFileFormatName(): String + + external fun getBlockSize(): Long + + external fun getCompressionMethod(): String + + external fun shouldShowFileFormatDetails(): Boolean + + external fun shouldAllowConversion(): Boolean + + external fun getFileSize(): Long + + external fun isDatelDisc(): Boolean + + external fun isNKit(): Boolean + + external fun getBanner(): IntArray + + external fun getBannerWidth(): Int + + external fun getBannerHeight(): Int + + val customCoverPath: String + get() = "${getPath().substring(0, getPath().lastIndexOf("."))}.cover.png" + + companion object { + var REGION_NTSC_J = 0 + var REGION_NTSC_U = 1 + var REGION_PAL = 2 + var REGION_NTSC_K = 4 + + @JvmStatic + external fun parse(path: String): GameFile? + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoverHelper.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoverHelper.kt index 9359d38eb3..172be697e9 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoverHelper.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoverHelper.kt @@ -8,16 +8,16 @@ object CoverHelper { @JvmStatic fun buildGameTDBUrl(game: GameFile, region: String?): String { val baseUrl = "https://art.gametdb.com/wii/cover/%s/%s.png" - return String.format(baseUrl, region, game.gameTdbId) + return String.format(baseUrl, region, game.getGameTdbId()) } @JvmStatic fun getRegion(game: GameFile): String { - val region: String = when (game.region) { + val region: String = when (game.getRegion()) { GameFile.REGION_NTSC_J -> "JA" GameFile.REGION_NTSC_U -> "US" GameFile.REGION_NTSC_K -> "KO" - GameFile.REGION_PAL -> when (game.country) { + GameFile.REGION_PAL -> when (game.getCountry()) { 3 -> "AU" // Australia 4 -> "FR" // France 5 -> "DE" // Germany diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index e4cacaa462..2396f0960b 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -563,7 +563,7 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) const jclass game_file_class = env->FindClass("org/dolphinemu/dolphinemu/model/GameFile"); s_game_file_class = reinterpret_cast(env->NewGlobalRef(game_file_class)); - s_game_file_pointer = env->GetFieldID(game_file_class, "mPointer", "J"); + s_game_file_pointer = env->GetFieldID(game_file_class, "pointer", "J"); s_game_file_constructor = env->GetMethodID(game_file_class, "", "(J)V"); env->DeleteLocalRef(game_file_class); From 09c2c6541d5d4d1aefa42c4bf1616bcceeb37f1d Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 14:19:28 -0400 Subject: [PATCH 2/4] Android: Convert GameFileCache to Kotlin --- .../dolphinemu/model/GameFileCache.java | 139 ---------------- .../dolphinemu/model/GameFileCache.kt | 152 ++++++++++++++++++ .../dolphinemu/ui/main/MainPresenter.kt | 2 +- Source/Android/jni/AndroidCommon/IDCache.cpp | 2 +- 4 files changed, 154 insertions(+), 141 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.java deleted file mode 100644 index 59c8893dcf..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.java +++ /dev/null @@ -1,139 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.model; - -import androidx.annotation.Keep; - -import org.dolphinemu.dolphinemu.NativeLibrary; -import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting; -import org.dolphinemu.dolphinemu.features.settings.model.Settings; -import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile; -import org.dolphinemu.dolphinemu.utils.ContentHandler; -import org.dolphinemu.dolphinemu.utils.IniFile; - -import java.io.File; -import java.util.LinkedHashSet; - -public class GameFileCache -{ - @Keep - private long mPointer; - - public GameFileCache() - { - mPointer = newGameFileCache(); - } - - private static native long newGameFileCache(); - - @Override - public native void finalize(); - - public static void addGameFolder(String path) - { - File dolphinFile = SettingsFile.getSettingsFile(Settings.FILE_DOLPHIN); - IniFile dolphinIni = new IniFile(dolphinFile); - LinkedHashSet pathSet = getPathSet(false); - int totalISOPaths = - dolphinIni.getInt(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATHS, 0); - - if (!pathSet.contains(path)) - { - dolphinIni.setInt(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATHS, - totalISOPaths + 1); - dolphinIni.setString(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATH_BASE + - totalISOPaths, path); - dolphinIni.save(dolphinFile); - NativeLibrary.ReloadConfig(); - } - } - - private static LinkedHashSet getPathSet(boolean removeNonExistentFolders) - { - File dolphinFile = SettingsFile.getSettingsFile(Settings.FILE_DOLPHIN); - IniFile dolphinIni = new IniFile(dolphinFile); - LinkedHashSet pathSet = new LinkedHashSet<>(); - int totalISOPaths = - dolphinIni.getInt(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATHS, 0); - - for (int i = 0; i < totalISOPaths; i++) - { - String path = dolphinIni.getString(Settings.SECTION_INI_GENERAL, - SettingsFile.KEY_ISO_PATH_BASE + i, ""); - - if (ContentHandler.isContentUri(path) ? ContentHandler.exists(path) : new File(path).exists()) - { - pathSet.add(path); - } - } - - if (removeNonExistentFolders && totalISOPaths > pathSet.size()) - { - int setIndex = 0; - - dolphinIni.setInt(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATHS, - pathSet.size()); - - // One or more folders have been removed. - for (String entry : pathSet) - { - dolphinIni.setString(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATH_BASE + - setIndex, entry); - - setIndex++; - } - - // Delete known unnecessary keys. Ignore i values beyond totalISOPaths. - for (int i = setIndex; i < totalISOPaths; i++) - { - dolphinIni.deleteKey(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATH_BASE + i); - } - - dolphinIni.save(dolphinFile); - NativeLibrary.ReloadConfig(); - } - - return pathSet; - } - - public static String[] getAllGamePaths() - { - boolean recursiveScan = BooleanSetting.MAIN_RECURSIVE_ISO_PATHS.getBoolean(); - - LinkedHashSet folderPathsSet = getPathSet(true); - - String[] folderPaths = folderPathsSet.toArray(new String[0]); - - return getAllGamePaths(folderPaths, recursiveScan); - } - - public static native String[] getAllGamePaths(String[] folderPaths, boolean recursiveScan); - - public synchronized native int getSize(); - - public synchronized native GameFile[] getAllGames(); - - public synchronized native GameFile addOrGet(String gamePath); - - /** - * Sets the list of games to cache. - * - * Games which are in the passed-in list but not in the cache are scanned and added to the cache, - * and games which are in the cache but not in the passed-in list are removed from the cache. - * - * @return true if the cache was modified - */ - public synchronized native boolean update(String[] gamePaths); - - /** - * For each game that already is in the cache, scans the folder that contains the game - * for additional metadata files (PNG/XML). - * - * @return true if the cache was modified - */ - public synchronized native boolean updateAdditionalMetadata(); - - public synchronized native boolean load(); - - public synchronized native boolean save(); -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.kt new file mode 100644 index 0000000000..74b1bd535a --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.kt @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.model + +import androidx.annotation.Keep +import org.dolphinemu.dolphinemu.NativeLibrary +import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting +import org.dolphinemu.dolphinemu.features.settings.model.Settings +import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile +import org.dolphinemu.dolphinemu.utils.ContentHandler +import org.dolphinemu.dolphinemu.utils.IniFile +import java.io.File + +class GameFileCache { + @Keep + private val pointer: Long = newGameFileCache() + + external fun finalize() + + @Synchronized + external fun getSize(): Int + + @Synchronized + external fun getAllGames(): Array + + @Synchronized + external fun addOrGet(gamePath: String): GameFile? + + /** + * Sets the list of games to cache. + * + * Games which are in the passed-in list but not in the cache are scanned and added to the cache, + * and games which are in the cache but not in the passed-in list are removed from the cache. + * + * @return true if the cache was modified + */ + @Synchronized + external fun update(gamePaths: Array): Boolean + + /** + * For each game that already is in the cache, scans the folder that contains the game + * for additional metadata files (PNG/XML). + * + * @return true if the cache was modified + */ + @Synchronized + external fun updateAdditionalMetadata(): Boolean + + @Synchronized + external fun load(): Boolean + + @Synchronized + external fun save(): Boolean + + companion object { + @JvmStatic + private external fun newGameFileCache(): Long + + fun addGameFolder(path: String) { + val dolphinFile = SettingsFile.getSettingsFile(Settings.FILE_DOLPHIN) + val dolphinIni = IniFile(dolphinFile) + val pathSet = getPathSet(false) + val totalISOPaths = + dolphinIni.getInt(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATHS, 0) + + if (!pathSet.contains(path)) { + dolphinIni.setInt( + Settings.SECTION_INI_GENERAL, + SettingsFile.KEY_ISO_PATHS, + totalISOPaths + 1 + ) + dolphinIni.setString( + Settings.SECTION_INI_GENERAL, + SettingsFile.KEY_ISO_PATH_BASE + totalISOPaths, + path + ) + dolphinIni.save(dolphinFile) + NativeLibrary.ReloadConfig() + } + } + + private fun getPathSet(removeNonExistentFolders: Boolean): LinkedHashSet { + val dolphinFile = SettingsFile.getSettingsFile(Settings.FILE_DOLPHIN) + val dolphinIni = IniFile(dolphinFile) + val pathSet = LinkedHashSet() + val totalISOPaths = + dolphinIni.getInt(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATHS, 0) + + for (i in 0 until totalISOPaths) { + val path = dolphinIni.getString( + Settings.SECTION_INI_GENERAL, + SettingsFile.KEY_ISO_PATH_BASE + i, + "" + ) + + val pathExists = if (ContentHandler.isContentUri(path)) + ContentHandler.exists(path) + else + File(path).exists() + if (pathExists) { + pathSet.add(path) + } + } + + if (removeNonExistentFolders && totalISOPaths > pathSet.size) { + var setIndex = 0 + + dolphinIni.setInt( + Settings.SECTION_INI_GENERAL, + SettingsFile.KEY_ISO_PATHS, + pathSet.size + ) + + // One or more folders have been removed. + for (entry in pathSet) { + dolphinIni.setString( + Settings.SECTION_INI_GENERAL, + SettingsFile.KEY_ISO_PATH_BASE + setIndex, + entry + ) + setIndex++ + } + + // Delete known unnecessary keys. Ignore i values beyond totalISOPaths. + for (i in setIndex until totalISOPaths) { + dolphinIni.deleteKey( + Settings.SECTION_INI_GENERAL, + SettingsFile.KEY_ISO_PATH_BASE + i + ) + } + + dolphinIni.save(dolphinFile) + NativeLibrary.ReloadConfig() + } + return pathSet + } + + @JvmStatic + fun getAllGamePaths(): Array { + val recursiveScan = BooleanSetting.MAIN_RECURSIVE_ISO_PATHS.boolean + val folderPathsSet = getPathSet(true) + val folderPaths = folderPathsSet.toTypedArray() + return getAllGamePaths(folderPaths, recursiveScan) + } + + @JvmStatic + external fun getAllGamePaths( + folderPaths: Array, + recursiveScan: Boolean + ): Array + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.kt index 64695a0ace..e77b694096 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.kt @@ -123,7 +123,7 @@ class MainPresenter(private val mainView: MainView, private val activity: Fragme fun onResume() { if (dirToAdd != null) { - GameFileCache.addGameFolder(dirToAdd) + GameFileCache.addGameFolder(dirToAdd!!) dirToAdd = null } diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index 2396f0960b..f83707cebe 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -570,7 +570,7 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) const jclass game_file_cache_class = env->FindClass("org/dolphinemu/dolphinemu/model/GameFileCache"); s_game_file_cache_class = reinterpret_cast(env->NewGlobalRef(game_file_cache_class)); - s_game_file_cache_pointer = env->GetFieldID(game_file_cache_class, "mPointer", "J"); + s_game_file_cache_pointer = env->GetFieldID(game_file_cache_class, "pointer", "J"); env->DeleteLocalRef(game_file_cache_class); const jclass analytics_class = env->FindClass("org/dolphinemu/dolphinemu/utils/Analytics"); From ed9467dc1bcfea079b4c7c9a6df725e1616bdeb3 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 14:19:41 -0400 Subject: [PATCH 3/4] Android: Convert HomeScreenChannel to Kotlin --- .../dolphinemu/model/HomeScreenChannel.java | 63 ------------------- .../dolphinemu/model/HomeScreenChannel.kt | 12 ++++ 2 files changed, 12 insertions(+), 63 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/HomeScreenChannel.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/HomeScreenChannel.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/HomeScreenChannel.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/HomeScreenChannel.java deleted file mode 100644 index 6e35b1de83..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/HomeScreenChannel.java +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.model; - -import android.net.Uri; - -/** - * Represents a home screen channel for Android TV api 26+ - */ -public class HomeScreenChannel -{ - private long channelId; - private String name; - private String description; - private Uri appLinkIntentUri; - - public HomeScreenChannel(String name, String description, Uri appLinkIntentUri) - { - this.name = name; - this.description = description; - this.appLinkIntentUri = appLinkIntentUri; - } - - public long getChannelId() - { - return channelId; - } - - public void setChannelId(long channelId) - { - this.channelId = channelId; - } - - public String getName() - { - return name; - } - - public void setName(String name) - { - this.name = name; - } - - public String getDescription() - { - return description; - } - - public void setDescription(String description) - { - this.description = description; - } - - public Uri getAppLinkIntentUri() - { - return appLinkIntentUri; - } - - public void setAppLinkIntentUri(Uri appLinkIntentUri) - { - this.appLinkIntentUri = appLinkIntentUri; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/HomeScreenChannel.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/HomeScreenChannel.kt new file mode 100644 index 0000000000..b5b269fa77 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/HomeScreenChannel.kt @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.model + +import android.net.Uri + +/** + * Represents a home screen channel for Android TV api 26+ + */ +class HomeScreenChannel(var name: String, var description: String, var appLinkIntentUri: Uri) { + var channelId: Long = 0 +} From 6a19629fc6da96fc8fb7f65bae9c72ba4a08f7b0 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 14:19:52 -0400 Subject: [PATCH 4/4] Android: Convert TvSettingsItem to Kotlin --- .../dolphinemu/model/TvSettingsItem.java | 32 ------------------- .../dolphinemu/model/TvSettingsItem.kt | 5 +++ 2 files changed, 5 insertions(+), 32 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TvSettingsItem.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TvSettingsItem.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TvSettingsItem.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TvSettingsItem.java deleted file mode 100644 index c9c4019d94..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TvSettingsItem.java +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.model; - -public final class TvSettingsItem -{ - private final int mItemId; - private final int mIconId; - private final int mLabelId; - - public TvSettingsItem(int itemId, int iconId, int labelId) - { - mItemId = itemId; - mIconId = iconId; - mLabelId = labelId; - } - - public int getItemId() - { - return mItemId; - } - - public int getIconId() - { - return mIconId; - } - - public int getLabelId() - { - return mLabelId; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TvSettingsItem.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TvSettingsItem.kt new file mode 100644 index 0000000000..5c4d5aa3c9 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TvSettingsItem.kt @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.model + +class TvSettingsItem(val itemId: Int, val iconId: Int, val labelId: Int)