From 7773000864d2b047751038ed7752c4a96ba88266 Mon Sep 17 00:00:00 2001 From: Ian Y Date: Fri, 4 Oct 2024 18:13:50 +0800 Subject: [PATCH 01/19] feat: init android MediaStore --- .../java/com/rnfs2/RNFSFileTransformer.java | 10 + .../java/com/rnfs2/RNFSMediaStoreManager.java | 316 +++ .../src/main/java/com/rnfs2/RNFSPackage.java | 5 +- .../java/com/rnfs2/Utils/FileDescription.java | 17 + .../main/java/com/rnfs2/Utils/MimeType.java | 96 + example/App/example3.tsx | 206 ++ example/App/index.tsx | 21 +- .../android/app/src/main/AndroidManifest.xml | 6 +- example/yarn.lock | 2519 ++++++----------- src/index.ts | 24 + src/types.ts | 2 + 11 files changed, 1624 insertions(+), 1598 deletions(-) create mode 100644 android/src/main/java/com/rnfs2/RNFSFileTransformer.java create mode 100644 android/src/main/java/com/rnfs2/RNFSMediaStoreManager.java create mode 100644 android/src/main/java/com/rnfs2/Utils/FileDescription.java create mode 100644 android/src/main/java/com/rnfs2/Utils/MimeType.java create mode 100644 example/App/example3.tsx diff --git a/android/src/main/java/com/rnfs2/RNFSFileTransformer.java b/android/src/main/java/com/rnfs2/RNFSFileTransformer.java new file mode 100644 index 00000000..a95bc32b --- /dev/null +++ b/android/src/main/java/com/rnfs2/RNFSFileTransformer.java @@ -0,0 +1,10 @@ +package com.rnfs2; + +public class RNFSFileTransformer { + public interface FileTransformer { + public byte[] onWriteFile(byte[] data); + public byte[] onReadFile(byte[] data); + } + + public static RNFSFileTransformer.FileTransformer sharedFileTransformer; +} diff --git a/android/src/main/java/com/rnfs2/RNFSMediaStoreManager.java b/android/src/main/java/com/rnfs2/RNFSMediaStoreManager.java new file mode 100644 index 00000000..418e2cf7 --- /dev/null +++ b/android/src/main/java/com/rnfs2/RNFSMediaStoreManager.java @@ -0,0 +1,316 @@ +package com.rnfs2; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.os.ParcelFileDescriptor; +import android.provider.MediaStore; + +import androidx.annotation.RequiresApi; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableArray; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.module.annotations.ReactModule; + +import com.rnfs2.Utils.FileDescription; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; + +@ReactModule(name = RNFSMediaStoreManager.MODULE_NAME) +public class RNFSMediaStoreManager extends ReactContextBaseJavaModule { + + static final String MODULE_NAME = "RNFSMediaStoreManager"; + private static ReactApplicationContext reactContext; + + public enum MediaType { + Audio, + Image, + Video, + } + + private static final String RNFSMediaStoreTypeAudio = MediaType.Audio.toString(); + private static final String RNFSMediaStoreTypeImage = MediaType.Image.toString();; + private static final String RNFSMediaStoreTypeVideo = MediaType.Video.toString();; + + public RNFSMediaStoreManager(ReactApplicationContext reactContext) { + super(reactContext); + RNFSMediaStoreManager.reactContext = reactContext; + } + + @Override + public String getName() { + return MODULE_NAME; + } + + private static Uri getMediaUri(MediaType mt) { + Uri res = null; + if (mt == MediaType.Audio) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + res = MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY); + } else { + res = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + } else if (mt == MediaType.Video) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + res = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY); + } else { + res = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } + } else if (mt == MediaType.Image) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + res = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY); + } else { + res = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } + } + + return res; + } + + private static String getRelativePath(MediaType mt, ReactApplicationContext ctx) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + if (mt == MediaType.Audio) return Environment.DIRECTORY_MUSIC; + if (mt == MediaType.Video) return Environment.DIRECTORY_MOVIES; + if (mt == MediaType.Image) return Environment.DIRECTORY_PICTURES; + return Environment.DIRECTORY_DOWNLOADS; + } else { + // throw error not supported + return null; + } + } + +// @ReactMethod +// public void writeMediaStoreFile(Promise promise) { +// } +// +// @ReactMethod +// public void getMediaStoreFile(Promise promise) { +// } +// +// @ReactMethod +// public void deleteMediaStoreFile(Promise promise) { +// } +// +// @ReactMethod +// public void exists(String fileName, String mediaType, Promise promise) { +// } + + @ReactMethod + public void createMediaFile(ReadableMap filedata, String mediaType, Promise promise) { + if (!(filedata.hasKey("name") && filedata.hasKey("parentFolder") && filedata.hasKey("mimeType"))) { + promise.reject("RNFS2.createMediaFile", "invalid filedata: " + filedata.toString()); + return; + } + if (mediaType == null) promise.reject("RNFS2.createMediaFile", "invalid mediatype"); + + FileDescription file = new FileDescription(filedata.getString("name"), filedata.getString("mimeType"), filedata.getString("parentFolder")); + Uri res = createNewMediaFile(file, MediaType.valueOf(mediaType), reactContext); + if (res != null) promise.resolve(res.toString()); + else promise.reject("RNFS2.createMediaFile", "File could not be created"); + } + + @ReactMethod + public void writeToMediaFile(String fileUri, String path, boolean transformFile, Promise promise) { + boolean res = writeToMediaFile(Uri.parse(fileUri), path, transformFile, promise, reactContext); + if (res) promise.resolve("Success"); + } + + @ReactMethod + public void copyToMediaStore(ReadableMap filedata, String mediaType, String path, Promise promise) { + if (!(filedata.hasKey("name") && filedata.hasKey("parentFolder") && filedata.hasKey("mimeType"))) { + promise.reject("RNFS2.createMediaFile", "invalid filedata: " + filedata.toString()); + return; + } + if (mediaType == null) { + promise.reject("RNFS2.createMediaFile", "invalid mediatype"); + return; + } + if (path == null) { + promise.reject("RNFS2.createMediaFile", "invalid path"); + return; + } + + FileDescription file = new FileDescription(filedata.getString("name"), filedata.getString("mimeType"), filedata.getString("parentFolder")); + Uri fileuri = createNewMediaFile(file, MediaType.valueOf(mediaType), reactContext); + + if (fileuri == null) { + promise.reject("RNFS2.createMediaFile", "File could not be created"); + return; + } + + boolean res = writeToMediaFile(fileuri, path, false, promise, reactContext); + if (res) promise.resolve(fileuri.toString()); + } + + public static Uri createNewMediaFile(FileDescription file, MediaType mediaType, ReactApplicationContext ctx) { + // Add a specific media item. + Context appCtx = reactContext.getApplicationContext(); + ContentResolver resolver = appCtx.getContentResolver(); + + ContentValues fileDetails = new ContentValues(); + String relativePath = getRelativePath(mediaType, ctx); + String mimeType = file.mimeType; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + fileDetails.put(MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis() / 1000); + fileDetails.put(MediaStore.MediaColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000); + fileDetails.put(MediaStore.MediaColumns.MIME_TYPE, mimeType); + fileDetails.put(MediaStore.MediaColumns.DISPLAY_NAME, file.name); + fileDetails.put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath + '/' + file.partentFolder); + + Uri mediauri = getMediaUri(mediaType); + + try { + // Keeps a handle to the new file's URI in case we need to modify it later. + return resolver.insert(mediauri, fileDetails); + } catch (Exception e) { + return null; + } + } else { + File f = new File(relativePath + file.getFullPath()); + if (true) { + if (!f.exists()) { + File parent = f.getParentFile(); + if (parent != null && !parent.exists() && !parent.mkdirs()) { + return null; + } + try { + if (f.createNewFile()) ; + { + return Uri.fromFile(f); + } + } catch (IOException ioException) { + return null; + } + + } else { + return Uri.fromFile(f); + } + } + } + + return null; + } + + public static boolean writeToMediaFile(Uri fileUri, String data, boolean transformFile, Promise promise, ReactApplicationContext ctx) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + try { + Context appCtx = ctx.getApplicationContext(); + ContentResolver resolver = appCtx.getContentResolver(); + + // set pending doesn't work right now. We would have to requery for the item + //ContentValues contentValues = new ContentValues(); + //contentValues.put(MediaStore.MediaColumns.IS_PENDING, 1); + //resolver.update(fileUri, contentValues, null, null); + + // write data + OutputStream stream = null; + Uri uri = null; + + try { + ParcelFileDescriptor descr; + try { + assert fileUri != null; + descr = appCtx.getContentResolver().openFileDescriptor(fileUri, "w"); + assert descr != null; + File src = new File(data); + if (!src.exists()) { + promise.reject("ENOENT", "No such file ('" + data + "')"); + return false; + } + + + FileInputStream fin = new FileInputStream(src); + FileOutputStream out = new FileOutputStream(descr.getFileDescriptor()); + + if (transformFile) { + // in order to transform file, we must load the entire file onto memory + int length = (int) src.length(); + byte[] bytes = new byte[length]; + fin.read(bytes); + if (RNFSFileTransformer.sharedFileTransformer == null) { + throw new IllegalStateException("Write to media file with transform was specified but the shared file transformer is not set"); + } + byte[] transformedBytes = RNFSFileTransformer.sharedFileTransformer.onWriteFile(bytes); + out.write(transformedBytes); + } else { + byte[] buf = new byte[10240]; + int read; + + while ((read = fin.read(buf)) > 0) { + out.write(buf, 0, read); + } + } + + fin.close(); + out.close(); + descr.close(); + } catch (Exception e) { + e.printStackTrace(); + promise.reject(new IOException("Failed to get output stream.")); + return false; + } + + //contentValues.clear(); + //contentValues.put(MediaStore.Video.Media.IS_PENDING, 0); + //appCtx.getContentResolver().update(fileUri, contentValues, null, null); + stream = resolver.openOutputStream(fileUri); + if (stream == null) { + promise.reject(new IOException("Failed to get output stream.")); + return false; + } + } catch (IOException e) { + // Don't leave an orphan entry in the MediaStore + resolver.delete(uri, null, null); + promise.reject(e); + return false; + } finally { + if (stream != null) { + stream.close(); + } + } + + // remove pending + //contentValues = new ContentValues(); + //contentValues.put(MediaStore.MediaColumns.IS_PENDING, 0); + //resolver.update(fileUri, contentValues, null, null); + + } catch (IOException e) { + promise.reject("RNFS2.createMediaFile", "Cannot write to file, file might not exist"); + return false; + } + + return true; + } else { + // throw error not supported + return false; + } + } + + @Override + public Map getConstants() { + final Map constants = new HashMap<>(); + + constants.put(RNFSMediaStoreTypeAudio, RNFSMediaStoreTypeAudio); + constants.put(RNFSMediaStoreTypeImage, RNFSMediaStoreTypeImage); + constants.put(RNFSMediaStoreTypeVideo, RNFSMediaStoreTypeVideo); + + return constants; + } +} diff --git a/android/src/main/java/com/rnfs2/RNFSPackage.java b/android/src/main/java/com/rnfs2/RNFSPackage.java index 2c85dbb1..12301525 100644 --- a/android/src/main/java/com/rnfs2/RNFSPackage.java +++ b/android/src/main/java/com/rnfs2/RNFSPackage.java @@ -15,7 +15,10 @@ public class RNFSPackage implements ReactPackage { @NonNull @Override public List createNativeModules(ReactApplicationContext reactContext) { - return Arrays.asList(new RNFSManager(reactContext)); + return Arrays.asList( + new RNFSManager(reactContext), + new RNFSMediaStoreManager(reactContext) + ); } @NonNull diff --git a/android/src/main/java/com/rnfs2/Utils/FileDescription.java b/android/src/main/java/com/rnfs2/Utils/FileDescription.java new file mode 100644 index 00000000..0ced4687 --- /dev/null +++ b/android/src/main/java/com/rnfs2/Utils/FileDescription.java @@ -0,0 +1,17 @@ +package com.rnfs2.Utils; + +public class FileDescription { + public String name; + public String partentFolder; + public String mimeType; + + public FileDescription(String n, String mT, String pF) { + name = n; + partentFolder = pF != null ? pF : ""; + mimeType = mT; + } + + public String getFullPath(){ + return partentFolder + "/" + MimeType.getFullFileName(name, mimeType); + } +} diff --git a/android/src/main/java/com/rnfs2/Utils/MimeType.java b/android/src/main/java/com/rnfs2/Utils/MimeType.java new file mode 100644 index 00000000..f9d061f9 --- /dev/null +++ b/android/src/main/java/com/rnfs2/Utils/MimeType.java @@ -0,0 +1,96 @@ +package com.rnfs2.Utils; + +import android.webkit.MimeTypeMap; + +public class MimeType { + static String UNKNOWN = "*/*"; + static String BINARY_FILE = "application/octet-stream"; + static String IMAGE = "image/*"; + static String AUDIO = "audio/*"; + static String VIDEO = "video/*"; + static String TEXT = "text/*"; + static String FONT = "font/*"; + static String APPLICATION = "application/*"; + static String CHEMICAL = "chemical/*"; + static String MODEL = "model/*"; + + /** + * * Given `name` = `ABC` AND `mimeType` = `video/mp4`, then return `ABC.mp4` + * * Given `name` = `ABC` AND `mimeType` = `null`, then return `ABC` + * * Given `name` = `ABC.mp4` AND `mimeType` = `video/mp4`, then return `ABC.mp4` + * + * @param name can have file extension or not + */ + + public static String getFullFileName(String name, String mimeType) { + // Prior to API 29, MimeType.BINARY_FILE has no file extension + String ext = MimeType.getExtensionFromMimeType(mimeType); + if ((ext == null || ext.isEmpty()) || name.endsWith("." + ext)) return name; + else { + String fn = name + "." + ext; + if (fn.endsWith(".")) return stripEnd(fn, "."); + else return fn; + } + } + + /** + * Some mime types return no file extension on older API levels. This function adds compatibility accross API levels. + * + * @see this.getExtensionFromMimeTypeOrFileName + */ + + public static String getExtensionFromMimeType(String mimeType) { + if (mimeType != null) { + if (mimeType.equals(BINARY_FILE)) return "bin"; + else return MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType); + } else return ""; + } + + /** + * @see this.getExtensionFromMimeType + */ + public static String getExtensionFromMimeTypeOrFileName(String mimeType, String filename) { + if (mimeType == null || mimeType.equals(UNKNOWN)) return substringAfterLast(filename, "."); + else return getExtensionFromMimeType(mimeType); + } + + /** + * Some file types return no mime type on older API levels. This function adds compatibility across API levels. + */ + public static String getMimeTypeFromExtension(String fileExtension) { + if (fileExtension.equals("bin")) return BINARY_FILE; + else { + String mt = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension); + if (mt != null) return mt; + else return UNKNOWN; + } + } + + /** + * + */ + public static String stripEnd(String str, String stripChars) { + if (str == null || stripChars == null) { + return str; + } + int end = str.length(); + while (end != 0 && stripChars.indexOf(str.charAt(end - 1)) != -1) { + end--; + } + return str.substring(0, end); + } + + public static String substringAfterLast(String str, String separator) { + if (str == null) { + return null; + } else if (str.isEmpty()) { + return ""; + } else { + int pos = str.lastIndexOf(separator); + if (pos == -1 || pos == str.length() - 1) { + return ""; + } + return str.substring(pos + 1); + } + } +} diff --git a/example/App/example3.tsx b/example/App/example3.tsx new file mode 100644 index 00000000..6ba43587 --- /dev/null +++ b/example/App/example3.tsx @@ -0,0 +1,206 @@ +import React, {useState} from 'react'; +import {Image} from 'react-native'; +import RNFS from 'react-native-fs2'; +import ManageExternalStorage from 'react-native-external-storage-permission'; + +import {StyleSheet, Text, View, Button, Platform, ActivityIndicator, PermissionsAndroid} from 'react-native'; +import {getTestFolder, getFolderText, requestAndroidPermission} from './utils'; + +const DUMMY_IMAGE = + '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'; + +const Example = () => { + const [runningAction, setRunningAction] = useState(false); + const [result, setResult] = useState(''); + const [image, setImage] = useState(''); + + React.useEffect(() => {}, []); + + const copyImageToDocumentsDirectory = async ( + imagePath: string, + imageFileName: string, + folderName = 'UploadedImages', + prefix = '', + overwrite = false, + ) => { + /** + * USE DocumentDirectory on iOS and DownloadDirectory on android + * + * Note: + * DocumentDirectory not accessible on non-rooted android devices + */ + const targetDirectory = getTestFolder(); + + /** + * Create UploadedImages folder if it does not exist + */ + await RNFS.mkdir(`${targetDirectory}/${folderName}`); + + /** + * Create .nomedia folder for android + */ + if (Platform.OS === 'android') { + await RNFS.mkdir(`${targetDirectory}/${folderName}/.nomedia`); + } + + // generate destination path + const destinationFolder = `${targetDirectory}/${folderName}`; + const destinationPath = `${destinationFolder}/${prefix}${imageFileName}`; + + // Check if file already exist on the destination path + // Happens when a queued job fails and is added back to queue again + const fileExist = await RNFS.exists(destinationPath); + if (fileExist) { + // if overwrite flag is true then we replace the file with the new one + if (overwrite) { + try { + // attempt to delete existing file + await RNFS.unlink(destinationPath); + } catch {} + + // copy file to destination path + await RNFS.copyFile(imagePath, destinationPath); + + return destinationPath; + } + + return destinationPath; + } + + // get file stat to ensure file is not corrupted + // and to add another layer of check if RNFS.exists() fails to return the true value + try { + const fileStat = await RNFS.stat(destinationPath); + if (fileStat?.size > 0 && fileStat?.isFile()) { + return destinationPath; + } + } catch { + console.log('File does not exist'); + } + + // otherwise copy file to destination path + await RNFS.copyFile(imagePath, destinationPath); + + return destinationPath; + }; + + /** + * Methods + */ + const executeExample = async () => { + if (Platform.OS === 'android') { + const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.READ_MEDIA_IMAGES, { + title: 'RNFS2 Storage Permission', + message: 'RNFS2 Example App needs read/write access to your phone storage to save images', + buttonNeutral: 'Ask Me Later', + buttonNegative: 'Cancel', + buttonPositive: 'OK', + }); + + if (granted !== PermissionsAndroid.RESULTS.GRANTED) { + /** + * Android 13 does not need this permission anymore, so don't throw error + */ + if (typeof Platform.Version === 'number' && Platform.Version < 33) { + throw new Error('Permission denied'); + } + } + } + + let runStatus = ''; + try { + const dummyImagePath = `${RNFS.DocumentDirectoryPath}/dummyImage2.png`; + const dummyImageFile = await RNFS.exists(dummyImagePath); + + if (!dummyImageFile) { + await RNFS.writeFile(dummyImagePath, DUMMY_IMAGE, 'base64'); + } + + console.log('dummyImagePath', dummyImagePath); + + // await copyImageToDocumentsDirectory(dummyImagePath, 'dummyImage.png'); + + // const contentURI = await RNFS.MediaStore.copyToMediaStore( + // { + // name: 'dummyImage', + // parentFolder: 'RNFSExample3Folder', + // mimeType: 'image/png', + // }, + // 'Image', + // dummyImagePath, + // ); + + // console.log('contentURI', contentURI); + + const contentResult = await RNFS.readFile('content://media/external_primary/images/media/1000000031', 'base64'); + + setImage(`data:image/png;base64,${contentResult}`); + } catch (err) { + console.log(err); + setResult(`${runStatus}\n- Error Running Example`); + } finally { + setRunningAction(false); + } + }; + + return ( + + Example #3 + This example will: + + + - Adds an entry to MEDIASTORE.IMAGE + + + - accesses that image and displays it below: + + + {runningAction && } + {result} + + + {!!image && } + + +