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,14 +96,17 @@ object RawKeyboardParser {
context.assets.open("layouts${File.separator}$name").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)) {
try {
val florisKeyData = parseJsonString(layoutText)
return { params ->
florisKeyData.mapTo(mutableListOf()) { row ->
row.mapNotNullTo(mutableListOf()) { it.compute(params) }
}
}
} else {
} catch (_: Exception) { }
}
// not a json, or invalid json
val simpleKeyData = parseSimpleString(layoutText)
return { params ->
simpleKeyData.mapIndexedTo(mutableListOf()) { i, row ->
@ -118,7 +121,6 @@ object RawKeyboardParser {
}
}
}
}
private fun getLayoutName(params: KeyboardParams, context: Context) = when (params.mId.mElementId) {
KeyboardId.ELEMENT_SYMBOLS -> if (params.mId.locale.script() == ScriptUtils.SCRIPT_ARABIC) LAYOUT_SYMBOLS_ARABIC else LAYOUT_SYMBOLS

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.DeviceProtectedUtils
import helium314.keyboard.latin.utils.DictionaryInfoUtils
import helium314.keyboard.latin.utils.Log
import helium314.keyboard.latin.utils.ToolbarKey
import helium314.keyboard.latin.utils.defaultPinnedToolbarPref
import helium314.keyboard.latin.utils.getCustomLayoutFile
import helium314.keyboard.latin.utils.getCustomLayoutFiles
import helium314.keyboard.latin.utils.onCustomLayoutFileListChanged
import helium314.keyboard.latin.utils.upgradeToolbarPrefs
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)
onCustomLayoutFileListChanged() // just to be sure
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 backupFilePatterns by lazy { listOf(
"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(),
"UserHistoryDictionary.*/UserHistoryDictionary.*\\.(body|header)".toRegex(),
"custom_background_image.*".toRegex(),
@ -190,7 +190,7 @@ class AdvancedSettingsFragment : SubScreenFragment() {
?.let { requireContext().assets.open("layouts" + File.separator + it).reader().readText() }
}
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() {
@ -216,7 +216,7 @@ class AdvancedSettingsFragment : SubScreenFragment() {
requireContext().assets.open("layouts" + File.separator + defaultLayoutName).reader().readText()
}
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")

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) {
var name = layoutName
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}"))
if (!checkLayout(layoutContent, context))
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)
.setTitle(R.string.title_layout_name_select)
@ -66,7 +68,7 @@ fun loadCustomLayout(layoutContent: String, layoutName: String, languageTag: Str
})
.setPositiveButton(android.R.string.ok) { _, _ ->
// 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)
if (file.exists())
file.delete()
@ -77,28 +79,23 @@ fun loadCustomLayout(layoutContent: String, layoutName: String, languageTag: Str
.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()
params.mId = KeyboardLayoutSet.getFakeKeyboardId(KeyboardId.ELEMENT_ALPHABET)
params.mPopupKeyTypes.add(POPUP_KEYS_LAYOUT)
addLocaleKeyTextsToParams(context, params, POPUP_KEYS_NORMAL)
try {
val keys = RawKeyboardParser.parseJsonString(layoutContent).map { row -> row.mapNotNull { it.compute(params)?.toKeyParams(params) } }
if (!checkKeys(keys))
return null
return true
return checkKeys(keys)
} catch (e: SerializationException) {
Log.w(TAG, "json parsing error", e)
} catch (e: Exception) {
Log.w(TAG, "json layout parsed, but considered invalid", e)
return null
return false
}
try {
val keys = RawKeyboardParser.parseSimpleString(layoutContent).map { row -> row.map { it.toKeyParams(params) } }
if (!checkKeys(keys))
return null
return false
return checkKeys(keys)
} catch (e: Exception) { Log.w(TAG, "error parsing custom simple layout", e) }
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
@ -106,7 +103,7 @@ private fun checkLayout(layoutContent: String, context: Context): Boolean? {
RawKeyboardParser.parseJsonString(layoutContent).map { row -> row.mapNotNull { it.compute(params)?.toKeyParams(params) } }
} catch (e: Exception) { Log.w(TAG, "json parsing error", e) }
}
return null
return false
}
fun checkKeys(keys: List<List<Key.KeyParams>>): Boolean {
@ -190,27 +187,12 @@ fun editCustomLayout(layoutName: String, context: Context, startContent: String?
.setView(editText)
.setPositiveButton(R.string.save) { _, _ ->
val content = editText.text.toString()
val isJson = checkLayout(content, context)
if (isJson == null) {
if (!checkLayout(content, context)) {
editCustomLayout(layoutName, context, content)
infoDialog(context, context.getString(R.string.layout_error, Log.getLog(10).lastOrNull { it.tag == TAG }?.message))
} else {
val wasJson = file.name.substringAfterLast(".") == "json"
file.parentFile?.mkdir()
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()
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(context)
}