committed by
bunnei
2 changed files with 292 additions and 296 deletions
-
296src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.java
-
292src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
@ -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<MinimalDocumentFile> 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<String> 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<String> 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) { |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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<MinimalDocumentFile> { |
|||
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<MinimalDocumentFile> = 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<String> { |
|||
val uri = Uri.parse(path) |
|||
val files: MutableList<String> = 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) { |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue