don't use json/txt file endings for custom layouts

just determine json or simple when parsing

fixes #1034
This commit is contained in:
Helium314 2024-08-26 22:51:09 +02:00
parent a1a7489856
commit 44eb296d38
4 changed files with 75 additions and 50 deletions

View file

@ -96,26 +96,28 @@ object RawKeyboardParser {
context.assets.open("layouts${File.separator}$name").reader().use { it.readText() } context.assets.open("layouts${File.separator}$name").reader().use { it.readText() }
} }
} else context.assets.open("layouts${File.separator}$layoutFileName").reader().use { it.readText() } } else context.assets.open("layouts${File.separator}$layoutFileName").reader().use { it.readText() }
if (layoutFileName.endsWith(".json")) { if (layoutFileName.endsWith(".json") || layoutFileName.startsWith(CUSTOM_LAYOUT_PREFIX)) {
val florisKeyData = parseJsonString(layoutText) try {
return { params -> val florisKeyData = parseJsonString(layoutText)
florisKeyData.mapTo(mutableListOf()) { row -> return { params ->
row.mapNotNullTo(mutableListOf()) { it.compute(params) } florisKeyData.mapTo(mutableListOf()) { row ->
} row.mapNotNullTo(mutableListOf()) { it.compute(params) }
}
} else {
val simpleKeyData = parseSimpleString(layoutText)
return { params ->
simpleKeyData.mapIndexedTo(mutableListOf()) { i, row ->
val newRow = row.toMutableList()
if (params.mId.isAlphabetKeyboard
&& params.mId.mSubtype.keyboardLayoutSetName.endsWith("+")
&& "$layoutName+" == params.mId.mSubtype.keyboardLayoutSetName
) {
params.mLocaleKeyboardInfos.getExtraKeys(i+1)?.let { newRow.addAll(it) }
} }
newRow
} }
} catch (_: Exception) { }
}
// not a json, or invalid json
val simpleKeyData = parseSimpleString(layoutText)
return { params ->
simpleKeyData.mapIndexedTo(mutableListOf()) { i, row ->
val newRow = row.toMutableList()
if (params.mId.isAlphabetKeyboard
&& params.mId.mSubtype.keyboardLayoutSetName.endsWith("+")
&& "$layoutName+" == params.mId.mSubtype.keyboardLayoutSetName
) {
params.mLocaleKeyboardInfos.getExtraKeys(i+1)?.let { newRow.addAll(it) }
}
newRow
} }
} }
} }

View file

@ -11,9 +11,11 @@ import helium314.keyboard.latin.settings.USER_DICTIONARY_SUFFIX
import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX
import helium314.keyboard.latin.utils.DeviceProtectedUtils import helium314.keyboard.latin.utils.DeviceProtectedUtils
import helium314.keyboard.latin.utils.DictionaryInfoUtils import helium314.keyboard.latin.utils.DictionaryInfoUtils
import helium314.keyboard.latin.utils.Log
import helium314.keyboard.latin.utils.ToolbarKey import helium314.keyboard.latin.utils.ToolbarKey
import helium314.keyboard.latin.utils.defaultPinnedToolbarPref import helium314.keyboard.latin.utils.defaultPinnedToolbarPref
import helium314.keyboard.latin.utils.getCustomLayoutFile import helium314.keyboard.latin.utils.getCustomLayoutFile
import helium314.keyboard.latin.utils.getCustomLayoutFiles
import helium314.keyboard.latin.utils.onCustomLayoutFileListChanged import helium314.keyboard.latin.utils.onCustomLayoutFileListChanged
import helium314.keyboard.latin.utils.upgradeToolbarPrefs import helium314.keyboard.latin.utils.upgradeToolbarPrefs
import java.io.File import java.io.File
@ -104,6 +106,45 @@ fun checkVersionUpgrade(context: Context) {
} }
} }
} }
if (oldVersion <= 2201) {
val additionalSubtypeString = Settings.readPrefAdditionalSubtypes(prefs, context.resources)
if (additionalSubtypeString.contains(".")) { // means there are custom layouts
val subtypeStrings = additionalSubtypeString.split(";")
val newSubtypeStrings = subtypeStrings.mapNotNull {
val split = it.split(":").toMutableList()
Log.i("test", "0: $it")
if (split.size < 2) return@mapNotNull null // should never happen
val oldName = split[1]
val newName = oldName.substringBeforeLast(".") + "."
if (oldName == newName) return@mapNotNull split.joinToString(":") // should never happen
val oldFile = getCustomLayoutFile(oldName, context)
val newFile = getCustomLayoutFile(newName, context)
Log.i("test", "1")
if (!oldFile.exists()) return@mapNotNull null // should never happen
Log.i("test", "2")
if (newFile.exists()) newFile.delete() // should never happen
Log.i("test", "3")
oldFile.renameTo(newFile)
val enabledSubtypes = prefs.getString(Settings.PREF_ENABLED_SUBTYPES, "")!!
if (enabledSubtypes.contains(oldName))
prefs.edit { putString(Settings.PREF_ENABLED_SUBTYPES, enabledSubtypes.replace(oldName, newName)) }
val selectedSubtype = prefs.getString(Settings.PREF_SELECTED_SUBTYPE, "")!!
if (selectedSubtype.contains(oldName))
prefs.edit { putString(Settings.PREF_SELECTED_SUBTYPE, selectedSubtype.replace(oldName, newName)) }
split[1] = newName
split.joinToString(":")
}
Settings.writePrefAdditionalSubtypes(prefs, newSubtypeStrings.joinToString(";"))
}
// rename other custom layouts
onCustomLayoutFileListChanged()
getCustomLayoutFiles(context).forEach {
val newFile = getCustomLayoutFile(it.name.substringBeforeLast(".") + ".", context)
if (newFile.name == it.name) return@forEach
if (newFile.exists()) newFile.delete() // should never happen
it.renameTo(newFile)
}
}
upgradeToolbarPrefs(prefs) upgradeToolbarPrefs(prefs)
onCustomLayoutFileListChanged() // just to be sure onCustomLayoutFileListChanged() // just to be sure
prefs.edit { putInt(Settings.PREF_VERSION_CODE, BuildConfig.VERSION_CODE) } prefs.edit { putInt(Settings.PREF_VERSION_CODE, BuildConfig.VERSION_CODE) }

View file

@ -87,7 +87,7 @@ class AdvancedSettingsFragment : SubScreenFragment() {
private val libfile by lazy { File(requireContext().filesDir.absolutePath + File.separator + JniUtils.JNI_LIB_IMPORT_FILE_NAME) } private val libfile by lazy { File(requireContext().filesDir.absolutePath + File.separator + JniUtils.JNI_LIB_IMPORT_FILE_NAME) }
private val backupFilePatterns by lazy { listOf( private val backupFilePatterns by lazy { listOf(
"blacklists/.*\\.txt".toRegex(), "blacklists/.*\\.txt".toRegex(),
"layouts/.*.(txt|json)".toRegex(), "layouts/$CUSTOM_LAYOUT_PREFIX+\\..{0,4}".toRegex(), // can't expect a period at the end, as this would break restoring older backups
"dicts/.*/.*user\\.dict".toRegex(), "dicts/.*/.*user\\.dict".toRegex(),
"UserHistoryDictionary.*/UserHistoryDictionary.*\\.(body|header)".toRegex(), "UserHistoryDictionary.*/UserHistoryDictionary.*\\.(body|header)".toRegex(),
"custom_background_image.*".toRegex(), "custom_background_image.*".toRegex(),
@ -190,7 +190,7 @@ class AdvancedSettingsFragment : SubScreenFragment() {
?.let { requireContext().assets.open("layouts" + File.separator + it).reader().readText() } ?.let { requireContext().assets.open("layouts" + File.separator + it).reader().readText() }
} }
val displayName = layoutName.getStringResourceOrName("layout_", requireContext()) val displayName = layoutName.getStringResourceOrName("layout_", requireContext())
editCustomLayout(customLayoutName ?: "$CUSTOM_LAYOUT_PREFIX$layoutName.txt", requireContext(), originalLayout, displayName) editCustomLayout(customLayoutName ?: "$CUSTOM_LAYOUT_PREFIX$layoutName.", requireContext(), originalLayout, displayName)
} }
private fun showCustomizeFunctionalKeyLayoutsDialog() { private fun showCustomizeFunctionalKeyLayoutsDialog() {
@ -216,7 +216,7 @@ class AdvancedSettingsFragment : SubScreenFragment() {
requireContext().assets.open("layouts" + File.separator + defaultLayoutName).reader().readText() requireContext().assets.open("layouts" + File.separator + defaultLayoutName).reader().readText()
} }
val displayName = layoutName.substringAfter(CUSTOM_LAYOUT_PREFIX).getStringResourceOrName("layout_", requireContext()) val displayName = layoutName.substringAfter(CUSTOM_LAYOUT_PREFIX).getStringResourceOrName("layout_", requireContext())
editCustomLayout(customLayoutName ?: "$layoutName.json", requireContext(), originalLayout, displayName) editCustomLayout(customLayoutName ?: "$layoutName.", requireContext(), originalLayout, displayName)
} }
@SuppressLint("ApplySharedPref") @SuppressLint("ApplySharedPref")

View file

@ -52,8 +52,10 @@ fun loadCustomLayout(uri: Uri?, languageTag: String, context: Context, onAdded:
fun loadCustomLayout(layoutContent: String, layoutName: String, languageTag: String, context: Context, onAdded: (String) -> Unit) { fun loadCustomLayout(layoutContent: String, layoutName: String, languageTag: String, context: Context, onAdded: (String) -> Unit) {
var name = layoutName var name = layoutName
val isJson = checkLayout(layoutContent, context) if (!checkLayout(layoutContent, context))
?: return infoDialog(context, context.getString(R.string.layout_error, "invalid layout file, ${Log.getLog(10).lastOrNull { it.tag == TAG }?.message}")) return infoDialog(context, context.getString(R.string.layout_error, "invalid layout file, ${Log.getLog(10).lastOrNull { it.tag == TAG }?.message}"))
// val isJson = checkLayout(layoutContent, context)
// ?: return infoDialog(context, context.getString(R.string.layout_error, "invalid layout file, ${Log.getLog(10).lastOrNull { it.tag == TAG }?.message}"))
AlertDialog.Builder(context) AlertDialog.Builder(context)
.setTitle(R.string.title_layout_name_select) .setTitle(R.string.title_layout_name_select)
@ -66,7 +68,7 @@ fun loadCustomLayout(layoutContent: String, layoutName: String, languageTag: Str
}) })
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
// name must be encoded to avoid issues with validity of subtype extra string or file name // name must be encoded to avoid issues with validity of subtype extra string or file name
name = "$CUSTOM_LAYOUT_PREFIX${languageTag}.${encodeBase36(name)}.${if (isJson) "json" else "txt"}" name = "$CUSTOM_LAYOUT_PREFIX${languageTag}.${encodeBase36(name)}."
val file = getCustomLayoutFile(name, context) val file = getCustomLayoutFile(name, context)
if (file.exists()) if (file.exists())
file.delete() file.delete()
@ -77,28 +79,23 @@ fun loadCustomLayout(layoutContent: String, layoutName: String, languageTag: Str
.show() .show()
} }
/** @return true if json, false if simple, null if invalid */ private fun checkLayout(layoutContent: String, context: Context): Boolean {
private fun checkLayout(layoutContent: String, context: Context): Boolean? {
val params = KeyboardParams() val params = KeyboardParams()
params.mId = KeyboardLayoutSet.getFakeKeyboardId(KeyboardId.ELEMENT_ALPHABET) params.mId = KeyboardLayoutSet.getFakeKeyboardId(KeyboardId.ELEMENT_ALPHABET)
params.mPopupKeyTypes.add(POPUP_KEYS_LAYOUT) params.mPopupKeyTypes.add(POPUP_KEYS_LAYOUT)
addLocaleKeyTextsToParams(context, params, POPUP_KEYS_NORMAL) addLocaleKeyTextsToParams(context, params, POPUP_KEYS_NORMAL)
try { try {
val keys = RawKeyboardParser.parseJsonString(layoutContent).map { row -> row.mapNotNull { it.compute(params)?.toKeyParams(params) } } val keys = RawKeyboardParser.parseJsonString(layoutContent).map { row -> row.mapNotNull { it.compute(params)?.toKeyParams(params) } }
if (!checkKeys(keys)) return checkKeys(keys)
return null
return true
} catch (e: SerializationException) { } catch (e: SerializationException) {
Log.w(TAG, "json parsing error", e) Log.w(TAG, "json parsing error", e)
} catch (e: Exception) { } catch (e: Exception) {
Log.w(TAG, "json layout parsed, but considered invalid", e) Log.w(TAG, "json layout parsed, but considered invalid", e)
return null return false
} }
try { try {
val keys = RawKeyboardParser.parseSimpleString(layoutContent).map { row -> row.map { it.toKeyParams(params) } } val keys = RawKeyboardParser.parseSimpleString(layoutContent).map { row -> row.map { it.toKeyParams(params) } }
if (!checkKeys(keys)) return checkKeys(keys)
return null
return false
} catch (e: Exception) { Log.w(TAG, "error parsing custom simple layout", e) } } catch (e: Exception) { Log.w(TAG, "error parsing custom simple layout", e) }
if (layoutContent.trimStart().startsWith("[") && layoutContent.trimEnd().endsWith("]")) { if (layoutContent.trimStart().startsWith("[") && layoutContent.trimEnd().endsWith("]")) {
// layout can't be loaded, assume it's json -> load json layout again because the error message shown to the user is from the most recent error // layout can't be loaded, assume it's json -> load json layout again because the error message shown to the user is from the most recent error
@ -106,7 +103,7 @@ private fun checkLayout(layoutContent: String, context: Context): Boolean? {
RawKeyboardParser.parseJsonString(layoutContent).map { row -> row.mapNotNull { it.compute(params)?.toKeyParams(params) } } RawKeyboardParser.parseJsonString(layoutContent).map { row -> row.mapNotNull { it.compute(params)?.toKeyParams(params) } }
} catch (e: Exception) { Log.w(TAG, "json parsing error", e) } } catch (e: Exception) { Log.w(TAG, "json parsing error", e) }
} }
return null return false
} }
fun checkKeys(keys: List<List<Key.KeyParams>>): Boolean { fun checkKeys(keys: List<List<Key.KeyParams>>): Boolean {
@ -190,27 +187,12 @@ fun editCustomLayout(layoutName: String, context: Context, startContent: String?
.setView(editText) .setView(editText)
.setPositiveButton(R.string.save) { _, _ -> .setPositiveButton(R.string.save) { _, _ ->
val content = editText.text.toString() val content = editText.text.toString()
val isJson = checkLayout(content, context) if (!checkLayout(content, context)) {
if (isJson == null) {
editCustomLayout(layoutName, context, content) editCustomLayout(layoutName, context, content)
infoDialog(context, context.getString(R.string.layout_error, Log.getLog(10).lastOrNull { it.tag == TAG }?.message)) infoDialog(context, context.getString(R.string.layout_error, Log.getLog(10).lastOrNull { it.tag == TAG }?.message))
} else { } else {
val wasJson = file.name.substringAfterLast(".") == "json"
file.parentFile?.mkdir() file.parentFile?.mkdir()
file.writeText(content) file.writeText(content)
if (isJson != wasJson) {
// unlikely to be needed
// actually does not work properly (need to restart the app)
// todo: can we just remove the json and txt endings and determine type using trc/catch?
// now we cache the layouts, so a bit slower reading should be fine
val newEnding = if (isJson) ".json" else ".txt"
file.renameTo(File(file.absolutePath.substringBeforeLast(".") + newEnding))
val newLayoutName = layoutName.substringBeforeLast(".") + newEnding
val prefs = DeviceProtectedUtils.getSharedPreferences(context)
val subtypesString = Settings.readPrefAdditionalSubtypes(prefs, context.resources)
val newSubtypesString = subtypesString.replace(layoutName, newLayoutName)
Settings.writePrefAdditionalSubtypes(prefs, newSubtypesString)
}
onCustomLayoutFileListChanged() onCustomLayoutFileListChanged()
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(context) KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(context)
} }