Android: Use JNI for setting/getting ISO paths

This gets rid of the last Android-specific code that directly interfaces
with INI files.
This commit is contained in:
JosJuice
2023-09-02 12:44:26 +02:00
parent 5e5887a378
commit 190e71a318
8 changed files with 35 additions and 595 deletions

View File

@ -1,107 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.settings.utils
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivityView
import org.dolphinemu.dolphinemu.utils.BiMap
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization
import org.dolphinemu.dolphinemu.utils.IniFile
import org.dolphinemu.dolphinemu.utils.Log
import java.io.File
/**
* Contains static methods for interacting with .ini files in which settings are stored.
*/
object SettingsFile {
const val KEY_ISO_PATH_BASE = "ISOPath"
const val KEY_ISO_PATHS = "ISOPaths"
private val sectionsMap = BiMap<String?, String?>()
init {
sectionsMap.apply {
add("Hardware", "Video_Hardware")
add("Settings", "Video_Settings")
add("Enhancements", "Video_Enhancements")
add("Stereoscopy", "Video_Stereoscopy")
add("Hacks", "Video_Hacks")
add("GameSpecific", "Video")
}
}
/**
* Reads a given .ini file from disk and returns it.
* If unsuccessful, outputs an error telling why it failed.
*
* @param file The ini file to load the settings from
* @param ini The object to load into
* @param view The current view.
*/
private fun readFile(file: File, ini: IniFile, view: SettingsActivityView) {
if (!ini.load(file, true)) {
Log.error("[SettingsFile] Error reading from: " + file.absolutePath)
view.onSettingsFileNotFound()
}
}
fun readFile(fileName: String, ini: IniFile, view: SettingsActivityView) {
readFile(getSettingsFile(fileName), ini, view)
}
/**
* Reads a given .ini file from disk and returns it.
* If unsuccessful, outputs an error telling why it failed.
*
* @param gameId the id of the game to load settings for.
* @param ini The object to load into
* @param view The current view.
*/
fun readCustomGameSettings(
gameId: String,
ini: IniFile,
view: SettingsActivityView
) {
readFile(getCustomGameSettingsFile(gameId), ini, view)
}
/**
* Saves a given .ini file on disk.
* If unsuccessful, outputs an error telling why it failed.
*
* @param fileName The target filename without a path or extension.
* @param ini The IniFile we want to serialize.
* @param view The current view.
*/
fun saveFile(fileName: String, ini: IniFile, view: SettingsActivityView) {
if (!ini.save(getSettingsFile(fileName))) {
Log.error("[SettingsFile] Error saving to: $fileName.ini")
view.showToastMessage("Error saving $fileName.ini")
}
}
fun saveCustomGameSettings(gameId: String, ini: IniFile) {
ini.save(getCustomGameSettingsFile(gameId))
}
fun mapSectionNameFromIni(generalSectionName: String): String? {
return if (sectionsMap.getForward(generalSectionName) != null) {
sectionsMap.getForward(generalSectionName)
} else generalSectionName
}
fun mapSectionNameToIni(generalSectionName: String): String? {
return if (sectionsMap.getBackward(generalSectionName) != null) {
sectionsMap.getBackward(generalSectionName)
} else generalSectionName
}
@JvmStatic
fun getSettingsFile(fileName: String): File {
return File(DirectoryInitialization.getUserDirectory() + "/Config/" + fileName + ".ini")
}
private fun getCustomGameSettingsFile(gameId: String): File {
return File(
DirectoryInitialization.getUserDirectory() + "/GameSettings/" + gameId + ".ini"
)
}
}

View File

@ -3,12 +3,9 @@
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.features.settings.model.NativeConfig
import org.dolphinemu.dolphinemu.utils.ContentHandler
import org.dolphinemu.dolphinemu.utils.IniFile
import java.io.File
class GameFileCache {
@ -57,89 +54,36 @@ class GameFileCache {
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)
val paths = getIsoPaths()
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()
if (!paths.contains(path)) {
setIsoPaths(paths + path)
NativeConfig.save(NativeConfig.LAYER_BASE)
}
}
private fun getPathSet(removeNonExistentFolders: Boolean): LinkedHashSet<String> {
val dolphinFile = SettingsFile.getSettingsFile(Settings.FILE_DOLPHIN)
val dolphinIni = IniFile(dolphinFile)
val pathSet = LinkedHashSet<String>()
val totalISOPaths =
dolphinIni.getInt(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATHS, 0)
private fun getFolderPaths(removeNonExistentFolders: Boolean): Array<String> {
val paths = getIsoPaths()
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))
val filteredPaths = paths.filter {path ->
if (ContentHandler.isContentUri(path))
ContentHandler.exists(path)
else
File(path).exists()
if (pathExists) {
pathSet.add(path)
}
}.toTypedArray()
if (removeNonExistentFolders && paths.size > filteredPaths.size) {
setIsoPaths(filteredPaths)
NativeConfig.save(NativeConfig.LAYER_BASE)
}
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
return filteredPaths
}
@JvmStatic
fun getAllGamePaths(): Array<String> {
val recursiveScan = BooleanSetting.MAIN_RECURSIVE_ISO_PATHS.boolean
val folderPathsSet = getPathSet(true)
val folderPaths = folderPathsSet.toTypedArray()
val folderPaths = getFolderPaths(true)
return getAllGamePaths(folderPaths, recursiveScan)
}
@ -148,5 +92,11 @@ class GameFileCache {
folderPaths: Array<String>,
recursiveScan: Boolean
): Array<String>
@JvmStatic
external fun getIsoPaths(): Array<String>
@JvmStatic
external fun setIsoPaths(paths: Array<String>)
}
}

View File

@ -1,118 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.utils;
import androidx.annotation.Keep;
import java.io.File;
// An in-memory copy of an INI file
public class IniFile
{
// This class is non-static to ensure that the IniFile parent does not get garbage collected
// while a section still is accessible. (The finalizer of IniFile deletes the native sections.)
@SuppressWarnings("InnerClassMayBeStatic")
public class Section
{
@Keep
private long mPointer;
@Keep
private Section(long pointer)
{
mPointer = pointer;
}
public native boolean exists(String key);
public native boolean delete(String key);
public native String getString(String key, String defaultValue);
public native boolean getBoolean(String key, boolean defaultValue);
public native int getInt(String key, int defaultValue);
public native float getFloat(String key, float defaultValue);
public native void setString(String key, String newValue);
public native void setBoolean(String key, boolean newValue);
public native void setInt(String key, int newValue);
public native void setFloat(String key, float newFloat);
}
@Keep
private long mPointer;
public IniFile()
{
mPointer = newIniFile();
}
public IniFile(IniFile other)
{
mPointer = copyIniFile(other);
}
public IniFile(String path)
{
this();
load(path, false);
}
public IniFile(File file)
{
this();
load(file, false);
}
public native boolean load(String path, boolean keepCurrentData);
public boolean load(File file, boolean keepCurrentData)
{
return load(file.getPath(), keepCurrentData);
}
public native boolean save(String path);
public boolean save(File file)
{
return save(file.getPath());
}
public native Section getOrCreateSection(String sectionName);
public native boolean exists(String sectionName);
public native boolean exists(String sectionName, String key);
public native boolean deleteSection(String sectionName);
public native boolean deleteKey(String sectionName, String key);
public native String getString(String sectionName, String key, String defaultValue);
public native boolean getBoolean(String sectionName, String key, boolean defaultValue);
public native int getInt(String sectionName, String key, int defaultValue);
public native float getFloat(String sectionName, String key, float defaultValue);
public native void setString(String sectionName, String key, String newValue);
public native void setBoolean(String sectionName, String key, boolean newValue);
public native void setInt(String sectionName, String key, int newValue);
public native void setFloat(String sectionName, String key, float newValue);
@Override
public native void finalize();
private native long newIniFile();
private native long copyIniFile(IniFile other);
}