tauri-plugin-android-fs 28.2.1

Android file system API for Tauri.
Documentation
package com.plugin.android_fs

import android.net.Uri
import android.webkit.MimeTypeMap
import app.tauri.plugin.JSArray
import app.tauri.plugin.JSObject
import java.io.File

class RawFileController: FileController {

    override fun getMimeType(uri: AFUri): String? {
        return _getMimeType(File(Uri.parse(uri.uri).path!!))
    }

    override fun getName(uri: AFUri): String {
        return File(Uri.parse(uri.uri).path!!).name
    }

    override fun getLen(uri: AFUri): Long {
        val entry = File(Uri.parse(uri.uri).path!!)
        if (!entry.isFile) {
            throw Exception("This is not a file: ${entry.path}")
        }
        return entry.length()
    }

    override fun getLastModified(uri: AFUri): Long {
        val entry = File(Uri.parse(uri.uri).path!!)
        return entry.lastModified()
    }

    override fun readDir(dirUri: AFUri, options: ReadDirEntryOptions, offset: ULong, limit: ULong?): JSArray {
        val dir = File(Uri.parse(dirUri.uri).path!!)
        val buffer = JSArray()
        var i = 0UL

        for (file in dir.listFiles()!!) {
            if (i < offset) {
                i++
                continue
            }
            if (limit != null && limit <= (i - offset)) {
                break
            }
            i++


            val obj = JSObject()

            if (options.uri) {
                obj.put("uri", JSObject().apply {
                    put("uri", file.toURI())
                    put("documentTopTreeUri", null)
                })
            }
            if (options.name) {
                obj.put("name", file.name)
            }
            if (options.lastModified) {
                obj.put("lastModified", file.lastModified())
            }
            val mimeType = _getMimeType(file)
            if (mimeType != null) {
                obj.put("mimeType", mimeType)
                if (options.len) {
                    obj.put("len", file.length())
                }
            }
            buffer.put(obj)
        }

        return buffer
    }

    override fun getMetadata(uri: AFUri): JSObject {
        val file = File(Uri.parse(uri.uri).path!!)
        val obj = JSObject()
        obj.put("uri", JSObject().apply {
            put("uri", file.toURI())
            put("documentTopTreeUri", null)
        })
        obj.put("name", file.name)
        obj.put("lastModified", file.lastModified())

        val mimeType = _getMimeType(file)
        if (mimeType != null) {
            obj.put("mimeType", mimeType)
            obj.put("len", file.length())
        }

        return obj
    }

    // この関数が返すUriは他のアプリに共有できない
    @Synchronized
    override fun createNewFile(dirUri: AFUri, relativePath: String, mimeType: String): JSObject {
        val dir = File(Uri.parse(dirUri.uri).path!!)
        val baseFile = File(dir.path + "/" + relativePath.trimStart('/'))
        val fileName = baseFile.nameWithoutExtension
        val fileExtension = baseFile.extension
    
        var file = baseFile
        var counter = 1
    
        // 同じ名前のファイルが既に存在する場合、連番を追加してファイル名を変更
        while (file.exists()) {
            val newFileName = if (fileExtension.isEmpty()) {
                "$fileName($counter)"
            } else {
                "$fileName($counter).$fileExtension"
            }
            file = File(baseFile.parentFile, newFileName)
            counter++
        }
    
        file.parentFile?.mkdirs()
        file.createNewFile()

        val res = JSObject()
        res.put("uri", Uri.fromFile(file))
        res.put("documentTopTreeUri", null)
        return res
    }

    @Synchronized
    override fun createNewFileAndReturnRelativePath(
        dirUri: AFUri,
        relativePath: String,
        mimeType: String
    ): JSObject {

        val dir = File(Uri.parse(dirUri.uri).path!!)
        val baseFile = File(dir.path + "/" + relativePath.trimStart('/'))
        val fileName = baseFile.nameWithoutExtension
        val fileExtension = baseFile.extension

        var file = baseFile
        var counter = 1
        var actualRelativePath = relativePath

        // 同じ名前のファイルが既に存在する場合、連番を追加してファイル名を変更
        while (file.exists()) {
            val newFileName = if (fileExtension.isEmpty()) {
                "$fileName($counter)"
            } else {
                "$fileName($counter).$fileExtension"
            }
            file = File(baseFile.parentFile, newFileName)
            actualRelativePath = file.absolutePath
            counter++
        }

        file.parentFile?.mkdirs()
        file.createNewFile()

        return JSObject().apply {
            put("relativePath", actualRelativePath)
            put("uri", JSObject().apply {
                put("uri", Uri.fromFile(file))
                put("documentTopTreeUri", null)
            })
        }
    }

    @Synchronized
    override fun createNewDir(dirUri: AFUri, relativePath: String): JSObject {
        val parentDir = File(Uri.parse(dirUri.uri).path!!)
        val baseDir = File(parentDir.path + "/" + relativePath.trimStart('/'))
        val dirName = baseDir.name

        var dir = baseDir
        var counter = 1

        // 同じ名前のファイルが既に存在する場合、連番を追加してファイル名を変更
        while (dir.exists()) {
            val newFileName = "$dirName($counter)"
            dir = File(baseDir.parentFile, newFileName)
            counter++
        }

        dir.mkdirs()

        val res = JSObject()
        res.put("uri", Uri.fromFile(dir))
        res.put("documentTopTreeUri", null)
        return res
    }

    @Synchronized
    override fun createNewDirAndReturnRelativePath(
        dirUri: AFUri,
        relativePath: String,
    ): JSObject {

        val dir = File(Uri.parse(dirUri.uri).path!!)
        val baseFile = File(dir.path + "/" + relativePath.trimStart('/'))
        val fileName = baseFile.name

        var file = baseFile
        var counter = 1
        var actualRelativePath = relativePath

        // 同じ名前のファイルが既に存在する場合、連番を追加してファイル名を変更
        while (file.exists()) {
            val newFileName = "$fileName($counter)"
            file = File(baseFile.parentFile, newFileName)
            actualRelativePath = file.absolutePath
            counter++
        }

        file.mkdirs()

        return JSObject().apply {
            put("relativePath", actualRelativePath)
            put("uri", JSObject().apply {
                put("uri", Uri.fromFile(file))
                put("documentTopTreeUri", null)
            })
        }
    }

    @Synchronized
    override fun createDirAll(dirUri: AFUri, relativePath: String): JSObject {
        val parentPath = Uri.parse(dirUri.uri).path!!.trimEnd('/')
        val dir = File(parentPath + "/" + relativePath.trimStart('/'))
        dir.mkdirs()

        val res = JSObject()
        res.put("uri", Uri.fromFile(dir))
        res.put("documentTopTreeUri", null)
        return res
    }

    @Synchronized
    override fun createDirAllAndReturnRelativePath(dirUri: AFUri, relativePath: String): JSObject {
        val parentPath = Uri.parse(dirUri.uri).path!!.trimEnd('/')
        val dir = File(parentPath + "/" + relativePath.trimStart('/'))
        dir.mkdirs()

        return JSObject().apply {
            put("relativePath", relativePath)
            put("uri", JSObject().apply {
                put("uri", Uri.fromFile(dir))
                put("documentTopTreeUri", null)
            })
        }
    }

    override fun deleteFile(uri: AFUri) {
        val file = File(Uri.parse(uri.uri).path!!)
        if (!file.isFile) {
            throw Exception("This is not file: ${uri.uri}")
        }
        if (!file.delete()) {
            throw Exception("Failed to delete file: ${uri.uri}")
        }
    }

    override fun deleteEmptyDir(uri: AFUri) {
        val file = File(Uri.parse(uri.uri).path!!)
        if (!file.isDirectory) {
            throw Exception("This is not dir: ${uri.uri}")
        }
        if (!file.delete()) {
            throw Exception("Failed to delete file: ${uri.uri}")
        }
    }

    override fun deleteDirAll(uri: AFUri) {
        val file = File(Uri.parse(uri.uri).path!!)
        if (!file.isDirectory) {
            throw Exception("This is not dir: ${uri.uri}")
        }
        
        if (!deleteRecursive(file)) {
            throw Exception("Failed to delete file: ${uri.uri}")
        }
    }

    override fun rename(uri: AFUri, newName: String): JSObject {
        val file = File(Uri.parse(uri.uri).path!!)
        val newFile = File(file.parentFile, newName)

        if (newFile.exists()) {
            throw Exception("File already exists: ${newFile.path}")
        }

        if (!file.renameTo(newFile)) {
            throw Exception("Failed to rename file: ${uri.uri}")
        }

        val res = JSObject()
        res.put("uri", Uri.fromFile(newFile).toString())
        res.put("documentTopTreeUri", uri.documentTopTreeUri)
        return res
    }

    private fun deleteRecursive(fileOrDirectory: File): Boolean {
        if (fileOrDirectory.isDirectory) {
            val children = fileOrDirectory.listFiles()
            if (children != null) {
                for (child in children) {
                    deleteRecursive(child)
                }
            }
        }
        return fileOrDirectory.delete()
    }

    // フォルダの場合のみnullを返す
    private fun _getMimeType(file: File): String? {
        if (file.isDirectory) {
            return null
        }

        return MimeTypeMap
            .getSingleton()
            .getMimeTypeFromExtension(file.extension)
            ?: "application/octet-stream"
    }
}