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