tauri-apps / plugins-workspace

All of the official Tauri plugins in one place!
https://tauri.app
Apache License 2.0
829 stars 230 forks source link

[fs] On Android I cannot extract filename returned by `open` / `pick_file(s)` #1775

Open micouy opened 6 days ago

micouy commented 6 days ago

When I select files on Android (either by open on frontend or pick_files on backend) a content:// URI is returned:

content://com.android.providers.media.documents/document/image%3A18

How do I get to the filename? Should I use some resolver or open the file some other way?

aiueo13 commented 2 days ago

Resolved? I'll make a temp plugin for you if you need it.

micouy commented 1 day ago

I got it partially working by fetching type and name while opening the file descriptor in FsPlugin.kt:

    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)
                // Get MIME type
                // val mimeType =
                //
                // activity.contentResolver.getType(Uri.parse("file:///android_asset/$path"))
                val mimeType = activity.contentResolver.getType(Uri.parse(args.uri))
                res.put("mimeType", mimeType ?: "application/octet-stream")

                // Get file name
                val fileName = Uri.parse(args.uri).lastPathSegment
                res.put("fileName", fileName)
                res.put("branch", "uncompressed asset")
            } 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)

                // Get MIME type
                val mimeType = activity.contentResolver.getType(Uri.fromFile(cacheFile))
                res.put("mimeType", mimeType ?: "application/octet-stream")

                // Get file name
                val fileName = Uri.fromFile(cacheFile).lastPathSegment
                res.put("fileName", fileName)
                res.put("branch", "compressed asset $e")
            }
        } else {
            val mimeType = activity.contentResolver.getType(Uri.parse(args.uri))
            val fileName =
                    activity.contentResolver.query(Uri.parse(args.uri), null, null, null, null)
                            ?.use { cursor ->
                                if (cursor.moveToFirst()) {
                                    val nameIndex =
                                            cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)

                                    if (nameIndex != -1) {
                                        cursor.getString(nameIndex)
                                    } else {
                                        null
                                    }
                                } else {
                                    null
                                }
                            }
                            ?: Uri.parse(args.uri).lastPathSegment
            val fd =
                    activity.contentResolver
                            .openAssetFileDescriptor(Uri.parse(args.uri), args.mode)
                            ?.parcelFileDescriptor
                            ?.detachFd()
            res.put("fd", fd)

            res.put("mimeType", mimeType)
            res.put("fileName", fileName)
            res.put("branch", "not asset")
        }

        invoke.resolve(res)
    }

However, I'm still interested if and when such functionality is going to be added to the plugin.