From 7b54c2b2e25adb81c95d54941f55bae76770cdb9 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 11 Mar 2023 00:37:04 -0500 Subject: [PATCH] android: Convert FileUtil to Kotlin --- .../org/yuzu/yuzu_emu/utils/FileUtil.java | 296 ------------------ .../java/org/yuzu/yuzu_emu/utils/FileUtil.kt | 292 +++++++++++++++++ 2 files changed, 292 insertions(+), 296 deletions(-) delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.java create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.java deleted file mode 100644 index 8665704cc4..0000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.java +++ /dev/null @@ -1,296 +0,0 @@ -package org.yuzu.yuzu_emu.utils; - -import android.content.ContentResolver; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.provider.DocumentsContract; - -import androidx.annotation.Nullable; -import androidx.documentfile.provider.DocumentFile; - -import org.yuzu.yuzu_emu.model.MinimalDocumentFile; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URLDecoder; -import java.util.ArrayList; -import java.util.List; - -public class FileUtil { - static final String PATH_TREE = "tree"; - static final String DECODE_METHOD = "UTF-8"; - static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; - static final String TEXT_PLAIN = "text/plain"; - - /** - * Create a file from directory with filename. - * @param context Application context - * @param directory parent path for file. - * @param filename file display name. - * @return boolean - */ - @Nullable - public static DocumentFile createFile(Context context, String directory, String filename) { - try { - Uri directoryUri = Uri.parse(directory); - DocumentFile parent = DocumentFile.fromTreeUri(context, directoryUri); - if (parent == null) return null; - filename = URLDecoder.decode(filename, DECODE_METHOD); - String mimeType = APPLICATION_OCTET_STREAM; - if (filename.endsWith(".txt")) { - mimeType = TEXT_PLAIN; - } - DocumentFile exists = parent.findFile(filename); - if (exists != null) return exists; - return parent.createFile(mimeType, filename); - } catch (Exception e) { - Log.error("[FileUtil]: Cannot create file, error: " + e.getMessage()); - } - return null; - } - - /** - * Create a directory from directory with filename. - * @param context Application context - * @param directory parent path for directory. - * @param directoryName directory display name. - * @return boolean - */ - @Nullable - public static DocumentFile createDir(Context context, String directory, String directoryName) { - try { - Uri directoryUri = Uri.parse(directory); - DocumentFile parent = DocumentFile.fromTreeUri(context, directoryUri); - if (parent == null) return null; - directoryName = URLDecoder.decode(directoryName, DECODE_METHOD); - DocumentFile isExist = parent.findFile(directoryName); - if (isExist != null) return isExist; - return parent.createDirectory(directoryName); - } catch (Exception e) { - Log.error("[FileUtil]: Cannot create file, error: " + e.getMessage()); - } - return null; - } - - /** - * Open content uri and return file descriptor to JNI. - * @param context Application context - * @param path Native content uri path - * @param openmode will be one of "r", "r", "rw", "wa", "rwa" - * @return file descriptor - */ - public static int openContentUri(Context context, String path, String openmode) { - try { - Uri uri = Uri.parse(path); - ParcelFileDescriptor parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri, openmode); - if (parcelFileDescriptor == null) { - Log.error("[FileUtil]: Cannot get the file descriptor from uri: " + path); - return -1; - } - return parcelFileDescriptor.detachFd(); - } - catch (Exception e) { - Log.error("[FileUtil]: Cannot open content uri, error: " + e.getMessage()); - } - return -1; - } - - /** - * Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow - * This function will be faster than DoucmentFile.listFiles - * @param context Application context - * @param uri Directory uri. - * @return CheapDocument lists. - */ - public static MinimalDocumentFile[] listFiles(Context context, Uri uri) { - final ContentResolver resolver = context.getContentResolver(); - final String[] columns = new String[]{ - DocumentsContract.Document.COLUMN_DOCUMENT_ID, - DocumentsContract.Document.COLUMN_DISPLAY_NAME, - DocumentsContract.Document.COLUMN_MIME_TYPE, - }; - Cursor c = null; - final List results = new ArrayList<>(); - try { - String docId; - if (isRootTreeUri(uri)) { - docId = DocumentsContract.getTreeDocumentId(uri); - } else { - docId = DocumentsContract.getDocumentId(uri); - } - final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, docId); - c = resolver.query(childrenUri, columns, null, null, null); - while(c.moveToNext()) { - final String documentId = c.getString(0); - final String documentName = c.getString(1); - final String documentMimeType = c.getString(2); - final Uri documentUri = DocumentsContract.buildDocumentUriUsingTree(uri, documentId); - MinimalDocumentFile document = new MinimalDocumentFile(documentName, documentMimeType, documentUri); - results.add(document); - } - } catch (Exception e) { - Log.error("[FileUtil]: Cannot list file error: " + e.getMessage()); - } finally { - closeQuietly(c); - } - return results.toArray(new MinimalDocumentFile[0]); - } - - /** - * Check whether given path exists. - * @param path Native content uri path - * @return bool - */ - public static boolean Exists(Context context, String path) { - Cursor c = null; - try { - Uri mUri = Uri.parse(path); - final String[] columns = new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID }; - c = context.getContentResolver().query(mUri, columns, null, null, null); - return c.getCount() > 0; - } catch (Exception e) { - Log.info("[FileUtil] Cannot find file from given path, error: " + e.getMessage()); - } finally { - closeQuietly(c); - } - return false; - } - - /** - * Check whether given path is a directory - * @param path content uri path - * @return bool - */ - public static boolean isDirectory(Context context, String path) { - final ContentResolver resolver = context.getContentResolver(); - final String[] columns = new String[] { - DocumentsContract.Document.COLUMN_MIME_TYPE - }; - boolean isDirectory = false; - Cursor c = null; - try { - Uri mUri = Uri.parse(path); - c = resolver.query(mUri, columns, null, null, null); - c.moveToNext(); - final String mimeType = c.getString(0); - isDirectory = mimeType.equals(DocumentsContract.Document.MIME_TYPE_DIR); - } catch (Exception e) { - Log.error("[FileUtil]: Cannot list files, error: " + e.getMessage()); - } finally { - closeQuietly(c); - } - return isDirectory; - } - - /** - * Get file display name from given path - * @param path content uri path - * @return String display name - */ - public static String getFilename(Context context, String path) { - final ContentResolver resolver = context.getContentResolver(); - final String[] columns = new String[] { - DocumentsContract.Document.COLUMN_DISPLAY_NAME - }; - String filename = ""; - Cursor c = null; - try { - Uri mUri = Uri.parse(path); - c = resolver.query(mUri, columns, null, null, null); - c.moveToNext(); - filename = c.getString(0); - } catch (Exception e) { - Log.error("[FileUtil]: Cannot get file size, error: " + e.getMessage()); - } finally { - closeQuietly(c); - } - return filename; - } - - public static String[] getFilesName(Context context, String path) { - Uri uri = Uri.parse(path); - List files = new ArrayList<>(); - for (MinimalDocumentFile file: FileUtil.listFiles(context, uri)) { - files.add(file.getFilename()); - } - return files.toArray(new String[0]); - } - - /** - * Get file size from given path. - * @param path content uri path - * @return long file size - */ - public static long getFileSize(Context context, String path) { - final ContentResolver resolver = context.getContentResolver(); - final String[] columns = new String[] { - DocumentsContract.Document.COLUMN_SIZE - }; - long size = 0; - Cursor c =null; - try { - Uri mUri = Uri.parse(path); - c = resolver.query(mUri, columns, null, null, null); - c.moveToNext(); - size = c.getLong(0); - } catch (Exception e) { - Log.error("[FileUtil]: Cannot get file size, error: " + e.getMessage()); - } finally { - closeQuietly(c); - } - return size; - } - - public static boolean copyUriToInternalStorage(Context context, Uri sourceUri, String destinationParentPath, String destinationFilename) { - InputStream input = null; - FileOutputStream output = null; - try { - input = context.getContentResolver().openInputStream(sourceUri); - output = new FileOutputStream(destinationParentPath + "/" + destinationFilename); - byte[] buffer = new byte[1024]; - int len; - while ((len = input.read(buffer)) != -1) { - output.write(buffer, 0, len); - } - output.flush(); - return true; - } catch (Exception e) { - Log.error("[FileUtil]: Cannot copy file, error: " + e.getMessage()); - } finally { - if (input != null) { - try { - input.close(); - } catch (IOException e) { - Log.error("[FileUtil]: Cannot close input file, error: " + e.getMessage()); - } - } - if (output != null) { - try { - output.close(); - } catch (IOException e) { - Log.error("[FileUtil]: Cannot close output file, error: " + e.getMessage()); - } - } - } - return false; - } - - public static boolean isRootTreeUri(Uri uri) { - final List paths = uri.getPathSegments(); - return paths.size() == 2 && PATH_TREE.equals(paths.get(0)); - } - - public static void closeQuietly(AutoCloseable closeable) { - if (closeable != null) { - try { - closeable.close(); - } catch (RuntimeException rethrown) { - throw rethrown; - } catch (Exception ignored) { - } - } - } -} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt new file mode 100644 index 0000000000..47fae09335 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt @@ -0,0 +1,292 @@ +package org.yuzu.yuzu_emu.utils + +import android.content.Context +import android.database.Cursor +import android.net.Uri +import android.provider.DocumentsContract +import androidx.documentfile.provider.DocumentFile +import org.yuzu.yuzu_emu.model.MinimalDocumentFile +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream +import java.net.URLDecoder + +object FileUtil { + const val PATH_TREE = "tree" + const val DECODE_METHOD = "UTF-8" + const val APPLICATION_OCTET_STREAM = "application/octet-stream" + const val TEXT_PLAIN = "text/plain" + + /** + * Create a file from directory with filename. + * @param context Application context + * @param directory parent path for file. + * @param filename file display name. + * @return boolean + */ + fun createFile(context: Context?, directory: String?, filename: String): DocumentFile? { + var decodedFilename = filename + try { + val directoryUri = Uri.parse(directory) + val parent = DocumentFile.fromTreeUri(context!!, directoryUri) ?: return null + decodedFilename = URLDecoder.decode(decodedFilename, DECODE_METHOD) + var mimeType = APPLICATION_OCTET_STREAM + if (decodedFilename.endsWith(".txt")) { + mimeType = TEXT_PLAIN + } + val exists = parent.findFile(decodedFilename) + return exists ?: parent.createFile(mimeType, decodedFilename) + } catch (e: Exception) { + Log.error("[FileUtil]: Cannot create file, error: " + e.message) + } + return null + } + + /** + * Create a directory from directory with filename. + * @param context Application context + * @param directory parent path for directory. + * @param directoryName directory display name. + * @return boolean + */ + fun createDir(context: Context?, directory: String?, directoryName: String?): DocumentFile? { + var decodedDirectoryName = directoryName + try { + val directoryUri = Uri.parse(directory) + val parent = DocumentFile.fromTreeUri(context!!, directoryUri) ?: return null + decodedDirectoryName = URLDecoder.decode(decodedDirectoryName, DECODE_METHOD) + val isExist = parent.findFile(decodedDirectoryName) + return isExist ?: parent.createDirectory(decodedDirectoryName) + } catch (e: Exception) { + Log.error("[FileUtil]: Cannot create file, error: " + e.message) + } + return null + } + + /** + * Open content uri and return file descriptor to JNI. + * @param context Application context + * @param path Native content uri path + * @param openMode will be one of "r", "r", "rw", "wa", "rwa" + * @return file descriptor + */ + @JvmStatic + fun openContentUri(context: Context, path: String, openMode: String?): Int { + try { + val uri = Uri.parse(path) + val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, openMode!!) + if (parcelFileDescriptor == null) { + Log.error("[FileUtil]: Cannot get the file descriptor from uri: $path") + return -1 + } + val fileDescriptor = parcelFileDescriptor.detachFd() + parcelFileDescriptor.close() + return fileDescriptor + } catch (e: Exception) { + Log.error("[FileUtil]: Cannot open content uri, error: " + e.message) + } + return -1 + } + + /** + * Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow + * This function will be faster than DoucmentFile.listFiles + * @param context Application context + * @param uri Directory uri. + * @return CheapDocument lists. + */ + fun listFiles(context: Context, uri: Uri): Array { + val resolver = context.contentResolver + val columns = arrayOf( + DocumentsContract.Document.COLUMN_DOCUMENT_ID, + DocumentsContract.Document.COLUMN_DISPLAY_NAME, + DocumentsContract.Document.COLUMN_MIME_TYPE + ) + var c: Cursor? = null + val results: MutableList = ArrayList() + try { + val docId: String = if (isRootTreeUri(uri)) { + DocumentsContract.getTreeDocumentId(uri) + } else { + DocumentsContract.getDocumentId(uri) + } + val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, docId) + c = resolver.query(childrenUri, columns, null, null, null) + while (c!!.moveToNext()) { + val documentId = c.getString(0) + val documentName = c.getString(1) + val documentMimeType = c.getString(2) + val documentUri = DocumentsContract.buildDocumentUriUsingTree(uri, documentId) + val document = MinimalDocumentFile(documentName, documentMimeType, documentUri) + results.add(document) + } + } catch (e: Exception) { + Log.error("[FileUtil]: Cannot list file error: " + e.message) + } finally { + closeQuietly(c) + } + return results.toTypedArray() + } + + /** + * Check whether given path exists. + * @param path Native content uri path + * @return bool + */ + fun exists(context: Context, path: String?): Boolean { + var c: Cursor? = null + try { + val mUri = Uri.parse(path) + val columns = arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID) + c = context.contentResolver.query(mUri, columns, null, null, null) + return c!!.count > 0 + } catch (e: Exception) { + Log.info("[FileUtil] Cannot find file from given path, error: " + e.message) + } finally { + closeQuietly(c) + } + return false + } + + /** + * Check whether given path is a directory + * @param path content uri path + * @return bool + */ + fun isDirectory(context: Context, path: String): Boolean { + val resolver = context.contentResolver + val columns = arrayOf( + DocumentsContract.Document.COLUMN_MIME_TYPE + ) + var isDirectory = false + var c: Cursor? = null + try { + val mUri = Uri.parse(path) + c = resolver.query(mUri, columns, null, null, null) + c!!.moveToNext() + val mimeType = c.getString(0) + isDirectory = mimeType == DocumentsContract.Document.MIME_TYPE_DIR + } catch (e: Exception) { + Log.error("[FileUtil]: Cannot list files, error: " + e.message) + } finally { + closeQuietly(c) + } + return isDirectory + } + + /** + * Get file display name from given path + * @param path content uri path + * @return String display name + */ + fun getFilename(context: Context, path: String): String { + val resolver = context.contentResolver + val columns = arrayOf( + DocumentsContract.Document.COLUMN_DISPLAY_NAME + ) + var filename = "" + var c: Cursor? = null + try { + val mUri = Uri.parse(path) + c = resolver.query(mUri, columns, null, null, null) + c!!.moveToNext() + filename = c.getString(0) + } catch (e: Exception) { + Log.error("[FileUtil]: Cannot get file size, error: " + e.message) + } finally { + closeQuietly(c) + } + return filename + } + + fun getFilesName(context: Context, path: String): Array { + val uri = Uri.parse(path) + val files: MutableList = ArrayList() + for (file in listFiles(context, uri)) { + files.add(file.filename) + } + return files.toTypedArray() + } + + /** + * Get file size from given path. + * @param path content uri path + * @return long file size + */ + @JvmStatic + fun getFileSize(context: Context, path: String): Long { + val resolver = context.contentResolver + val columns = arrayOf( + DocumentsContract.Document.COLUMN_SIZE + ) + var size: Long = 0 + var c: Cursor? = null + try { + val mUri = Uri.parse(path) + c = resolver.query(mUri, columns, null, null, null) + c!!.moveToNext() + size = c.getLong(0) + } catch (e: Exception) { + Log.error("[FileUtil]: Cannot get file size, error: " + e.message) + } finally { + closeQuietly(c) + } + return size + } + + @JvmStatic + fun copyUriToInternalStorage( + context: Context, + sourceUri: Uri?, + destinationParentPath: String, + destinationFilename: String + ): Boolean { + var input: InputStream? = null + var output: FileOutputStream? = null + try { + input = context.contentResolver.openInputStream(sourceUri!!) + output = FileOutputStream("$destinationParentPath/$destinationFilename") + val buffer = ByteArray(1024) + var len: Int + while (input!!.read(buffer).also { len = it } != -1) { + output.write(buffer, 0, len) + } + output.flush() + return true + } catch (e: Exception) { + Log.error("[FileUtil]: Cannot copy file, error: " + e.message) + } finally { + if (input != null) { + try { + input.close() + } catch (e: IOException) { + Log.error("[FileUtil]: Cannot close input file, error: " + e.message) + } + } + if (output != null) { + try { + output.close() + } catch (e: IOException) { + Log.error("[FileUtil]: Cannot close output file, error: " + e.message) + } + } + } + return false + } + + fun isRootTreeUri(uri: Uri): Boolean { + val paths = uri.pathSegments + return paths.size == 2 && PATH_TREE == paths[0] + } + + fun closeQuietly(closeable: AutoCloseable?) { + if (closeable != null) { + try { + closeable.close() + } catch (rethrown: RuntimeException) { + throw rethrown + } catch (ignored: Exception) { + } + } + } +}