summaryrefslogtreecommitdiff
path: root/src/android
diff options
context:
space:
mode:
authorGravatar Charles Lombardo2023-03-11 00:37:04 -0500
committerGravatar bunnei2023-06-03 00:05:41 -0700
commit7b54c2b2e25adb81c95d54941f55bae76770cdb9 (patch)
tree28032417460af44cd98210e098ab394cffdcd66b /src/android
parentandroid: Convert FileBrowserHelper to Kotlin (diff)
downloadyuzu-7b54c2b2e25adb81c95d54941f55bae76770cdb9.tar.gz
yuzu-7b54c2b2e25adb81c95d54941f55bae76770cdb9.tar.xz
yuzu-7b54c2b2e25adb81c95d54941f55bae76770cdb9.zip
android: Convert FileUtil to Kotlin
Diffstat (limited to 'src/android')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.java296
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt292
2 files changed, 292 insertions, 296 deletions
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 8665704cc..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.java
+++ /dev/null
@@ -1,296 +0,0 @@
1package org.yuzu.yuzu_emu.utils;
2
3import android.content.ContentResolver;
4import android.content.Context;
5import android.database.Cursor;
6import android.net.Uri;
7import android.os.ParcelFileDescriptor;
8import android.provider.DocumentsContract;
9
10import androidx.annotation.Nullable;
11import androidx.documentfile.provider.DocumentFile;
12
13import org.yuzu.yuzu_emu.model.MinimalDocumentFile;
14
15import java.io.FileOutputStream;
16import java.io.IOException;
17import java.io.InputStream;
18import java.net.URLDecoder;
19import java.util.ArrayList;
20import java.util.List;
21
22public class FileUtil {
23 static final String PATH_TREE = "tree";
24 static final String DECODE_METHOD = "UTF-8";
25 static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
26 static final String TEXT_PLAIN = "text/plain";
27
28 /**
29 * Create a file from directory with filename.
30 * @param context Application context
31 * @param directory parent path for file.
32 * @param filename file display name.
33 * @return boolean
34 */
35 @Nullable
36 public static DocumentFile createFile(Context context, String directory, String filename) {
37 try {
38 Uri directoryUri = Uri.parse(directory);
39 DocumentFile parent = DocumentFile.fromTreeUri(context, directoryUri);
40 if (parent == null) return null;
41 filename = URLDecoder.decode(filename, DECODE_METHOD);
42 String mimeType = APPLICATION_OCTET_STREAM;
43 if (filename.endsWith(".txt")) {
44 mimeType = TEXT_PLAIN;
45 }
46 DocumentFile exists = parent.findFile(filename);
47 if (exists != null) return exists;
48 return parent.createFile(mimeType, filename);
49 } catch (Exception e) {
50 Log.error("[FileUtil]: Cannot create file, error: " + e.getMessage());
51 }
52 return null;
53 }
54
55 /**
56 * Create a directory from directory with filename.
57 * @param context Application context
58 * @param directory parent path for directory.
59 * @param directoryName directory display name.
60 * @return boolean
61 */
62 @Nullable
63 public static DocumentFile createDir(Context context, String directory, String directoryName) {
64 try {
65 Uri directoryUri = Uri.parse(directory);
66 DocumentFile parent = DocumentFile.fromTreeUri(context, directoryUri);
67 if (parent == null) return null;
68 directoryName = URLDecoder.decode(directoryName, DECODE_METHOD);
69 DocumentFile isExist = parent.findFile(directoryName);
70 if (isExist != null) return isExist;
71 return parent.createDirectory(directoryName);
72 } catch (Exception e) {
73 Log.error("[FileUtil]: Cannot create file, error: " + e.getMessage());
74 }
75 return null;
76 }
77
78 /**
79 * Open content uri and return file descriptor to JNI.
80 * @param context Application context
81 * @param path Native content uri path
82 * @param openmode will be one of "r", "r", "rw", "wa", "rwa"
83 * @return file descriptor
84 */
85 public static int openContentUri(Context context, String path, String openmode) {
86 try {
87 Uri uri = Uri.parse(path);
88 ParcelFileDescriptor parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri, openmode);
89 if (parcelFileDescriptor == null) {
90 Log.error("[FileUtil]: Cannot get the file descriptor from uri: " + path);
91 return -1;
92 }
93 return parcelFileDescriptor.detachFd();
94 }
95 catch (Exception e) {
96 Log.error("[FileUtil]: Cannot open content uri, error: " + e.getMessage());
97 }
98 return -1;
99 }
100
101 /**
102 * Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow
103 * This function will be faster than DoucmentFile.listFiles
104 * @param context Application context
105 * @param uri Directory uri.
106 * @return CheapDocument lists.
107 */
108 public static MinimalDocumentFile[] listFiles(Context context, Uri uri) {
109 final ContentResolver resolver = context.getContentResolver();
110 final String[] columns = new String[]{
111 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
112 DocumentsContract.Document.COLUMN_DISPLAY_NAME,
113 DocumentsContract.Document.COLUMN_MIME_TYPE,
114 };
115 Cursor c = null;
116 final List<MinimalDocumentFile> results = new ArrayList<>();
117 try {
118 String docId;
119 if (isRootTreeUri(uri)) {
120 docId = DocumentsContract.getTreeDocumentId(uri);
121 } else {
122 docId = DocumentsContract.getDocumentId(uri);
123 }
124 final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, docId);
125 c = resolver.query(childrenUri, columns, null, null, null);
126 while(c.moveToNext()) {
127 final String documentId = c.getString(0);
128 final String documentName = c.getString(1);
129 final String documentMimeType = c.getString(2);
130 final Uri documentUri = DocumentsContract.buildDocumentUriUsingTree(uri, documentId);
131 MinimalDocumentFile document = new MinimalDocumentFile(documentName, documentMimeType, documentUri);
132 results.add(document);
133 }
134 } catch (Exception e) {
135 Log.error("[FileUtil]: Cannot list file error: " + e.getMessage());
136 } finally {
137 closeQuietly(c);
138 }
139 return results.toArray(new MinimalDocumentFile[0]);
140 }
141
142 /**
143 * Check whether given path exists.
144 * @param path Native content uri path
145 * @return bool
146 */
147 public static boolean Exists(Context context, String path) {
148 Cursor c = null;
149 try {
150 Uri mUri = Uri.parse(path);
151 final String[] columns = new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID };
152 c = context.getContentResolver().query(mUri, columns, null, null, null);
153 return c.getCount() > 0;
154 } catch (Exception e) {
155 Log.info("[FileUtil] Cannot find file from given path, error: " + e.getMessage());
156 } finally {
157 closeQuietly(c);
158 }
159 return false;
160 }
161
162 /**
163 * Check whether given path is a directory
164 * @param path content uri path
165 * @return bool
166 */
167 public static boolean isDirectory(Context context, String path) {
168 final ContentResolver resolver = context.getContentResolver();
169 final String[] columns = new String[] {
170 DocumentsContract.Document.COLUMN_MIME_TYPE
171 };
172 boolean isDirectory = false;
173 Cursor c = null;
174 try {
175 Uri mUri = Uri.parse(path);
176 c = resolver.query(mUri, columns, null, null, null);
177 c.moveToNext();
178 final String mimeType = c.getString(0);
179 isDirectory = mimeType.equals(DocumentsContract.Document.MIME_TYPE_DIR);
180 } catch (Exception e) {
181 Log.error("[FileUtil]: Cannot list files, error: " + e.getMessage());
182 } finally {
183 closeQuietly(c);
184 }
185 return isDirectory;
186 }
187
188 /**
189 * Get file display name from given path
190 * @param path content uri path
191 * @return String display name
192 */
193 public static String getFilename(Context context, String path) {
194 final ContentResolver resolver = context.getContentResolver();
195 final String[] columns = new String[] {
196 DocumentsContract.Document.COLUMN_DISPLAY_NAME
197 };
198 String filename = "";
199 Cursor c = null;
200 try {
201 Uri mUri = Uri.parse(path);
202 c = resolver.query(mUri, columns, null, null, null);
203 c.moveToNext();
204 filename = c.getString(0);
205 } catch (Exception e) {
206 Log.error("[FileUtil]: Cannot get file size, error: " + e.getMessage());
207 } finally {
208 closeQuietly(c);
209 }
210 return filename;
211 }
212
213 public static String[] getFilesName(Context context, String path) {
214 Uri uri = Uri.parse(path);
215 List<String> files = new ArrayList<>();
216 for (MinimalDocumentFile file: FileUtil.listFiles(context, uri)) {
217 files.add(file.getFilename());
218 }
219 return files.toArray(new String[0]);
220 }
221
222 /**
223 * Get file size from given path.
224 * @param path content uri path
225 * @return long file size
226 */
227 public static long getFileSize(Context context, String path) {
228 final ContentResolver resolver = context.getContentResolver();
229 final String[] columns = new String[] {
230 DocumentsContract.Document.COLUMN_SIZE
231 };
232 long size = 0;
233 Cursor c =null;
234 try {
235 Uri mUri = Uri.parse(path);
236 c = resolver.query(mUri, columns, null, null, null);
237 c.moveToNext();
238 size = c.getLong(0);
239 } catch (Exception e) {
240 Log.error("[FileUtil]: Cannot get file size, error: " + e.getMessage());
241 } finally {
242 closeQuietly(c);
243 }
244 return size;
245 }
246
247 public static boolean copyUriToInternalStorage(Context context, Uri sourceUri, String destinationParentPath, String destinationFilename) {
248 InputStream input = null;
249 FileOutputStream output = null;
250 try {
251 input = context.getContentResolver().openInputStream(sourceUri);
252 output = new FileOutputStream(destinationParentPath + "/" + destinationFilename);
253 byte[] buffer = new byte[1024];
254 int len;
255 while ((len = input.read(buffer)) != -1) {
256 output.write(buffer, 0, len);
257 }
258 output.flush();
259 return true;
260 } catch (Exception e) {
261 Log.error("[FileUtil]: Cannot copy file, error: " + e.getMessage());
262 } finally {
263 if (input != null) {
264 try {
265 input.close();
266 } catch (IOException e) {
267 Log.error("[FileUtil]: Cannot close input file, error: " + e.getMessage());
268 }
269 }
270 if (output != null) {
271 try {
272 output.close();
273 } catch (IOException e) {
274 Log.error("[FileUtil]: Cannot close output file, error: " + e.getMessage());
275 }
276 }
277 }
278 return false;
279 }
280
281 public static boolean isRootTreeUri(Uri uri) {
282 final List<String> paths = uri.getPathSegments();
283 return paths.size() == 2 && PATH_TREE.equals(paths.get(0));
284 }
285
286 public static void closeQuietly(AutoCloseable closeable) {
287 if (closeable != null) {
288 try {
289 closeable.close();
290 } catch (RuntimeException rethrown) {
291 throw rethrown;
292 } catch (Exception ignored) {
293 }
294 }
295 }
296}
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 000000000..47fae0933
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
@@ -0,0 +1,292 @@
1package org.yuzu.yuzu_emu.utils
2
3import android.content.Context
4import android.database.Cursor
5import android.net.Uri
6import android.provider.DocumentsContract
7import androidx.documentfile.provider.DocumentFile
8import org.yuzu.yuzu_emu.model.MinimalDocumentFile
9import java.io.FileOutputStream
10import java.io.IOException
11import java.io.InputStream
12import java.net.URLDecoder
13
14object FileUtil {
15 const val PATH_TREE = "tree"
16 const val DECODE_METHOD = "UTF-8"
17 const val APPLICATION_OCTET_STREAM = "application/octet-stream"
18 const val TEXT_PLAIN = "text/plain"
19
20 /**
21 * Create a file from directory with filename.
22 * @param context Application context
23 * @param directory parent path for file.
24 * @param filename file display name.
25 * @return boolean
26 */
27 fun createFile(context: Context?, directory: String?, filename: String): DocumentFile? {
28 var decodedFilename = filename
29 try {
30 val directoryUri = Uri.parse(directory)
31 val parent = DocumentFile.fromTreeUri(context!!, directoryUri) ?: return null
32 decodedFilename = URLDecoder.decode(decodedFilename, DECODE_METHOD)
33 var mimeType = APPLICATION_OCTET_STREAM
34 if (decodedFilename.endsWith(".txt")) {
35 mimeType = TEXT_PLAIN
36 }
37 val exists = parent.findFile(decodedFilename)
38 return exists ?: parent.createFile(mimeType, decodedFilename)
39 } catch (e: Exception) {
40 Log.error("[FileUtil]: Cannot create file, error: " + e.message)
41 }
42 return null
43 }
44
45 /**
46 * Create a directory from directory with filename.
47 * @param context Application context
48 * @param directory parent path for directory.
49 * @param directoryName directory display name.
50 * @return boolean
51 */
52 fun createDir(context: Context?, directory: String?, directoryName: String?): DocumentFile? {
53 var decodedDirectoryName = directoryName
54 try {
55 val directoryUri = Uri.parse(directory)
56 val parent = DocumentFile.fromTreeUri(context!!, directoryUri) ?: return null
57 decodedDirectoryName = URLDecoder.decode(decodedDirectoryName, DECODE_METHOD)
58 val isExist = parent.findFile(decodedDirectoryName)
59 return isExist ?: parent.createDirectory(decodedDirectoryName)
60 } catch (e: Exception) {
61 Log.error("[FileUtil]: Cannot create file, error: " + e.message)
62 }
63 return null
64 }
65
66 /**
67 * Open content uri and return file descriptor to JNI.
68 * @param context Application context
69 * @param path Native content uri path
70 * @param openMode will be one of "r", "r", "rw", "wa", "rwa"
71 * @return file descriptor
72 */
73 @JvmStatic
74 fun openContentUri(context: Context, path: String, openMode: String?): Int {
75 try {
76 val uri = Uri.parse(path)
77 val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, openMode!!)
78 if (parcelFileDescriptor == null) {
79 Log.error("[FileUtil]: Cannot get the file descriptor from uri: $path")
80 return -1
81 }
82 val fileDescriptor = parcelFileDescriptor.detachFd()
83 parcelFileDescriptor.close()
84 return fileDescriptor
85 } catch (e: Exception) {
86 Log.error("[FileUtil]: Cannot open content uri, error: " + e.message)
87 }
88 return -1
89 }
90
91 /**
92 * Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow
93 * This function will be faster than DoucmentFile.listFiles
94 * @param context Application context
95 * @param uri Directory uri.
96 * @return CheapDocument lists.
97 */
98 fun listFiles(context: Context, uri: Uri): Array<MinimalDocumentFile> {
99 val resolver = context.contentResolver
100 val columns = arrayOf(
101 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
102 DocumentsContract.Document.COLUMN_DISPLAY_NAME,
103 DocumentsContract.Document.COLUMN_MIME_TYPE
104 )
105 var c: Cursor? = null
106 val results: MutableList<MinimalDocumentFile> = ArrayList()
107 try {
108 val docId: String = if (isRootTreeUri(uri)) {
109 DocumentsContract.getTreeDocumentId(uri)
110 } else {
111 DocumentsContract.getDocumentId(uri)
112 }
113 val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, docId)
114 c = resolver.query(childrenUri, columns, null, null, null)
115 while (c!!.moveToNext()) {
116 val documentId = c.getString(0)
117 val documentName = c.getString(1)
118 val documentMimeType = c.getString(2)
119 val documentUri = DocumentsContract.buildDocumentUriUsingTree(uri, documentId)
120 val document = MinimalDocumentFile(documentName, documentMimeType, documentUri)
121 results.add(document)
122 }
123 } catch (e: Exception) {
124 Log.error("[FileUtil]: Cannot list file error: " + e.message)
125 } finally {
126 closeQuietly(c)
127 }
128 return results.toTypedArray()
129 }
130
131 /**
132 * Check whether given path exists.
133 * @param path Native content uri path
134 * @return bool
135 */
136 fun exists(context: Context, path: String?): Boolean {
137 var c: Cursor? = null
138 try {
139 val mUri = Uri.parse(path)
140 val columns = arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID)
141 c = context.contentResolver.query(mUri, columns, null, null, null)
142 return c!!.count > 0
143 } catch (e: Exception) {
144 Log.info("[FileUtil] Cannot find file from given path, error: " + e.message)
145 } finally {
146 closeQuietly(c)
147 }
148 return false
149 }
150
151 /**
152 * Check whether given path is a directory
153 * @param path content uri path
154 * @return bool
155 */
156 fun isDirectory(context: Context, path: String): Boolean {
157 val resolver = context.contentResolver
158 val columns = arrayOf(
159 DocumentsContract.Document.COLUMN_MIME_TYPE
160 )
161 var isDirectory = false
162 var c: Cursor? = null
163 try {
164 val mUri = Uri.parse(path)
165 c = resolver.query(mUri, columns, null, null, null)
166 c!!.moveToNext()
167 val mimeType = c.getString(0)
168 isDirectory = mimeType == DocumentsContract.Document.MIME_TYPE_DIR
169 } catch (e: Exception) {
170 Log.error("[FileUtil]: Cannot list files, error: " + e.message)
171 } finally {
172 closeQuietly(c)
173 }
174 return isDirectory
175 }
176
177 /**
178 * Get file display name from given path
179 * @param path content uri path
180 * @return String display name
181 */
182 fun getFilename(context: Context, path: String): String {
183 val resolver = context.contentResolver
184 val columns = arrayOf(
185 DocumentsContract.Document.COLUMN_DISPLAY_NAME
186 )
187 var filename = ""
188 var c: Cursor? = null
189 try {
190 val mUri = Uri.parse(path)
191 c = resolver.query(mUri, columns, null, null, null)
192 c!!.moveToNext()
193 filename = c.getString(0)
194 } catch (e: Exception) {
195 Log.error("[FileUtil]: Cannot get file size, error: " + e.message)
196 } finally {
197 closeQuietly(c)
198 }
199 return filename
200 }
201
202 fun getFilesName(context: Context, path: String): Array<String> {
203 val uri = Uri.parse(path)
204 val files: MutableList<String> = ArrayList()
205 for (file in listFiles(context, uri)) {
206 files.add(file.filename)
207 }
208 return files.toTypedArray()
209 }
210
211 /**
212 * Get file size from given path.
213 * @param path content uri path
214 * @return long file size
215 */
216 @JvmStatic
217 fun getFileSize(context: Context, path: String): Long {
218 val resolver = context.contentResolver
219 val columns = arrayOf(
220 DocumentsContract.Document.COLUMN_SIZE
221 )
222 var size: Long = 0
223 var c: Cursor? = null
224 try {
225 val mUri = Uri.parse(path)
226 c = resolver.query(mUri, columns, null, null, null)
227 c!!.moveToNext()
228 size = c.getLong(0)
229 } catch (e: Exception) {
230 Log.error("[FileUtil]: Cannot get file size, error: " + e.message)
231 } finally {
232 closeQuietly(c)
233 }
234 return size
235 }
236
237 @JvmStatic
238 fun copyUriToInternalStorage(
239 context: Context,
240 sourceUri: Uri?,
241 destinationParentPath: String,
242 destinationFilename: String
243 ): Boolean {
244 var input: InputStream? = null
245 var output: FileOutputStream? = null
246 try {
247 input = context.contentResolver.openInputStream(sourceUri!!)
248 output = FileOutputStream("$destinationParentPath/$destinationFilename")
249 val buffer = ByteArray(1024)
250 var len: Int
251 while (input!!.read(buffer).also { len = it } != -1) {
252 output.write(buffer, 0, len)
253 }
254 output.flush()
255 return true
256 } catch (e: Exception) {
257 Log.error("[FileUtil]: Cannot copy file, error: " + e.message)
258 } finally {
259 if (input != null) {
260 try {
261 input.close()
262 } catch (e: IOException) {
263 Log.error("[FileUtil]: Cannot close input file, error: " + e.message)
264 }
265 }
266 if (output != null) {
267 try {
268 output.close()
269 } catch (e: IOException) {
270 Log.error("[FileUtil]: Cannot close output file, error: " + e.message)
271 }
272 }
273 }
274 return false
275 }
276
277 fun isRootTreeUri(uri: Uri): Boolean {
278 val paths = uri.pathSegments
279 return paths.size == 2 && PATH_TREE == paths[0]
280 }
281
282 fun closeQuietly(closeable: AutoCloseable?) {
283 if (closeable != null) {
284 try {
285 closeable.close()
286 } catch (rethrown: RuntimeException) {
287 throw rethrown
288 } catch (ignored: Exception) {
289 }
290 }
291 }
292}