RemoteFileHandleManager.kt (5618B) - raw


      1 package me.rhunk.snapenhance
      2 
      3 import android.os.ParcelFileDescriptor
      4 import me.rhunk.snapenhance.bridge.storage.FileHandle
      5 import me.rhunk.snapenhance.bridge.storage.FileHandleManager
      6 import me.rhunk.snapenhance.common.bridge.FileHandleScope
      7 import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
      8 import me.rhunk.snapenhance.common.bridge.wrapper.LocaleWrapper
      9 import me.rhunk.snapenhance.common.logger.AbstractLogger
     10 import me.rhunk.snapenhance.common.util.ktx.toParcelFileDescriptor
     11 import me.rhunk.snapenhance.storage.getEnabledThemesContent
     12 import java.io.File
     13 import java.io.OutputStream
     14 
     15 
     16 class ByteArrayFileHandle(
     17     private val context: RemoteSideContext,
     18     private val data: ByteArray
     19 ): FileHandle.Stub() {
     20     override fun exists() = true
     21     override fun create() = false
     22     override fun delete() = false
     23 
     24     override fun open(mode: Int): ParcelFileDescriptor? {
     25         return runCatching {
     26             data.inputStream().toParcelFileDescriptor(context.coroutineScope)
     27         }.onFailure {
     28             context.log.error("Failed to open byte array file handle: ${it.message}", it)
     29         }.getOrNull()
     30     }
     31 }
     32 
     33 class LocalFileHandle(
     34     private val file: File
     35 ): FileHandle.Stub() {
     36     override fun exists() = file.exists()
     37     override fun create() = file.createNewFile()
     38     override fun delete() = file.delete()
     39 
     40     override fun open(mode: Int): ParcelFileDescriptor? {
     41         return runCatching {
     42             ParcelFileDescriptor.open(file, mode)
     43         }.onFailure {
     44             AbstractLogger.directError("Failed to open file handle: ${it.message}", it)
     45         }.getOrNull()
     46     }
     47 }
     48 
     49 class AssetFileHandle(
     50     private val context: RemoteSideContext,
     51     private val assetPath: String
     52 ): FileHandle.Stub() {
     53     override fun exists() = true
     54     override fun create() = false
     55     override fun delete() = false
     56 
     57     override fun open(mode: Int): ParcelFileDescriptor? {
     58         return runCatching {
     59             context.androidContext.assets.open(assetPath).toParcelFileDescriptor(context.coroutineScope)
     60         }.onFailure {
     61             context.log.error("Failed to open asset handle: ${it.message}", it)
     62         }.getOrNull()
     63     }
     64 }
     65 
     66 
     67 class RemoteFileHandleManager(
     68     private val context: RemoteSideContext
     69 ): FileHandleManager.Stub() {
     70     private val userImportFolder = File(context.androidContext.filesDir, "user_imports").apply {
     71         mkdirs()
     72     }
     73 
     74     override fun getFileHandle(scope: String, name: String): FileHandle? {
     75         val fileHandleScope = FileHandleScope.fromValue(scope) ?: run {
     76             context.log.error("invalid file handle scope: $scope", "FileHandleManager")
     77             return null
     78         }
     79         when (fileHandleScope) {
     80             FileHandleScope.INTERNAL -> {
     81                 val fileHandleType = InternalFileHandleType.fromValue(name) ?: run {
     82                     context.log.error("invalid file handle name: $name", "FileHandleManager")
     83                     return null
     84                 }
     85 
     86                 return LocalFileHandle(
     87                     fileHandleType.resolve(context.androidContext)
     88                 )
     89             }
     90             FileHandleScope.LOCALE -> {
     91                 val foundLocale = context.androidContext.resources.assets.list("lang")?.firstOrNull {
     92                     it.startsWith(name)
     93                 }?.substringBefore(".") ?: return null
     94 
     95                 if (name == LocaleWrapper.DEFAULT_LOCALE) {
     96                     return AssetFileHandle(
     97                         context,
     98                         "lang/${LocaleWrapper.DEFAULT_LOCALE}.json"
     99                     )
    100                 }
    101 
    102                 return AssetFileHandle(
    103                     context,
    104                     "lang/$foundLocale.json"
    105                 )
    106             }
    107             FileHandleScope.USER_IMPORT -> {
    108                 return LocalFileHandle(
    109                     File(userImportFolder, name.substringAfterLast("/"))
    110                 )
    111             }
    112             FileHandleScope.COMPOSER -> {
    113                 return AssetFileHandle(
    114                     context,
    115                     "composer/${name.substringAfterLast("/")}"
    116                 )
    117             }
    118             FileHandleScope.THEME -> {
    119                 return ByteArrayFileHandle(
    120                     context,
    121                     context.gson.toJson(context.database.getEnabledThemesContent()).toByteArray(Charsets.UTF_8)
    122                 )
    123             }
    124             else -> return null
    125         }
    126     }
    127 
    128     fun getStoredFiles(filter: ((File) -> Boolean)? = null): List<File> {
    129         return userImportFolder.listFiles()?.toList()?.let { files ->
    130             filter?.let { files.filter(it) } ?: files
    131         }?.sortedBy { -it.lastModified() } ?: emptyList()
    132     }
    133 
    134     fun getFileInfo(name: String): Pair<Long, Long>? {
    135         return runCatching {
    136             val file = File(userImportFolder, name)
    137             file.length() to file.lastModified()
    138         }.onFailure {
    139             context.log.error("Failed to get file info: ${it.message}", it)
    140         }.getOrNull()
    141     }
    142 
    143     fun importFile(name: String, block: OutputStream.() -> Unit): Boolean {
    144         return runCatching {
    145             val file = File(userImportFolder, name)
    146             file.outputStream().use(block)
    147             true
    148         }.onFailure {
    149             context.log.error("Failed to import file: ${it.message}", it)
    150         }.getOrDefault(false)
    151     }
    152 
    153     fun deleteFile(name: String): Boolean {
    154         return runCatching {
    155             File(userImportFolder, name).delete()
    156         }.onFailure {
    157             context.log.error("Failed to delete file: ${it.message}", it)
    158         }.isSuccess
    159     }
    160 }