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 }