From d659a3d9b718330794f9b35dcea370bc3712b71a Mon Sep 17 00:00:00 2001 From: Andrea Tosatto Date: Thu, 19 Apr 2018 19:11:50 +0200 Subject: [PATCH 1/3] Android only: support for originalFilapath with stat (content uri resolution) --- FS.common.js | 7 ++++-- .../src/main/java/com/rnfs/RNFSManager.java | 22 +++++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/FS.common.js b/FS.common.js index 21cda3df..5c17d3f2 100755 --- a/FS.common.js +++ b/FS.common.js @@ -25,7 +25,7 @@ var getJobId = () => { return jobId; }; -var normalizeFilePath = (path: string) => (path.startsWith('file://') ? path.slice(7) : path); +var normalizeFilePath = (path: string) => (path.startsWith('file://') && !path.startsWith('content://') ? path.slice(7) : path); type MkdirOptions = { NSURLIsExcludedFromBackupKey?: boolean; // iOS only @@ -42,12 +42,13 @@ type ReadDirItem = { }; type StatResult = { - name: string; // The name of the item + name: ?string; // The name of the item path: string; // The absolute path to the item size: string; // Size in bytes mode: number; // UNIX file mode ctime: number; // Created date mtime: number; // Last modified date + originalFilepath: ?string; // In case of content uri this is the pointed file path, otherwise is the same as path isFile: () => boolean; // Is the file just a file? isDirectory: () => boolean; // Is the file a directory? }; @@ -272,10 +273,12 @@ var RNFS = { stat(filepath: string): Promise { return RNFSManager.stat(normalizeFilePath(filepath)).then((result) => { return { + 'path': filepath, 'ctime': new Date(result.ctime * 1000), 'mtime': new Date(result.mtime * 1000), 'size': result.size, 'mode': result.mode, + 'originalFilepath': result.originalFilepath, isFile: () => result.type === RNFSFileTypeRegular, isDirectory: () => result.type === RNFSFileTypeDirectory, }; diff --git a/android/src/main/java/com/rnfs/RNFSManager.java b/android/src/main/java/com/rnfs/RNFSManager.java index 02316787..5957d0c3 100755 --- a/android/src/main/java/com/rnfs/RNFSManager.java +++ b/android/src/main/java/com/rnfs/RNFSManager.java @@ -2,9 +2,11 @@ import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; +import android.database.Cursor; import android.net.Uri; import android.os.Environment; import android.os.StatFs; +import android.provider.MediaStore; import android.support.annotation.Nullable; import android.util.Base64; import android.util.SparseArray; @@ -72,6 +74,21 @@ private Uri getFileUri(String filepath) throws IORejectionException { return uri; } + private String getOriginalFilepath(String filepath) throws IORejectionException { + Uri uri = getFileUri(filepath); + String originalFilepath = ""; + if (uri.getScheme().equals("content")) { + try { + Cursor cursor = reactContext.getContentResolver().query(uri, null, null, null, null); + if (cursor.moveToFirst()) { + originalFilepath = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)); + } + } catch (IllegalArgumentException ignored) { + } + } + return originalFilepath; + } + private InputStream getInputStream(String filepath) throws IORejectionException { Uri uri = getFileUri(filepath); InputStream stream; @@ -510,16 +527,17 @@ public void setReadable(String filepath, Boolean readable, Boolean ownerOnly, Pr @ReactMethod public void stat(String filepath, Promise promise) { try { - File file = new File(filepath); + String originalFilepath = getOriginalFilepath(filepath); + File file = new File(originalFilepath); if (!file.exists()) throw new Exception("File does not exist"); WritableMap statMap = Arguments.createMap(); - statMap.putInt("ctime", (int) (file.lastModified() / 1000)); statMap.putInt("mtime", (int) (file.lastModified() / 1000)); statMap.putInt("size", (int) file.length()); statMap.putInt("type", file.isDirectory() ? 1 : 0); + statMap.putString("originalFilepath", originalFilepath); promise.resolve(statMap); } catch (Exception ex) { From 95283b60da7a4d567d2362ad664cf3fa5a2f6c20 Mon Sep 17 00:00:00 2001 From: Andrea Tosatto Date: Fri, 20 Apr 2018 19:38:39 +0200 Subject: [PATCH 2/3] Improved stat resilience to deleted file --- .gitignore | 5 +++++ FS.common.js | 6 +++--- README.md | 2 ++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 5e975b22..f3ddec43 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,8 @@ DerivedData # Pods - for those of you who use CocoaPods Pods update-test.sh +.vscode/ +android/.gradle/* +android/gradle/* +android/*.iml +android/local.properties diff --git a/FS.common.js b/FS.common.js index 5c17d3f2..3214d117 100755 --- a/FS.common.js +++ b/FS.common.js @@ -25,7 +25,7 @@ var getJobId = () => { return jobId; }; -var normalizeFilePath = (path: string) => (path.startsWith('file://') && !path.startsWith('content://') ? path.slice(7) : path); +var normalizeFilePath = (path: string) => (path.startsWith('file://') ? path.slice(7) : path); type MkdirOptions = { NSURLIsExcludedFromBackupKey?: boolean; // iOS only @@ -42,13 +42,13 @@ type ReadDirItem = { }; type StatResult = { - name: ?string; // The name of the item + name: ?string; // The name of the item TODO: why is this not documented? path: string; // The absolute path to the item size: string; // Size in bytes mode: number; // UNIX file mode ctime: number; // Created date mtime: number; // Last modified date - originalFilepath: ?string; // In case of content uri this is the pointed file path, otherwise is the same as path + originalFilepath: string; // In case of content uri this is the pointed file path, otherwise is the same as path isFile: () => boolean; // Is the file just a file? isDirectory: () => boolean; // Is the file a directory? }; diff --git a/README.md b/README.md index bd5df018..889024db 100644 --- a/README.md +++ b/README.md @@ -383,10 +383,12 @@ The promise resolves with an object with the following properties: ``` type StatResult = { + path: // The same as filepath argument ctime: date; // The creation date of the file mtime: date; // The last modified date of the file size: string; // Size in bytes mode: number; // UNIX file mode + originalFilepath: string; // ANDROID: In case of content uri this is the pointed file path, otherwise is the same as path isFile: () => boolean; // Is the file just a file? isDirectory: () => boolean; // Is the file a directory? }; From 1e0917a4a84eabb07ee424bf7a1af013dc2a37d0 Mon Sep 17 00:00:00 2001 From: Andrea Tosatto Date: Thu, 10 May 2018 20:36:37 +0200 Subject: [PATCH 3/3] Added some notes about stat() in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 889024db..9d9d69c3 100644 --- a/README.md +++ b/README.md @@ -378,7 +378,7 @@ Node.js style version of `readDir` that returns only the names. Note the lowerca ### `stat(filepath: string): Promise` -Stats an item at `path`. +Stats an item at `filepath`. If the `filepath` is linked to a virtual file, for example Android Content URI, the `originalPath` can be used to find the pointed file path. The promise resolves with an object with the following properties: ```