0
0
mirror of https://github.com/florisboard/florisboard.git synced 2024-09-19 19:42:20 +02:00

Remove AssetManager and switch to extension functions

This commit is contained in:
lm41 2024-06-26 20:45:01 +02:00
parent f780ef0213
commit e1550d813b
No known key found for this signature in database
6 changed files with 128 additions and 139 deletions

View File

@ -40,7 +40,6 @@ import dev.patrickgold.florisboard.lib.devtools.Flog
import dev.patrickgold.florisboard.lib.devtools.LogTopic
import dev.patrickgold.florisboard.lib.devtools.flogError
import dev.patrickgold.florisboard.lib.ext.ExtensionManager
import dev.patrickgold.florisboard.lib.io.AssetManager
import dev.patrickgold.florisboard.lib.io.deleteContentsRecursively
import dev.patrickgold.jetpref.datastore.JetPref
import org.florisboard.lib.kotlin.tryOrNull
@ -67,7 +66,6 @@ class FlorisApplication : Application() {
private val prefs by florisPreferenceModel()
private val mainHandler by lazy { Handler(mainLooper) }
val assetManager = lazy { AssetManager(this) }
val cacheManager = lazy { CacheManager(this) }
val clipboardManager = lazy { ClipboardManager(this) }
val editorInstance = lazy { EditorInstance(this) }
@ -144,8 +142,6 @@ private tailrec fun Context.florisApplication(): FlorisApplication {
fun Context.appContext() = lazyOf(this.florisApplication())
fun Context.assetManager() = this.florisApplication().assetManager
fun Context.cacheManager() = this.florisApplication().cacheManager
fun Context.clipboardManager() = this.florisApplication().clipboardManager

View File

@ -28,12 +28,12 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.sp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.assetManager
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.florisHorizontalScroll
import dev.patrickgold.florisboard.lib.compose.florisVerticalScroll
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.io.FlorisRef
import dev.patrickgold.florisboard.lib.io.loadTextAsset
@Composable
fun ProjectLicenseScreen() = FlorisScreen {
@ -41,7 +41,6 @@ fun ProjectLicenseScreen() = FlorisScreen {
scrollable = false
val context = LocalContext.current
val assetManager by context.assetManager()
content {
// Forcing LTR because the Apache 2.0 License shipped and displayed
@ -54,8 +53,8 @@ fun ProjectLicenseScreen() = FlorisScreen {
.florisVerticalScroll()
.florisHorizontalScroll(),
) {
val licenseText = assetManager.loadTextAsset(
FlorisRef.assets("license/project_license.txt")
val licenseText = FlorisRef.assets("license/project_license.txt").loadTextAsset(
context
).getOrElse {
stringRes(R.string.about__project_license__error_license_text_failed, "error_message" to (it.message ?: ""))
}

View File

@ -19,7 +19,6 @@ package dev.patrickgold.florisboard.ime.keyboard
import android.content.Context
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.appContext
import dev.patrickgold.florisboard.assetManager
import dev.patrickgold.florisboard.extensionManager
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.popup.PopupMapping
@ -34,6 +33,7 @@ import dev.patrickgold.florisboard.lib.devtools.flogDebug
import dev.patrickgold.florisboard.lib.devtools.flogWarning
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
import dev.patrickgold.florisboard.lib.io.ZipUtils
import dev.patrickgold.florisboard.lib.io.loadJsonAsset
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
@ -69,7 +69,6 @@ private data class CachedPopupMapping(
class LayoutManager(context: Context) {
private val prefs by florisPreferenceModel()
private val appContext by context.appContext()
private val assetManager by context.assetManager()
private val extensionManager by context.extensionManager()
private val keyboardManager by context.keyboardManager()
@ -101,7 +100,7 @@ class LayoutManager(context: Context) {
val layout = async {
runCatching {
val jsonStr = ZipUtils.readFileFromArchive(appContext, ext.sourceRef!!, path).getOrThrow()
val arrangement = assetManager.loadJsonAsset<LayoutArrangement>(jsonStr).getOrThrow()
val arrangement = loadJsonAsset<LayoutArrangement>(jsonStr).getOrThrow()
CachedLayout(ltn.type, ltn.name, meta, arrangement)
}
}
@ -128,7 +127,7 @@ class LayoutManager(context: Context) {
val popupMapping = async {
runCatching {
val jsonStr = ZipUtils.readFileFromArchive(appContext, ext.sourceRef!!, path).getOrThrow()
val mapping = assetManager.loadJsonAsset<PopupMapping>(jsonStr).getOrThrow()
val mapping = loadJsonAsset<PopupMapping>(jsonStr).getOrThrow()
CachedPopupMapping(name, meta, mapping)
}
}

View File

@ -22,7 +22,6 @@ import android.os.FileObserver
import androidx.compose.runtime.Composable
import androidx.lifecycle.LiveData
import dev.patrickgold.florisboard.appContext
import dev.patrickgold.florisboard.assetManager
import dev.patrickgold.florisboard.ime.keyboard.KeyboardExtension
import dev.patrickgold.florisboard.ime.nlp.LanguagePackExtension
import dev.patrickgold.florisboard.ime.text.composing.Appender
@ -38,6 +37,10 @@ import dev.patrickgold.florisboard.lib.devtools.flogError
import dev.patrickgold.florisboard.lib.io.FlorisRef
import dev.patrickgold.florisboard.lib.io.FsFile
import dev.patrickgold.florisboard.lib.io.ZipUtils
import dev.patrickgold.florisboard.lib.io.delete
import dev.patrickgold.florisboard.lib.io.listDirs
import dev.patrickgold.florisboard.lib.io.listFiles
import dev.patrickgold.florisboard.lib.io.loadJsonAsset
import dev.patrickgold.florisboard.lib.io.writeJson
import dev.patrickgold.florisboard.lib.observeAsNonNullState
import kotlinx.coroutines.CoroutineScope
@ -88,7 +91,6 @@ class ExtensionManager(context: Context) {
}
private val appContext by context.appContext()
private val assetManager by context.assetManager()
private val ioScope = CoroutineScope(Dispatchers.IO)
val keyboardExtensions = ExtensionIndex(KeyboardExtension.serializer(), IME_KEYBOARD_PATH)
@ -149,7 +151,7 @@ class ExtensionManager(context: Context) {
fun delete(ext: Extension) {
check(canDelete(ext)) { "Cannot delete extension!" }
ext.unload(appContext)
assetManager.delete(ext.sourceRef!!)
ext.sourceRef!!.delete(appContext)
}
inner class ExtensionIndex<T : Extension>(
@ -207,11 +209,11 @@ class ExtensionManager(context: Context) {
private fun indexAssetsModule(): List<T> {
val list = mutableListOf<T>()
assetManager.listDirs(assetsModuleRef).fold(
assetsModuleRef.listDirs(appContext).fold(
onSuccess = { extRefs ->
for (extRef in extRefs) {
val fileRef = extRef.subRef(ExtensionDefaults.MANIFEST_FILE_NAME)
assetManager.loadJsonAsset(fileRef, serializer, ExtensionJsonConfig).fold(
fileRef.loadJsonAsset(appContext, serializer, ExtensionJsonConfig).fold(
onSuccess = { ext ->
ext.sourceRef = extRef
list.add(ext)
@ -231,7 +233,7 @@ class ExtensionManager(context: Context) {
private fun indexInternalModule(): List<T> {
val list = mutableListOf<T>()
assetManager.listFiles(internalModuleRef).fold(
internalModuleRef.listFiles(appContext).fold(
onSuccess = { extRefs ->
for (extRef in extRefs) {
val fileRef = extRef.absoluteFile(appContext)
@ -240,7 +242,7 @@ class ExtensionManager(context: Context) {
}
ZipUtils.readFileFromArchive(appContext, extRef, ExtensionDefaults.MANIFEST_FILE_NAME).fold(
onSuccess = { metaStr ->
assetManager.loadJsonAsset(metaStr, serializer, ExtensionJsonConfig).fold(
loadJsonAsset(metaStr, serializer, ExtensionJsonConfig).fold(
onSuccess = { ext ->
ext.sourceRef = extRef
list.add(ext)

View File

@ -17,7 +17,6 @@
package dev.patrickgold.florisboard.lib.io
import android.content.Context
import dev.patrickgold.florisboard.appContext
import dev.patrickgold.florisboard.ime.keyboard.AbstractKeyData
import dev.patrickgold.florisboard.ime.keyboard.CaseSelector
import dev.patrickgold.florisboard.ime.keyboard.CharWidthSelector
@ -66,122 +65,118 @@ val DefaultJsonConfig = Json {
}
}
// TODO: fully deprecate and remove this class, should be substituted by extension funs on the actual file and stream
// instances
class AssetManager(context: Context) {
val appContext by context.appContext()
fun delete(ref: FlorisRef) {
when {
ref.isCache || ref.isInternal -> {
ref.absoluteFile(appContext).delete()
}
else -> error("Can not delete directory/file in location '${ref.scheme}'.")
fun FlorisRef.delete(context: Context) {
when {
isCache || isInternal -> {
absoluteFile(context).delete()
}
}
fun hasAsset(ref: FlorisRef): Boolean {
return when {
ref.isAssets -> {
try {
val file = File(ref.relativePath)
val list = appContext.assets.list(file.parent?.toString() ?: "")
list?.contains(file.name) == true
} catch (e: Exception) {
false
}
}
ref.isCache || ref.isInternal -> {
val file = File(ref.absolutePath(appContext))
file.exists() && file.isFile
}
else -> false
}
}
fun list(ref: FlorisRef) = list(ref, files = true, dirs = true)
fun listFiles(ref: FlorisRef) = list(ref, files = true, dirs = false)
fun listDirs(ref: FlorisRef) = list(ref, files = false, dirs = true)
private fun list(ref: FlorisRef, files: Boolean, dirs: Boolean) = runCatching<List<FlorisRef>> {
when {
!files && !dirs -> listOf()
ref.isAssets -> {
appContext.assets.list(ref.relativePath)?.mapNotNull { fileName ->
val subList = appContext.assets.list("${ref.relativePath}/$fileName") ?: return@mapNotNull null
when {
files && dirs || files && subList.isEmpty() || dirs && subList.isNotEmpty() -> {
ref.subRef(fileName)
}
else -> null
}
} ?: listOf()
}
ref.isCache || ref.isInternal -> {
val dir = ref.absoluteFile(appContext)
if (dir.isDirectory) {
when {
files && dirs -> dir.listFiles()?.toList()
files -> dir.listFiles()?.filter { it.isFile }
dirs -> dir.listFiles()?.filter { it.isDirectory }
else -> null
}!!.map { ref.subRef(it.name) }
} else {
listOf()
}
}
else -> error("Unsupported FlorisRef source!")
}
}
fun <T> loadJsonAsset(
ref: FlorisRef,
serializer: KSerializer<T>,
jsonConfig: Json = DefaultJsonConfig,
) = runCatching<T> {
val jsonStr = loadTextAsset(ref).getOrThrow()
jsonConfig.decodeFromString(serializer, jsonStr)
}
inline fun <reified T> loadJsonAsset(jsonStr: String, jsonConfig: Json = DefaultJsonConfig): Result<T> {
return runCatching { jsonConfig.decodeFromString(jsonStr) }
}
fun <T> loadJsonAsset(
jsonStr: String,
serializer: KSerializer<T>,
jsonConfig: Json = DefaultJsonConfig,
) = runCatching<T> {
jsonConfig.decodeFromString(serializer, jsonStr)
}
fun loadTextAsset(ref: FlorisRef): Result<String> {
return when {
ref.isAssets -> runCatching {
appContext.assets.reader(ref.relativePath).use { it.readText() }
}
ref.isCache || ref.isInternal -> {
val file = File(ref.absolutePath(appContext))
val contents = readTextFile(file).getOrElse { return resultErr(it) }
if (contents.isBlank()) {
resultErrStr("File is blank!")
} else {
resultOk(contents)
}
}
else -> resultErrStr("Unsupported asset ref!")
}
}
/**
* Reads a given [file] and returns its content.
*
* @param file The file object.
* @return The contents of the file or an empty string, if the file does not exist.
*/
private fun readTextFile(file: File) = runCatching {
file.readText(Charsets.UTF_8)
else -> error("Can not delete directory/file in location '${scheme}'.")
}
}
fun FlorisRef.hasAsset(context: Context): Boolean {
return when {
isAssets -> {
try {
val file = File(relativePath)
val list = context.assets.list(file.parent?.toString() ?: "")
list?.contains(file.name) == true
} catch (e: Exception) {
false
}
}
isCache || isInternal -> {
val file = File(absolutePath(context))
file.exists() && file.isFile
}
else -> false
}
}
fun FlorisRef.list(context: Context) = list(context, files = true, dirs = true)
fun FlorisRef.listFiles(context: Context) = list(context, files = true, dirs = false)
fun FlorisRef.listDirs(context: Context) = list(context, files = false, dirs = true)
private fun FlorisRef.list(appContext: Context, files: Boolean, dirs: Boolean) = runCatching<List<FlorisRef>> {
when {
!files && !dirs -> listOf()
isAssets -> {
appContext.assets.list(relativePath)?.mapNotNull { fileName ->
val subList = appContext.assets.list("${relativePath}/$fileName") ?: return@mapNotNull null
when {
files && dirs || files && subList.isEmpty() || dirs && subList.isNotEmpty() -> {
subRef(fileName)
}
else -> null
}
} ?: listOf()
}
isCache || isInternal -> {
val dir = absoluteFile(appContext)
if (dir.isDirectory) {
when {
files && dirs -> dir.listFiles()?.toList()
files -> dir.listFiles()?.filter { it.isFile }
dirs -> dir.listFiles()?.filter { it.isDirectory }
else -> null
}!!.map { subRef(it.name) }
} else {
listOf()
}
}
else -> error("Unsupported FlorisRef source!")
}
}
fun <T> FlorisRef.loadJsonAsset(
context: Context,
serializer: KSerializer<T>,
jsonConfig: Json = DefaultJsonConfig,
) = runCatching<T> {
val jsonStr = loadTextAsset(context).getOrThrow()
jsonConfig.decodeFromString(serializer, jsonStr)
}
inline fun <reified T> loadJsonAsset(jsonStr: String, jsonConfig: Json = DefaultJsonConfig): Result<T> {
return runCatching { jsonConfig.decodeFromString(jsonStr) }
}
fun <T> loadJsonAsset(
jsonStr: String,
serializer: KSerializer<T>,
jsonConfig: Json = DefaultJsonConfig,
) = runCatching<T> {
jsonConfig.decodeFromString(serializer, jsonStr)
}
fun FlorisRef.loadTextAsset(context: Context): Result<String> {
return when {
isAssets -> runCatching {
context.assets.reader(relativePath).use { it.readText() }
}
isCache || isInternal -> {
val file = File(absolutePath(context))
val contents = readTextFile(file).getOrElse { return resultErr(it) }
if (contents.isBlank()) {
resultErrStr("File is blank!")
} else {
resultOk(contents)
}
}
else -> resultErrStr("Unsupported asset ref!")
}
}
/**
* Reads a given [file] and returns its content.
*
* @param file The file object.
* @return The contents of the file or an empty string, if the file does not exist.
*/
private fun readTextFile(file: File) = runCatching {
file.readText(Charsets.UTF_8)
}

View File

@ -18,7 +18,6 @@ package dev.patrickgold.florisboard.lib.io
import android.content.Context
import android.net.Uri
import dev.patrickgold.florisboard.assetManager
import dev.patrickgold.florisboard.lib.android.copyRecursively
import dev.patrickgold.florisboard.lib.android.write
import java.io.FileOutputStream
@ -28,10 +27,9 @@ import java.util.zip.ZipOutputStream
object ZipUtils {
fun readFileFromArchive(context: Context, zipRef: FlorisRef, relPath: String) = runCatching<String> {
val assetManager by context.assetManager()
when {
zipRef.isAssets -> {
assetManager.loadTextAsset(zipRef.subRef(relPath)).getOrThrow()
zipRef.subRef(relPath).loadTextAsset(context).getOrThrow()
}
zipRef.isCache || zipRef.isInternal -> {
val flexHandle = FsFile(zipRef.absolutePath(context))