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 }