// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT package com.plugin.fs import android.annotation.SuppressLint import android.app.Activity import android.content.res.AssetManager.ACCESS_BUFFER import android.net.Uri import android.os.ParcelFileDescriptor import app.tauri.annotation.Command import app.tauri.annotation.InvokeArg import app.tauri.annotation.TauriPlugin import app.tauri.plugin.Invoke import app.tauri.plugin.JSObject import app.tauri.plugin.Plugin import java.io.File import java.io.FileOutputStream import java.io.IOException import java.io.InputStream import java.io.OutputStream @InvokeArg class WriteTextFileArgs { val uri: String = "" val content: String = "" } @InvokeArg class GetFileDescriptorArgs { lateinit var uri: String lateinit var mode: String } @TauriPlugin class FsPlugin(private val activity: Activity): Plugin(activity) { @SuppressLint("Recycle") @Command fun getFileDescriptor(invoke: Invoke) { val args = invoke.parseArgs(GetFileDescriptorArgs::class.java) val res = JSObject() if (args.uri.startsWith(app.tauri.TAURI_ASSETS_DIRECTORY_URI)) { val path = args.uri.substring(app.tauri.TAURI_ASSETS_DIRECTORY_URI.length) try { val fd = activity.assets.openFd(path).parcelFileDescriptor?.detachFd() res.put("fd", fd) } catch (e: IOException) { // if the asset is compressed, we cannot open a file descriptor directly // so we copy it to the cache and get a fd from there // this is a lot faster than serializing the file and sending it as invoke response // because on the Rust side we can leverage the custom protocol IPC and read the file directly val cacheFile = File(activity.cacheDir, "_assets/$path") cacheFile.parentFile?.mkdirs() copyAsset(path, cacheFile) val fd = ParcelFileDescriptor.open(cacheFile, ParcelFileDescriptor.parseMode(args.mode)).detachFd() res.put("fd", fd) } } else { val fd = activity.contentResolver.openAssetFileDescriptor( Uri.parse(args.uri), args.mode )?.parcelFileDescriptor?.detachFd() res.put("fd", fd) } invoke.resolve(res) } @Throws(IOException::class) private fun copy(input: InputStream, output: OutputStream) { val buf = ByteArray(1024) var len: Int while ((input.read(buf).also { len = it }) > 0) { output.write(buf, 0, len) } } @Throws(IOException::class) private fun copyAsset(assetPath: String, cacheFile: File) { val input = activity.assets.open(assetPath, ACCESS_BUFFER) input.use { i -> val output = FileOutputStream(cacheFile, false) output.use { o -> copy(i, o) } } } }