Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] MediaStore Support #68

Merged
merged 19 commits into from
Oct 11, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor: replace "exists" with "query"
  • Loading branch information
ian-wd committed Oct 10, 2024
commit 000d8908c7847c2e8ec9e15395e61afe477680b2
64 changes: 48 additions & 16 deletions android/src/main/java/com/rnfs2/RNFSMediaStoreManager.java
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.util.Log;

import androidx.annotation.RequiresApi;

@@ -23,14 +24,17 @@
import com.facebook.react.module.annotations.ReactModule;

import com.rnfs2.Utils.FileDescription;
import com.rnfs2.Utils.MediaStoreQuery;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@ReactModule(name = RNFSMediaStoreManager.MODULE_NAME)
public class RNFSMediaStoreManager extends ReactContextBaseJavaModule {
@@ -46,9 +50,9 @@ public enum MediaType {
}

private static final String RNFSMediaStoreTypeAudio = MediaType.Audio.toString();
private static final String RNFSMediaStoreTypeImage = MediaType.Image.toString();;
private static final String RNFSMediaStoreTypeVideo = MediaType.Video.toString();;
private static final String RNFSMediaStoreTypeDownload = MediaType.Download.toString();;
private static final String RNFSMediaStoreTypeImage = MediaType.Image.toString();
private static final String RNFSMediaStoreTypeVideo = MediaType.Video.toString();
private static final String RNFSMediaStoreTypeDownload = MediaType.Download.toString();

public RNFSMediaStoreManager(ReactApplicationContext reactContext) {
super(reactContext);
@@ -168,8 +172,8 @@ public void copyToMediaStore(ReadableMap filedata, String mediaType, String path
return;
}
} catch (Exception e) {
promise.reject("RNFS2.copyToMediaStore", "Error opening file: " + e.getMessage());
return;
promise.reject("RNFS2.copyToMediaStore", "Error opening file: " + e.getMessage());
return;
}

FileDescription file = new FileDescription(filedata.getString("name"), filedata.getString("mimeType"), filedata.getString("parentFolder"));
@@ -187,12 +191,13 @@ public void copyToMediaStore(ReadableMap filedata, String mediaType, String path
}

@ReactMethod
public void exists(String fileUri, Promise promise) {
public void query(ReadableMap query, Promise promise) {
try {
boolean fileExists = exists(fileUri, promise, reactContext);
promise.resolve(fileExists);
MediaStoreQuery mediaStoreQuery = new MediaStoreQuery(query.getString("uri"), query.getString("fileName"), query.getString("relativePath"), query.getString("mediaType"));
WritableMap queryResult = query(mediaStoreQuery, promise, reactContext);
promise.resolve(queryResult);
} catch (Exception e) {
promise.reject("RNFS2.exists", "Error checking file existence: " + e.getMessage());
promise.reject("RNFS2.query", "Error checking file existence: " + e.getMessage());
}
}

@@ -260,7 +265,9 @@ private boolean writeToMediaFile(Uri fileUri, String filePath, boolean transform
assert fileUri != null;
descr = appCtx.getContentResolver().openFileDescriptor(fileUri, "w");
assert descr != null;

File src = new File(filePath);

if (!src.exists()) {
promise.reject("ENOENT", "No such file ('" + filePath + "')");
return false;
@@ -325,20 +332,45 @@ private boolean writeToMediaFile(Uri fileUri, String filePath, boolean transform
}
}

private boolean exists(String fileUri, Promise promise, ReactApplicationContext ctx) {
private WritableMap query(MediaStoreQuery query, Promise promise, ReactApplicationContext ctx) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Cursor cursor = null;
try {
Context appCtx = ctx.getApplicationContext();
ContentResolver resolver = appCtx.getContentResolver();
WritableMap queryResultsMap = Arguments.createMap();

Uri mediaURI = !Objects.equals(query.uri, "") ? Uri.parse(query.uri) : getMediaUri(MediaType.valueOf(query.mediaType));
String[] projection = {MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.RELATIVE_PATH};

String selection = null;
String[] selectionArgs = null;

if (Objects.equals(query.uri, "")) {
String relativePath = getRelativePath(MediaType.valueOf(query.mediaType), ctx);
selection = MediaStore.MediaColumns.DISPLAY_NAME + " = ? AND " + MediaStore.MediaColumns.RELATIVE_PATH + " = ?";
selectionArgs = new String[]{query.fileName, relativePath + '/' + query.relativePath + '/'};
}

Uri uri = Uri.parse(fileUri);
String[] projection = {MediaStore.MediaColumns._ID};
cursor = resolver.query(uri, projection, null, null, null);
// query the media store
cursor = resolver.query(mediaURI, projection, selection, selectionArgs, null);

return cursor != null && cursor.getCount() > 0;
if (cursor != null && cursor.moveToFirst()) {
int idColumnIndex = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID);
long id = cursor.getLong(idColumnIndex);

Uri contentUri = Uri.withAppendedPath(mediaURI, String.valueOf(id));

queryResultsMap.putString("contentUri", contentUri.toString());

promise.resolve(queryResultsMap);
} else {
promise.resolve(null);
}

return queryResultsMap;
} catch (Exception e) {
return false;
return null;
} finally {
if (cursor != null) {
cursor.close();
@@ -347,7 +379,7 @@ private boolean exists(String fileUri, Promise promise, ReactApplicationContext
} else {
// throw error not supported
promise.reject("RNFS2.exists", "Android version not supported");
return false;
return null;
}
}

15 changes: 15 additions & 0 deletions android/src/main/java/com/rnfs2/Utils/MediaStoreQuery.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.rnfs2.Utils;

public class MediaStoreQuery {
public String uri;
public String fileName;
public String relativePath;
public String mediaType;

public MediaStoreQuery(String contentURI, String contentFileName, String contentRelativePath, String contentMediaType) {
uri = contentURI != null ? contentURI : "";
fileName = contentFileName != null ? contentFileName : "";
relativePath = contentRelativePath != null ? contentRelativePath : "";
mediaType = contentMediaType;
}
}
21 changes: 13 additions & 8 deletions example/App/example3.tsx
Original file line number Diff line number Diff line change
@@ -16,19 +16,24 @@ const Example = () => {
const copyImageToMediaStore = async (
imagePath: string,
imageFileName: string,
folderName = 'RNFS2Example3Folder',
folderName = 'RNFSExample3Folder',
prefix = '',
overwrite = '',
overwrite = false,
) => {
if (Platform.OS === 'android') {
// Check if file already exist on MediaStore
const contentExists = await RNFS.MediaStore.existsInMediaStore(imagePath);
const contentExists = await RNFS.MediaStore.queryMediaStore({
uri: '',
fileName: imageFileName,
relativePath: folderName,
mediaType: RNFS.MediaStore.MEDIA_IMAGE,
});

if (contentExists) {
// if overwrite flag is true then we replace the file with the new one
if (overwrite) {
// overwrite
await RNFS.MediaStore.writeToMediaFile(imagePath, overwrite);
await RNFS.MediaStore.writeToMediaFile(contentExists.contentUri, imagePath);
}

return imagePath;
@@ -37,7 +42,7 @@ const Example = () => {
const contentURI = await RNFS.MediaStore.copyToMediaStore(
{
name: `${prefix}${imageFileName}`,
parentFolder: 'RNFSExample3Folder',
parentFolder: folderName,
mimeType: 'image/png',
},
RNFS.MediaStore.MEDIA_IMAGE,
@@ -133,11 +138,11 @@ const Example = () => {
}

const contentURI = await copyImageToMediaStore(
imageURI || dummyImagePath,
dummyImagePath,
'dummyImage2.png',
'RNFS2Example3Folder',
'RNFSExample3Folder',
'prefix',
dummyImagePath,
true,
);

setImageURI(contentURI);
6 changes: 4 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -14,6 +14,8 @@
ProcessedOptions,
FileDescriptor,
MediaCollections,
MediaStoreSearchOptions,
MediaStoreQueryResult,
} from './types';

let blobJSIHelper: any;
@@ -138,8 +140,8 @@
return RNFSMediaStoreManager.copyToMediaStore(fileDescriptor, mediatype, normalizeFilePath(path));
},

existsInMediaStore(uri: string): Promise<boolean> {
return RNFSMediaStoreManager.exists(uri);
queryMediaStore(searchOptions: MediaStoreSearchOptions): Promise<MediaStoreQueryResult> {
return RNFSMediaStoreManager.query(searchOptions);
},

deleteFromMediaStore(uri: string): Promise<boolean> {
@@ -154,15 +156,15 @@

export default {
mkdir(filepath: string, options: MkdirOptions = {}): Promise<undefined> {
return RNFSManager.mkdir(normalizeFilePath(filepath), options).then(() => void 0);

Check warning on line 159 in src/index.ts

GitHub Actions / Node 18

Expected 'undefined' and instead saw 'void'
},

moveFile(filepath: string, destPath: string, options: FileOptions = {}): Promise<undefined> {
return RNFSManager.moveFile(normalizeFilePath(filepath), normalizeFilePath(destPath), options).then(() => void 0);

Check warning on line 163 in src/index.ts

GitHub Actions / Node 18

Expected 'undefined' and instead saw 'void'
},

copyFile(filepath: string, destPath: string, options: FileOptions = {}): Promise<undefined> {
return RNFSManager.copyFile(normalizeFilePath(filepath), normalizeFilePath(destPath), options).then(() => void 0);

Check warning on line 167 in src/index.ts

GitHub Actions / Node 18

Expected 'undefined' and instead saw 'void'
},

getFSInfo(): Promise<FSInfoResult> {
@@ -174,7 +176,7 @@
},

unlink(filepath: string): Promise<void> {
return RNFSManager.unlink(normalizeFilePath(filepath)).then(() => void 0);

Check warning on line 179 in src/index.ts

GitHub Actions / Node 18

Expected 'undefined' and instead saw 'void'
},

exists(filepath: string): Promise<boolean> {
@@ -277,7 +279,7 @@
position = -1;
}

return RNFSManager.write(normalizeFilePath(filepath), b64, position).then(() => void 0);

Check warning on line 282 in src/index.ts

GitHub Actions / Node 18

Expected 'undefined' and instead saw 'void'
},

downloadFile(options: DownloadFileOptions): DownloadFileResult {
11 changes: 11 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -85,6 +85,17 @@ export type FileDescriptor = {
mimeType: string;
};

export type MediaStoreSearchOptions = {
uri: string;
fileName: string;
relativePath: string;
mediaType: MediaCollections;
};

export type MediaStoreQueryResult = {
contentUri: string;
};

export type Encoding = 'utf8' | 'base64' | 'ascii' | 'arraybuffer';
export type EncodingOrOptions = Encoding | Record<string, any>;
export type ProcessedOptions = Record<string, any | Encoding>;
Loading