From 0e4256651a1f082216b9fb8e4ecd6ebd2d4931f2 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Tue, 7 Mar 2023 21:36:42 -0500 Subject: [PATCH] android: Convert SettingsFile to Kotlin --- .../features/settings/utils/SettingsFile.java | 272 ------------------ .../features/settings/utils/SettingsFile.kt | 245 ++++++++++++++++ 2 files changed, 245 insertions(+), 272 deletions(-) delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.java create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.java deleted file mode 100644 index 2b3d257a3..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.java +++ /dev/null @@ -1,272 +0,0 @@ -package org.yuzu.yuzu_emu.features.settings.utils; - -import androidx.annotation.NonNull; - -import org.yuzu.yuzu_emu.YuzuApplication; -import org.yuzu.yuzu_emu.NativeLibrary; -import org.yuzu.yuzu_emu.R; -import org.yuzu.yuzu_emu.features.settings.model.FloatSetting; -import org.yuzu.yuzu_emu.features.settings.model.IntSetting; -import org.yuzu.yuzu_emu.features.settings.model.Setting; -import org.yuzu.yuzu_emu.features.settings.model.SettingSection; -import org.yuzu.yuzu_emu.features.settings.model.Settings; -import org.yuzu.yuzu_emu.features.settings.model.StringSetting; -import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView; -import org.yuzu.yuzu_emu.utils.BiMap; -import org.yuzu.yuzu_emu.utils.DirectoryInitialization; -import org.yuzu.yuzu_emu.utils.Log; -import org.ini4j.Wini; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.util.HashMap; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; - -/** - * Contains static methods for interacting with .ini files in which settings are stored. - */ -public final class SettingsFile { - public static final String FILE_NAME_CONFIG = "config"; - - public static final String KEY_DESIGN = "design"; - - // CPU - public static final String KEY_CPU_ACCURACY = "cpu_accuracy"; - // System - public static final String KEY_USE_DOCKED_MODE = "use_docked_mode"; - public static final String KEY_REGION_INDEX = "region_index"; - public static final String KEY_LANGUAGE_INDEX = "language_index"; - public static final String KEY_RENDERER_BACKEND = "backend"; - // Renderer - public static final String KEY_RENDERER_RESOLUTION = "resolution_setup"; - public static final String KEY_RENDERER_ASPECT_RATIO = "aspect_ratio"; - public static final String KEY_RENDERER_ACCURACY = "gpu_accuracy"; - public static final String KEY_RENDERER_ASYNCHRONOUS_SHADERS = "use_asynchronous_shaders"; - public static final String KEY_RENDERER_FORCE_MAX_CLOCK = "force_max_clock"; - public static final String KEY_RENDERER_USE_SPEED_LIMIT = "use_speed_limit"; - public static final String KEY_RENDERER_DEBUG = "debug"; - public static final String KEY_RENDERER_SPEED_LIMIT = "speed_limit"; - // Audio - public static final String KEY_AUDIO_VOLUME = "volume"; - - private static BiMap sectionsMap = new BiMap<>(); - - static { - //TODO: Add members to sectionsMap when game-specific settings are added - } - - - private SettingsFile() { - } - - /** - * Reads a given .ini file from disk and returns it as a HashMap of Settings, themselves - * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it - * failed. - * - * @param ini The ini file to load the settings from - * @param isCustomGame - * @param view The current view. - * @return An Observable that emits a HashMap of the file's contents, then completes. - */ - static HashMap readFile(final File ini, boolean isCustomGame, SettingsActivityView view) { - HashMap sections = new Settings.SettingsSectionMap(); - - BufferedReader reader = null; - - try { - reader = new BufferedReader(new FileReader(ini)); - - SettingSection current = null; - for (String line; (line = reader.readLine()) != null; ) { - if (line.startsWith("[") && line.endsWith("]")) { - current = sectionFromLine(line, isCustomGame); - sections.put(current.getName(), current); - } else if ((current != null)) { - Setting setting = settingFromLine(current, line); - if (setting != null) { - current.putSetting(setting); - } - } - } - } catch (FileNotFoundException e) { - Log.error("[SettingsFile] File not found: " + e.getMessage()); - if (view != null) - view.onSettingsFileNotFound(); - } catch (IOException e) { - Log.error("[SettingsFile] Error reading from: " + e.getMessage()); - if (view != null) - view.onSettingsFileNotFound(); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - Log.error("[SettingsFile] Error closing: " + e.getMessage()); - } - } - } - - return sections; - } - - public static HashMap readFile(final String fileName, SettingsActivityView view) { - return readFile(getSettingsFile(fileName), false, view); - } - - /** - * Reads a given .ini file from disk and returns it as a HashMap of SettingSections, themselves - * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it - * failed. - * - * @param gameId the id of the game to load it's settings. - * @param view The current view. - */ - public static HashMap readCustomGameSettings(final String gameId, SettingsActivityView view) { - return readFile(getCustomGameSettingsFile(gameId), true, view); - } - - /** - * Saves a Settings HashMap to 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 sections The HashMap containing the Settings we want to serialize. - * @param view The current view. - */ - public static void saveFile(final String fileName, TreeMap sections, - SettingsActivityView view) { - File ini = getSettingsFile(fileName); - - try { - Wini writer = new Wini(ini); - - Set keySet = sections.keySet(); - for (String key : keySet) { - SettingSection section = sections.get(key); - writeSection(writer, section); - } - writer.store(); - } catch (IOException e) { - Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.getMessage()); - view.showToastMessage(YuzuApplication.getAppContext().getString(R.string.error_saving, fileName, e.getMessage()), false); - } - } - - - public static void saveCustomGameSettings(final String gameId, final HashMap sections) { - Set sortedSections = new TreeSet<>(sections.keySet()); - - for (String sectionKey : sortedSections) { - SettingSection section = sections.get(sectionKey); - - HashMap settings = section.getSettings(); - Set sortedKeySet = new TreeSet<>(settings.keySet()); - - for (String settingKey : sortedKeySet) { - Setting setting = settings.get(settingKey); - NativeLibrary.SetUserSetting(gameId, mapSectionNameFromIni(section.getName()), setting.getKey(), setting.getValueAsString()); - } - } - } - - private static String mapSectionNameFromIni(String generalSectionName) { - if (sectionsMap.getForward(generalSectionName) != null) { - return sectionsMap.getForward(generalSectionName); - } - - return generalSectionName; - } - - private static String mapSectionNameToIni(String generalSectionName) { - if (sectionsMap.getBackward(generalSectionName) != null) { - return sectionsMap.getBackward(generalSectionName); - } - - return generalSectionName; - } - - @NonNull - private static File getSettingsFile(String fileName) { - return new File( - DirectoryInitialization.getUserDirectory() + "/config/" + fileName + ".ini"); - } - - private static File getCustomGameSettingsFile(String gameId) { - return new File(DirectoryInitialization.getUserDirectory() + "/GameSettings/" + gameId + ".ini"); - } - - private static SettingSection sectionFromLine(String line, boolean isCustomGame) { - String sectionName = line.substring(1, line.length() - 1); - if (isCustomGame) { - sectionName = mapSectionNameToIni(sectionName); - } - return new SettingSection(sectionName); - } - - /** - * For a line of text, determines what type of data is being represented, and returns - * a Setting object containing this data. - * - * @param current The section currently being parsed by the consuming method. - * @param line The line of text being parsed. - * @return A typed Setting containing the key/value contained in the line. - */ - private static Setting settingFromLine(SettingSection current, String line) { - String[] splitLine = line.split("="); - - if (splitLine.length != 2) { - Log.warning("Skipping invalid config line \"" + line + "\""); - return null; - } - - String key = splitLine[0].trim(); - String value = splitLine[1].trim(); - - if (value.isEmpty()) { - Log.warning("Skipping null value in config line \"" + line + "\""); - return null; - } - - try { - int valueAsInt = Integer.parseInt(value); - - return new IntSetting(key, current.getName(), valueAsInt); - } catch (NumberFormatException ex) { - } - - try { - float valueAsFloat = Float.parseFloat(value); - - return new FloatSetting(key, current.getName(), valueAsFloat); - } catch (NumberFormatException ex) { - } - - return new StringSetting(key, current.getName(), value); - } - - /** - * Writes the contents of a Section HashMap to disk. - * - * @param parser A Wini pointed at a file on disk. - * @param section A section containing settings to be written to the file. - */ - private static void writeSection(Wini parser, SettingSection section) { - // Write the section header. - String header = section.getName(); - - // Write this section's values. - HashMap settings = section.getSettings(); - Set keySet = settings.keySet(); - - for (String key : keySet) { - Setting setting = settings.get(key); - parser.put(header, setting.getKey(), setting.getValueAsString()); - } - } -} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt new file mode 100644 index 000000000..8e21c65f0 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt @@ -0,0 +1,245 @@ +package org.yuzu.yuzu_emu.features.settings.utils + +import org.ini4j.Wini +import org.yuzu.yuzu_emu.NativeLibrary +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.YuzuApplication +import org.yuzu.yuzu_emu.features.settings.model.* +import org.yuzu.yuzu_emu.features.settings.model.Settings.SettingsSectionMap +import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView +import org.yuzu.yuzu_emu.utils.BiMap +import org.yuzu.yuzu_emu.utils.DirectoryInitialization +import org.yuzu.yuzu_emu.utils.Log +import java.io.* +import java.util.* + +/** + * Contains static methods for interacting with .ini files in which settings are stored. + */ +object SettingsFile { + const val FILE_NAME_CONFIG = "config" + const val KEY_DESIGN = "design" + + // CPU + const val KEY_CPU_ACCURACY = "cpu_accuracy" + + // System + const val KEY_USE_DOCKED_MODE = "use_docked_mode" + const val KEY_REGION_INDEX = "region_index" + const val KEY_LANGUAGE_INDEX = "language_index" + const val KEY_RENDERER_BACKEND = "backend" + + // Renderer + const val KEY_RENDERER_RESOLUTION = "resolution_setup" + const val KEY_RENDERER_ASPECT_RATIO = "aspect_ratio" + const val KEY_RENDERER_ACCURACY = "gpu_accuracy" + const val KEY_RENDERER_ASYNCHRONOUS_SHADERS = "use_asynchronous_shaders" + const val KEY_RENDERER_FORCE_MAX_CLOCK = "force_max_clock" + const val KEY_RENDERER_USE_SPEED_LIMIT = "use_speed_limit" + const val KEY_RENDERER_DEBUG = "debug" + const val KEY_RENDERER_SPEED_LIMIT = "speed_limit" + + // Audio + const val KEY_AUDIO_VOLUME = "volume" + private val sectionsMap = BiMap() + + /** + * Reads a given .ini file from disk and returns it as a HashMap of Settings, themselves + * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it + * failed. + * + * @param ini The ini file to load the settings from + * @param isCustomGame + * @param view The current view. + * @return An Observable that emits a HashMap of the file's contents, then completes. + */ + private fun readFile( + ini: File?, + isCustomGame: Boolean, + view: SettingsActivityView? + ): HashMap { + val sections: HashMap = SettingsSectionMap() + var reader: BufferedReader? = null + try { + reader = BufferedReader(FileReader(ini)) + var current: SettingSection? = null + var line: String? + while (reader.readLine().also { line = it } != null) { + if (line!!.startsWith("[") && line!!.endsWith("]")) { + current = sectionFromLine(line!!, isCustomGame) + sections[current.name] = current + } else if (current != null) { + val setting = settingFromLine(current, line!!) + if (setting != null) { + current.putSetting(setting) + } + } + } + } catch (e: FileNotFoundException) { + Log.error("[SettingsFile] File not found: " + e.message) + view?.onSettingsFileNotFound() + } catch (e: IOException) { + Log.error("[SettingsFile] Error reading from: " + e.message) + view?.onSettingsFileNotFound() + } finally { + if (reader != null) { + try { + reader.close() + } catch (e: IOException) { + Log.error("[SettingsFile] Error closing: " + e.message) + } + } + } + return sections + } + + fun readFile(fileName: String, view: SettingsActivityView): HashMap { + return readFile(getSettingsFile(fileName), false, view) + } + + /** + * Reads a given .ini file from disk and returns it as a HashMap of SettingSections, themselves + * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it + * failed. + * + * @param gameId the id of the game to load it's settings. + * @param view The current view. + */ + fun readCustomGameSettings( + gameId: String, + view: SettingsActivityView + ): HashMap { + return readFile(getCustomGameSettingsFile(gameId), true, view) + } + + /** + * Saves a Settings HashMap to 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 sections The HashMap containing the Settings we want to serialize. + * @param view The current view. + */ + fun saveFile( + fileName: String, + sections: TreeMap, + view: SettingsActivityView + ) { + val ini = getSettingsFile(fileName) + try { + val writer = Wini(ini) + val keySet: Set = sections.keys + for (key in keySet) { + val section = sections[key] + writeSection(writer, section!!) + } + writer.store() + } catch (e: IOException) { + Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message) + view.showToastMessage( + YuzuApplication.appContext + .getString(R.string.error_saving, fileName, e.message), + false + ) + } + } + + fun saveCustomGameSettings(gameId: String?, sections: HashMap) { + val sortedSections: Set = TreeSet(sections.keys) + for (sectionKey in sortedSections) { + val section = sections[sectionKey] + val settings = section!!.settings + val sortedKeySet: Set = TreeSet(settings.keys) + for (settingKey in sortedKeySet) { + val setting = settings[settingKey] + NativeLibrary.SetUserSetting( + gameId, mapSectionNameFromIni( + section.name + ), setting!!.key, setting.valueAsString + ) + } + } + } + + private fun mapSectionNameFromIni(generalSectionName: String): String? { + return if (sectionsMap.getForward(generalSectionName) != null) { + sectionsMap.getForward(generalSectionName) + } else generalSectionName + } + + private fun mapSectionNameToIni(generalSectionName: String): String { + return if (sectionsMap.getBackward(generalSectionName) != null) { + sectionsMap.getBackward(generalSectionName).toString() + } else generalSectionName + } + + private fun getSettingsFile(fileName: String): File { + return File( + DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini" + ) + } + + private fun getCustomGameSettingsFile(gameId: String): File { + return File(DirectoryInitialization.userDirectory + "/GameSettings/" + gameId + ".ini") + } + + private fun sectionFromLine(line: String, isCustomGame: Boolean): SettingSection { + var sectionName: String = line.substring(1, line.length - 1) + if (isCustomGame) { + sectionName = mapSectionNameToIni(sectionName) + } + return SettingSection(sectionName) + } + + /** + * For a line of text, determines what type of data is being represented, and returns + * a Setting object containing this data. + * + * @param current The section currently being parsed by the consuming method. + * @param line The line of text being parsed. + * @return A typed Setting containing the key/value contained in the line. + */ + private fun settingFromLine(current: SettingSection, line: String): Setting? { + val splitLine = line.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + if (splitLine.size != 2) { + Log.warning("Skipping invalid config line \"$line\"") + return null + } + val key = splitLine[0].trim { it <= ' ' } + val value = splitLine[1].trim { it <= ' ' } + if (value.isEmpty()) { + Log.warning("Skipping null value in config line \"$line\"") + return null + } + try { + val valueAsInt = value.toInt() + return IntSetting(key, current.name, valueAsInt) + } catch (_: NumberFormatException) { + } + try { + val valueAsFloat = value.toFloat() + return FloatSetting(key, current.name, valueAsFloat) + } catch (_: NumberFormatException) { + } + return StringSetting(key, current.name, value) + } + + /** + * Writes the contents of a Section HashMap to disk. + * + * @param parser A Wini pointed at a file on disk. + * @param section A section containing settings to be written to the file. + */ + private fun writeSection(parser: Wini, section: SettingSection) { + // Write the section header. + val header = section.name + + // Write this section's values. + val settings = section.settings + val keySet: Set = settings.keys + for (key in keySet) { + val setting = settings[key] + parser.put(header, setting!!.key, setting.valueAsString) + } + } +}