refactor: file handle manager
This commit is contained in:
@@ -10,30 +10,18 @@ import me.rhunk.snapenhance.bridge.logger.TrackerInterface;
|
||||
import me.rhunk.snapenhance.bridge.ConfigStateListener;
|
||||
import me.rhunk.snapenhance.bridge.snapclient.MessagingBridge;
|
||||
import me.rhunk.snapenhance.bridge.AccountStorage;
|
||||
import me.rhunk.snapenhance.bridge.storage.FileHandleManager;
|
||||
|
||||
interface BridgeInterface {
|
||||
/**
|
||||
* broadcast a log message
|
||||
*/
|
||||
oneway void broadcastLog(String tag, String level, String message);
|
||||
|
||||
/**
|
||||
* Execute a file operation
|
||||
* @param fileType the corresponding file type (see BridgeFileType)
|
||||
*/
|
||||
byte[] fileOperation(int action, int fileType, in @nullable byte[] content);
|
||||
|
||||
/**
|
||||
* Get the application APK path (assets for the conversation exporter)
|
||||
*/
|
||||
String getApplicationApkPath();
|
||||
|
||||
/**
|
||||
* Fetch the locales
|
||||
*
|
||||
* @return the map of locales (key: locale short name, value: locale data as json)
|
||||
*/
|
||||
Map<String, ParcelFileDescriptor> fetchLocales(String userLocale);
|
||||
* broadcast a log message
|
||||
*/
|
||||
oneway void broadcastLog(String tag, String level, String message);
|
||||
|
||||
/**
|
||||
* Enqueue a download
|
||||
@@ -92,6 +80,8 @@ interface BridgeInterface {
|
||||
|
||||
AccountStorage getAccountStorage();
|
||||
|
||||
FileHandleManager getFileHandleManager();
|
||||
|
||||
oneway void registerMessagingBridge(MessagingBridge bridge);
|
||||
|
||||
oneway void openSettingsOverlay();
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package me.rhunk.snapenhance.bridge.storage;
|
||||
|
||||
interface FileHandle {
|
||||
boolean exists();
|
||||
boolean create();
|
||||
boolean delete();
|
||||
|
||||
@nullable ParcelFileDescriptor open(int mode);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package me.rhunk.snapenhance.bridge.storage;
|
||||
|
||||
import me.rhunk.snapenhance.bridge.storage.FileHandle;
|
||||
|
||||
interface FileHandleManager {
|
||||
@nullable FileHandle getFileHandle(String scope, String name);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package me.rhunk.snapenhance.common.bridge
|
||||
|
||||
import android.content.Context
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.os.ParcelFileDescriptor.AutoCloseInputStream
|
||||
import android.os.ParcelFileDescriptor.AutoCloseOutputStream
|
||||
import me.rhunk.snapenhance.bridge.storage.FileHandle
|
||||
import java.io.File
|
||||
|
||||
|
||||
enum class FileHandleScope(
|
||||
val key: String
|
||||
) {
|
||||
INTERNAL("internal"),
|
||||
LOCALE("locale");
|
||||
|
||||
companion object {
|
||||
fun fromValue(name: String): FileHandleScope? = entries.find { it.key == name }
|
||||
}
|
||||
}
|
||||
|
||||
enum class InternalFileHandleType(
|
||||
val key: String,
|
||||
val fileName: String,
|
||||
val isDatabase: Boolean = false
|
||||
) {
|
||||
CONFIG("config", "config.json"),
|
||||
MAPPINGS("mappings", "mappings.json"),
|
||||
MESSAGE_LOGGER("message_logger", "message_logger.db", isDatabase = true),
|
||||
SUSPEND_LOCATION_STATE("suspend_location_state", "suspend_location_state.txt"),
|
||||
PINNED_BEST_FRIEND("pinned_best_friend", "pinned_best_friend.txt");
|
||||
|
||||
|
||||
fun resolve(context: Context): File = if (isDatabase) {
|
||||
context.getDatabasePath(fileName)
|
||||
} else {
|
||||
File(context.filesDir, fileName)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromValue(name: String): InternalFileHandleType? = entries.find { it.key == name }
|
||||
}
|
||||
}
|
||||
|
||||
fun FileHandle.toWrapper() = FileHandleWrapper(lazy { this })
|
||||
|
||||
open class FileHandleWrapper(
|
||||
private val fileHandle: Lazy<FileHandle>
|
||||
) {
|
||||
fun exists() = fileHandle.value.exists()
|
||||
fun create() = fileHandle.value.create()
|
||||
fun delete() = fileHandle.value.delete()
|
||||
|
||||
fun writeBytes(data: ByteArray) = fileHandle.value.open(
|
||||
ParcelFileDescriptor.MODE_WRITE_ONLY or
|
||||
ParcelFileDescriptor.MODE_CREATE or
|
||||
ParcelFileDescriptor.MODE_TRUNCATE
|
||||
).use { pfd ->
|
||||
AutoCloseOutputStream(pfd).use {
|
||||
it.write(data)
|
||||
}
|
||||
}
|
||||
|
||||
open fun readBytes(): ByteArray = fileHandle.value.open(
|
||||
ParcelFileDescriptor.MODE_READ_ONLY or
|
||||
ParcelFileDescriptor.MODE_CREATE
|
||||
).use { pfd ->
|
||||
AutoCloseInputStream(pfd).use {
|
||||
it.readBytes()
|
||||
}
|
||||
}
|
||||
|
||||
fun inputStream(block: (AutoCloseInputStream) -> Unit) = fileHandle.value.open(
|
||||
ParcelFileDescriptor.MODE_READ_ONLY or
|
||||
ParcelFileDescriptor.MODE_CREATE
|
||||
).use { pfd ->
|
||||
AutoCloseInputStream(pfd).use {
|
||||
block(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun outputStream(block: (AutoCloseOutputStream) -> Unit) = fileHandle.value.open(
|
||||
ParcelFileDescriptor.MODE_WRITE_ONLY or
|
||||
ParcelFileDescriptor.MODE_CREATE or
|
||||
ParcelFileDescriptor.MODE_TRUNCATE
|
||||
).use { pfd ->
|
||||
AutoCloseOutputStream(pfd).use {
|
||||
block(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package me.rhunk.snapenhance.common.bridge
|
||||
|
||||
import android.content.Context
|
||||
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType
|
||||
|
||||
open class FileLoaderWrapper(
|
||||
val fileType: BridgeFileType,
|
||||
val defaultContent: ByteArray
|
||||
) {
|
||||
lateinit var isFileExists: () -> Boolean
|
||||
lateinit var write: (ByteArray) -> Unit
|
||||
lateinit var read: () -> ByteArray
|
||||
lateinit var delete: () -> Unit
|
||||
|
||||
fun loadFromContext(context: Context) {
|
||||
val file = fileType.resolve(context)
|
||||
isFileExists = { file.exists() }
|
||||
read = {
|
||||
if (!file.exists()) {
|
||||
file.createNewFile()
|
||||
file.writeBytes("{}".toByteArray(Charsets.UTF_8))
|
||||
}
|
||||
file.readBytes()
|
||||
}
|
||||
write = { file.writeBytes(it) }
|
||||
delete = { file.delete() }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package me.rhunk.snapenhance.common.bridge
|
||||
|
||||
import me.rhunk.snapenhance.bridge.storage.FileHandleManager
|
||||
|
||||
open class InternalFileWrapper(
|
||||
fileHandleManager: FileHandleManager,
|
||||
private val fileType: InternalFileHandleType,
|
||||
val defaultValue: String? = null
|
||||
): FileHandleWrapper(lazy { fileHandleManager.getFileHandle(FileHandleScope.INTERNAL.key, fileType.key)!! }) {
|
||||
override fun readBytes(): ByteArray {
|
||||
val bytes = super.readBytes()
|
||||
if (bytes.isEmpty()) {
|
||||
defaultValue?.let { writeBytes(it.toByteArray()) }
|
||||
}
|
||||
return super.readBytes()
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package me.rhunk.snapenhance.common.bridge.types
|
||||
|
||||
import android.content.Context
|
||||
import java.io.File
|
||||
|
||||
|
||||
enum class BridgeFileType(val value: Int, val fileName: String, val displayName: String, private val isDatabase: Boolean = false) {
|
||||
CONFIG(0, "config.json", "Config"),
|
||||
MAPPINGS(1, "mappings.json", "Mappings"),
|
||||
MESSAGE_LOGGER_DATABASE(2, "message_logger.db", "Message Logger",true),
|
||||
PINNED_CONVERSATIONS(3, "pinned_conversations.txt", "Pinned Conversations"),
|
||||
SUSPEND_LOCATION_STATE(4, "suspend_location_state.txt", "Suspend Location State"),
|
||||
PINNED_BEST_FRIEND(5, "pinned_best_friend.txt", "Pinned Best Friend");
|
||||
|
||||
fun resolve(context: Context): File = if (isDatabase) {
|
||||
context.getDatabasePath(fileName)
|
||||
} else {
|
||||
File(context.filesDir, fileName)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromValue(value: Int): BridgeFileType? {
|
||||
return entries.firstOrNull { it.value == value }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package me.rhunk.snapenhance.common.bridge.types
|
||||
|
||||
enum class FileActionType {
|
||||
CREATE_AND_READ, READ, WRITE, DELETE, EXISTS
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package me.rhunk.snapenhance.common.bridge.types
|
||||
|
||||
import android.os.ParcelFileDescriptor
|
||||
import java.util.Locale
|
||||
|
||||
data class LocalePair(
|
||||
val locale: String,
|
||||
val content: ParcelFileDescriptor
|
||||
) {
|
||||
fun getLocale(): Locale {
|
||||
if (locale.contains("_")) {
|
||||
val split = locale.split("_")
|
||||
return Locale(split[0], split[1])
|
||||
}
|
||||
return Locale(locale)
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,22 @@
|
||||
package me.rhunk.snapenhance.common.bridge.wrapper
|
||||
|
||||
import android.content.Context
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.os.ParcelFileDescriptor.AutoCloseInputStream
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import me.rhunk.snapenhance.common.bridge.types.LocalePair
|
||||
import me.rhunk.snapenhance.bridge.storage.FileHandleManager
|
||||
import me.rhunk.snapenhance.common.bridge.FileHandleScope
|
||||
import me.rhunk.snapenhance.common.logger.AbstractLogger
|
||||
import me.rhunk.snapenhance.common.util.ktx.toParcelFileDescriptor
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
class LocaleWrapper {
|
||||
class LocaleWrapper(
|
||||
private val fileHandleManager: FileHandleManager
|
||||
) {
|
||||
companion object {
|
||||
const val DEFAULT_LOCALE = "en_US"
|
||||
|
||||
fun fetchLocales(context: Context, locale: String = DEFAULT_LOCALE): List<LocalePair> {
|
||||
val coroutineScope = CoroutineScope(Dispatchers.IO)
|
||||
val locales = mutableListOf<LocalePair>().apply {
|
||||
add(LocalePair(DEFAULT_LOCALE, context.resources.assets.open("lang/$DEFAULT_LOCALE.json").toParcelFileDescriptor(coroutineScope)))
|
||||
}
|
||||
|
||||
if (locale == DEFAULT_LOCALE) return locales
|
||||
|
||||
val compatibleLocale = context.resources.assets.list("lang")?.firstOrNull { it.startsWith(locale) }?.substringBefore(".") ?: return locales
|
||||
|
||||
locales.add(
|
||||
LocalePair(
|
||||
compatibleLocale,
|
||||
context.resources.assets.open("lang/$compatibleLocale.json").toParcelFileDescriptor(coroutineScope)
|
||||
)
|
||||
)
|
||||
|
||||
return locales
|
||||
}
|
||||
|
||||
fun fetchAvailableLocales(context: Context): List<String> {
|
||||
return context.resources.assets.list("lang")?.map { it.substringBefore(".") }?.sorted() ?: listOf(DEFAULT_LOCALE)
|
||||
}
|
||||
@@ -47,14 +28,23 @@ class LocaleWrapper {
|
||||
|
||||
lateinit var loadedLocale: Locale
|
||||
|
||||
private fun load(localePair: LocalePair) {
|
||||
loadedLocale = localePair.getLocale()
|
||||
private fun load(locale: String, pfd: ParcelFileDescriptor) {
|
||||
loadedLocale = if (locale.contains("_")) {
|
||||
val split = locale.split("_")
|
||||
Locale(split[0], split[1])
|
||||
} else {
|
||||
Locale(locale)
|
||||
}
|
||||
|
||||
val translations = AutoCloseInputStream(localePair.content).use {
|
||||
JsonParser.parseReader(it.reader()).asJsonObject
|
||||
val translations = AutoCloseInputStream(pfd).use {
|
||||
runCatching {
|
||||
JsonParser.parseReader(it.reader()).asJsonObject
|
||||
}.onFailure {
|
||||
AbstractLogger.directError("Failed to parse locale file: ${it.message}", it)
|
||||
}.getOrNull()
|
||||
}
|
||||
if (translations == null || translations.isJsonNull) {
|
||||
return
|
||||
throw IllegalStateException("Failed to parse $locale.json")
|
||||
}
|
||||
|
||||
fun scanObject(jsonObject: JsonObject, prefix: String = "") {
|
||||
@@ -71,22 +61,25 @@ class LocaleWrapper {
|
||||
scanObject(translations)
|
||||
}
|
||||
|
||||
fun loadFromCallback(callback: (String) -> List<LocalePair>) {
|
||||
callback(userLocale).forEach {
|
||||
load(it)
|
||||
fun load() {
|
||||
load(
|
||||
DEFAULT_LOCALE,
|
||||
fileHandleManager.getFileHandle(FileHandleScope.LOCALE.key, "$DEFAULT_LOCALE.json")?.open(ParcelFileDescriptor.MODE_READ_ONLY) ?: run {
|
||||
throw IllegalStateException("Failed to load default locale")
|
||||
}
|
||||
)
|
||||
|
||||
if (userLocale != DEFAULT_LOCALE) {
|
||||
fileHandleManager.getFileHandle(FileHandleScope.LOCALE.key, "$userLocale.json")?.open(ParcelFileDescriptor.MODE_READ_ONLY)?.let {
|
||||
load(userLocale, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadFromContext(context: Context) {
|
||||
fetchLocales(context, userLocale).forEach {
|
||||
load(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun reloadFromContext(context: Context, locale: String) {
|
||||
fun reload(locale: String) {
|
||||
userLocale = locale
|
||||
translationMap.clear()
|
||||
loadFromContext(context)
|
||||
load()
|
||||
}
|
||||
|
||||
operator fun get(key: String) = translationMap[key] ?: key.also { AbstractLogger.directDebug("Missing translation for $key") }
|
||||
@@ -99,7 +92,7 @@ class LocaleWrapper {
|
||||
}
|
||||
|
||||
fun getCategory(key: String): LocaleWrapper {
|
||||
return LocaleWrapper().apply {
|
||||
return LocaleWrapper(fileHandleManager).apply {
|
||||
translationMap.putAll(
|
||||
this@LocaleWrapper.translationMap
|
||||
.filterKeys { it.startsWith("$key.") }
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package me.rhunk.snapenhance.common.bridge.wrapper
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonObject
|
||||
import kotlinx.coroutines.*
|
||||
import me.rhunk.snapenhance.bridge.logger.LoggerInterface
|
||||
import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
|
||||
import me.rhunk.snapenhance.common.data.StoryData
|
||||
import me.rhunk.snapenhance.common.logger.AbstractLogger
|
||||
import me.rhunk.snapenhance.common.util.SQLiteDatabaseHelper
|
||||
@@ -61,6 +63,8 @@ class TrackerLog(
|
||||
class LoggerWrapper(
|
||||
val databaseFile: File
|
||||
): LoggerInterface.Stub() {
|
||||
constructor(context: Context): this(File(context.getDatabasePath(InternalFileHandleType.MESSAGE_LOGGER.fileName).absolutePath))
|
||||
|
||||
private var _database: SQLiteDatabase? = null
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.IO.limitedParallelism(1))
|
||||
|
||||
@@ -3,16 +3,19 @@ package me.rhunk.snapenhance.common.bridge.wrapper
|
||||
import android.content.Context
|
||||
import com.google.gson.JsonParser
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import me.rhunk.snapenhance.bridge.storage.FileHandleManager
|
||||
import me.rhunk.snapenhance.common.BuildConfig
|
||||
import me.rhunk.snapenhance.common.Constants
|
||||
import me.rhunk.snapenhance.common.bridge.FileLoaderWrapper
|
||||
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
|
||||
import me.rhunk.snapenhance.common.bridge.InternalFileWrapper
|
||||
import me.rhunk.snapenhance.common.logger.AbstractLogger
|
||||
import me.rhunk.snapenhance.mapper.AbstractClassMapper
|
||||
import me.rhunk.snapenhance.mapper.ClassMapper
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteArray(Charsets.UTF_8)) {
|
||||
class MappingsWrapper(
|
||||
fileHandleManager: FileHandleManager
|
||||
): InternalFileWrapper(fileHandleManager, InternalFileHandleType.MAPPINGS, defaultValue = "{}") {
|
||||
private lateinit var context: Context
|
||||
private var mappingUniqueHash: Long = 0
|
||||
var isMappingsLoaded = false
|
||||
@@ -26,7 +29,7 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr
|
||||
this.context = context
|
||||
mappingUniqueHash = getUniqueBuildId()
|
||||
|
||||
if (isFileExists()) {
|
||||
if (exists()) {
|
||||
runCatching {
|
||||
loadCached()
|
||||
}.onFailure {
|
||||
@@ -46,10 +49,10 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr
|
||||
fun isMappingsOutdated() = mappingUniqueHash != getUniqueBuildId() || isMappingsLoaded.not()
|
||||
|
||||
private fun loadCached() {
|
||||
if (!isFileExists()) {
|
||||
if (!exists()) {
|
||||
throw Exception("Mappings file does not exist")
|
||||
}
|
||||
val mappingsObject = JsonParser.parseString(read().toString(Charsets.UTF_8)).asJsonObject.also {
|
||||
val mappingsObject = JsonParser.parseString(readBytes().toString(Charsets.UTF_8)).asJsonObject.also {
|
||||
mappingUniqueHash = it["unique_hash"].asLong
|
||||
}
|
||||
|
||||
@@ -76,7 +79,7 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr
|
||||
val result = classMapper.run().apply {
|
||||
addProperty("unique_hash", mappingUniqueHash)
|
||||
}
|
||||
write(result.toString().toByteArray())
|
||||
writeBytes(result.toString().toByteArray())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,20 +5,22 @@ import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonObject
|
||||
import me.rhunk.snapenhance.bridge.ConfigStateListener
|
||||
import me.rhunk.snapenhance.common.bridge.FileLoaderWrapper
|
||||
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.bridge.storage.FileHandleManager
|
||||
import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
|
||||
import me.rhunk.snapenhance.common.bridge.InternalFileWrapper
|
||||
import me.rhunk.snapenhance.common.bridge.wrapper.LocaleWrapper
|
||||
import me.rhunk.snapenhance.common.config.impl.RootConfig
|
||||
import me.rhunk.snapenhance.common.logger.AbstractLogger
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class ModConfig(
|
||||
private val context: Context
|
||||
private val context: Context,
|
||||
fileHandleManager: FileHandleManager
|
||||
) {
|
||||
private val fileWrapper = InternalFileWrapper(fileHandleManager, InternalFileHandleType.CONFIG, "{}")
|
||||
var locale: String = LocaleWrapper.DEFAULT_LOCALE
|
||||
|
||||
private val gson: Gson = GsonBuilder().setPrettyPrinting().create()
|
||||
private val file = FileLoaderWrapper(BridgeFileType.CONFIG, "{}".toByteArray(Charsets.UTF_8))
|
||||
var wasPresent by Delegates.notNull<Boolean>()
|
||||
|
||||
/* Used to notify the bridge client about config changes */
|
||||
@@ -30,9 +32,9 @@ class ModConfig(
|
||||
|
||||
private fun createRootConfig() = RootConfig().apply { lateInit(context) }
|
||||
|
||||
private fun load() {
|
||||
fun load() {
|
||||
root = createRootConfig()
|
||||
wasPresent = file.isFileExists()
|
||||
wasPresent = fileWrapper.exists()
|
||||
if (!wasPresent) {
|
||||
writeConfig()
|
||||
return
|
||||
@@ -46,7 +48,7 @@ class ModConfig(
|
||||
}
|
||||
|
||||
private fun loadConfig() {
|
||||
val configFileContent = file.read()
|
||||
val configFileContent = fileWrapper.readBytes()
|
||||
val configObject = gson.fromJson(configFileContent.toString(Charsets.UTF_8), JsonObject::class.java)
|
||||
locale = configObject.get("_locale")?.asString ?: LocaleWrapper.DEFAULT_LOCALE
|
||||
root.fromJson(configObject)
|
||||
@@ -99,8 +101,8 @@ class ModConfig(
|
||||
}
|
||||
}
|
||||
|
||||
val oldConfig = runCatching { file.read().toString(Charsets.UTF_8) }.getOrNull()
|
||||
file.write(exportToString().toByteArray(Charsets.UTF_8))
|
||||
val oldConfig = runCatching { fileWrapper.readBytes().toString(Charsets.UTF_8) }.getOrNull()
|
||||
fileWrapper.writeBytes(exportToString().toByteArray(Charsets.UTF_8))
|
||||
|
||||
configStateListener?.also {
|
||||
runCatching {
|
||||
@@ -125,14 +127,4 @@ class ModConfig(
|
||||
root.fromJson(configObject)
|
||||
writeConfig()
|
||||
}
|
||||
|
||||
fun loadFromContext(context: Context) {
|
||||
file.loadFromContext(context)
|
||||
load()
|
||||
}
|
||||
|
||||
fun loadFromCallback(callback: (FileLoaderWrapper) -> Unit) {
|
||||
callback(file)
|
||||
load()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user