@vali98/react-native-fs 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,7 +18,7 @@ This was not tested on lower Android SDK API's, might not working on Android 9 a
18
18
  ## Installation
19
19
 
20
20
  ```sh
21
- npm install @chatterui/react-native-local-download
21
+ npm install @vali98/react-native-fs
22
22
  ```
23
23
 
24
24
  ## Usage
@@ -35,4 +35,16 @@ public abstract class NativeReactNativeLocalDownloadSpec extends ReactContextBas
35
35
  @ReactMethod
36
36
  @DoNotStrip
37
37
  public abstract void localDownload(String uri, Promise promise);
38
+
39
+ @ReactMethod
40
+ @DoNotStrip
41
+ public abstract void getContentFd(String uri, Promise promise);
42
+
43
+ @ReactMethod
44
+ @DoNotStrip
45
+ public abstract void closeFd(String fd, Promise promise);
46
+
47
+ @ReactMethod
48
+ @DoNotStrip
49
+ public abstract void persistContentPermission(String uri, Promise promise);
38
50
  }
@@ -17,9 +17,27 @@ static facebook::jsi::Value __hostFunction_NativeReactNativeLocalDownloadSpecJSI
17
17
  return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "localDownload", "(Ljava/lang/String;Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
18
18
  }
19
19
 
20
+ static facebook::jsi::Value __hostFunction_NativeReactNativeLocalDownloadSpecJSI_getContentFd(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
21
+ static jmethodID cachedMethodId = nullptr;
22
+ return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "getContentFd", "(Ljava/lang/String;Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
23
+ }
24
+
25
+ static facebook::jsi::Value __hostFunction_NativeReactNativeLocalDownloadSpecJSI_closeFd(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
26
+ static jmethodID cachedMethodId = nullptr;
27
+ return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "closeFd", "(Ljava/lang/String;Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
28
+ }
29
+
30
+ static facebook::jsi::Value __hostFunction_NativeReactNativeLocalDownloadSpecJSI_persistContentPermission(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
31
+ static jmethodID cachedMethodId = nullptr;
32
+ return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "persistContentPermission", "(Ljava/lang/String;Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
33
+ }
34
+
20
35
  NativeReactNativeLocalDownloadSpecJSI::NativeReactNativeLocalDownloadSpecJSI(const JavaTurboModule::InitParams &params)
21
36
  : JavaTurboModule(params) {
22
37
  methodMap_["localDownload"] = MethodMetadata {1, __hostFunction_NativeReactNativeLocalDownloadSpecJSI_localDownload};
38
+ methodMap_["getContentFd"] = MethodMetadata {1, __hostFunction_NativeReactNativeLocalDownloadSpecJSI_getContentFd};
39
+ methodMap_["closeFd"] = MethodMetadata {1, __hostFunction_NativeReactNativeLocalDownloadSpecJSI_closeFd};
40
+ methodMap_["persistContentPermission"] = MethodMetadata {1, __hostFunction_NativeReactNativeLocalDownloadSpecJSI_persistContentPermission};
23
41
  }
24
42
 
25
43
  std::shared_ptr<TurboModule> RNReactNativeLocalDownloadSpec_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params) {
@@ -17,10 +17,31 @@ static jsi::Value __hostFunction_NativeReactNativeLocalDownloadCxxSpecJSI_localD
17
17
  count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt)
18
18
  );
19
19
  }
20
+ static jsi::Value __hostFunction_NativeReactNativeLocalDownloadCxxSpecJSI_getContentFd(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
21
+ return static_cast<NativeReactNativeLocalDownloadCxxSpecJSI *>(&turboModule)->getContentFd(
22
+ rt,
23
+ count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt)
24
+ );
25
+ }
26
+ static jsi::Value __hostFunction_NativeReactNativeLocalDownloadCxxSpecJSI_closeFd(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
27
+ return static_cast<NativeReactNativeLocalDownloadCxxSpecJSI *>(&turboModule)->closeFd(
28
+ rt,
29
+ count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt)
30
+ );
31
+ }
32
+ static jsi::Value __hostFunction_NativeReactNativeLocalDownloadCxxSpecJSI_persistContentPermission(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
33
+ return static_cast<NativeReactNativeLocalDownloadCxxSpecJSI *>(&turboModule)->persistContentPermission(
34
+ rt,
35
+ count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt)
36
+ );
37
+ }
20
38
 
21
39
  NativeReactNativeLocalDownloadCxxSpecJSI::NativeReactNativeLocalDownloadCxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker)
22
40
  : TurboModule("ReactNativeLocalDownload", jsInvoker) {
23
41
  methodMap_["localDownload"] = MethodMetadata {1, __hostFunction_NativeReactNativeLocalDownloadCxxSpecJSI_localDownload};
42
+ methodMap_["getContentFd"] = MethodMetadata {1, __hostFunction_NativeReactNativeLocalDownloadCxxSpecJSI_getContentFd};
43
+ methodMap_["closeFd"] = MethodMetadata {1, __hostFunction_NativeReactNativeLocalDownloadCxxSpecJSI_closeFd};
44
+ methodMap_["persistContentPermission"] = MethodMetadata {1, __hostFunction_NativeReactNativeLocalDownloadCxxSpecJSI_persistContentPermission};
24
45
  }
25
46
 
26
47
 
@@ -21,6 +21,9 @@ protected:
21
21
 
22
22
  public:
23
23
  virtual jsi::Value localDownload(jsi::Runtime &rt, jsi::String uri) = 0;
24
+ virtual jsi::Value getContentFd(jsi::Runtime &rt, jsi::String uri) = 0;
25
+ virtual jsi::Value closeFd(jsi::Runtime &rt, jsi::String fd) = 0;
26
+ virtual jsi::Value persistContentPermission(jsi::Runtime &rt, jsi::String uri) = 0;
24
27
 
25
28
  };
26
29
 
@@ -59,6 +62,30 @@ private:
59
62
  return bridging::callFromJs<jsi::Value>(
60
63
  rt, &T::localDownload, jsInvoker_, instance_, std::move(uri));
61
64
  }
65
+ jsi::Value getContentFd(jsi::Runtime &rt, jsi::String uri) override {
66
+ static_assert(
67
+ bridging::getParameterCount(&T::getContentFd) == 2,
68
+ "Expected getContentFd(...) to have 2 parameters");
69
+
70
+ return bridging::callFromJs<jsi::Value>(
71
+ rt, &T::getContentFd, jsInvoker_, instance_, std::move(uri));
72
+ }
73
+ jsi::Value closeFd(jsi::Runtime &rt, jsi::String fd) override {
74
+ static_assert(
75
+ bridging::getParameterCount(&T::closeFd) == 2,
76
+ "Expected closeFd(...) to have 2 parameters");
77
+
78
+ return bridging::callFromJs<jsi::Value>(
79
+ rt, &T::closeFd, jsInvoker_, instance_, std::move(fd));
80
+ }
81
+ jsi::Value persistContentPermission(jsi::Runtime &rt, jsi::String uri) override {
82
+ static_assert(
83
+ bridging::getParameterCount(&T::persistContentPermission) == 2,
84
+ "Expected persistContentPermission(...) to have 2 parameters");
85
+
86
+ return bridging::callFromJs<jsi::Value>(
87
+ rt, &T::persistContentPermission, jsInvoker_, instance_, std::move(uri));
88
+ }
62
89
 
63
90
  private:
64
91
  friend class NativeReactNativeLocalDownloadCxxSpec;
@@ -1,91 +1,181 @@
1
1
  package com.chatterui.reactnativelocaldownload
2
2
 
3
- import com.facebook.react.bridge.ReactApplicationContext
4
- import com.facebook.react.bridge.Promise
5
- import com.facebook.react.module.annotations.ReactModule
6
-
7
- import android.content.ContentValues
8
3
  import android.content.ContentResolver
4
+ import android.content.ContentValues
5
+ import android.content.Intent
9
6
  import android.net.Uri
7
+ import android.os.ParcelFileDescriptor
10
8
  import android.provider.MediaStore
9
+ import android.system.Os
10
+ import com.facebook.react.bridge.Promise
11
+ import com.facebook.react.bridge.ReactApplicationContext
12
+ import com.facebook.react.module.annotations.ReactModule
11
13
  import java.io.File
12
14
  import java.io.FileInputStream
13
15
  import java.io.FileOutputStream
14
16
  import java.net.URLConnection
15
17
 
16
18
  @ReactModule(name = ReactNativeLocalDownloadModule.NAME)
17
- class ReactNativeLocalDownloadModule(private val reactContext: ReactApplicationContext) :
18
- NativeReactNativeLocalDownloadSpec(reactContext) {
19
+ class ReactNativeLocalDownloadModule(
20
+ private val reactContext: ReactApplicationContext,
21
+ ) : NativeReactNativeLocalDownloadSpec(reactContext) {
22
+ override fun getName(): String = NAME
23
+
24
+ override fun getContentFd(
25
+ contentUri: String,
26
+ promise: Promise,
27
+ ) {
28
+ try {
29
+ val uri = Uri.parse(contentUri)
30
+
31
+ val pfd =
32
+ reactContext.contentResolver
33
+ .openFileDescriptor(uri, "r")
34
+ ?: run {
35
+ promise.reject("FD_OPEN_FAILED", "Unable to open content URI")
36
+ return
37
+ }
38
+
39
+ val fd = pfd.detachFd()
40
+ promise.resolve("$fd")
41
+ } catch (e: Exception) {
42
+ promise.reject(
43
+ "GET_FD_ERROR",
44
+ "Failed to get detached FD: ${e.message}",
45
+ e,
46
+ )
47
+ }
48
+ }
49
+
50
+ override fun closeFd(
51
+ fdOrPath: String,
52
+ promise: Promise,
53
+ ) {
54
+ try {
55
+ val fdInt =
56
+ when {
57
+ fdOrPath.startsWith("/proc/") -> {
58
+ fdOrPath.substringAfterLast("/").toInt()
59
+ }
60
+
61
+ else -> {
62
+ fdOrPath.toInt()
63
+ }
64
+ }
65
+ ParcelFileDescriptor.adoptFd(fdInt).close()
66
+ promise.resolve(true)
67
+ } catch (e: Exception) {
68
+ promise.reject(
69
+ "FD_CLOSE_ERROR",
70
+ "Failed to close FD: ${e.message}",
71
+ e,
72
+ )
73
+ }
74
+ }
75
+
76
+ override fun persistContentPermission(
77
+ uriString: String,
78
+ promise: Promise,
79
+ ) {
80
+ try {
81
+ val uri = Uri.parse(uriString)
19
82
 
20
- override fun getName(): String {
21
- return NAME
83
+ val flags =
84
+ Intent.FLAG_GRANT_READ_URI_PERMISSION or
85
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION
86
+
87
+ reactContext.contentResolver
88
+ .takePersistableUriPermission(uri, flags)
89
+
90
+ promise.resolve(true)
91
+ } catch (e: SecurityException) {
92
+ promise.reject(
93
+ "PERSIST_PERMISSION_DENIED",
94
+ "Persistable permission not granted for this URI",
95
+ e,
96
+ )
97
+ } catch (e: Exception) {
98
+ promise.reject(
99
+ "PERSIST_PERMISSION_ERROR",
100
+ e.message,
101
+ e,
102
+ )
103
+ }
22
104
  }
23
105
 
24
- override fun localDownload(uri: String, promise: Promise) {
106
+ override fun localDownload(
107
+ uri: String,
108
+ promise: Promise,
109
+ ) {
25
110
  try {
26
111
  val inputFile = File(uri)
27
112
  if (!inputFile.exists()) {
28
113
  promise.reject("FILE_NOT_FOUND", "File does not exist at path: $uri")
29
114
  return
30
115
  }
31
-
116
+
32
117
  val fileName = inputFile.name
33
118
  val mimeType = URLConnection.guessContentTypeFromName(inputFile.name) ?: "application/octet-stream"
34
- val resolver : ContentResolver = reactContext.contentResolver
119
+ val resolver: ContentResolver = reactContext.contentResolver
35
120
  val uniqueName = getUniqueFileName(resolver, fileName)
36
- val contentValues = ContentValues().apply {
37
- put(MediaStore.Downloads.DISPLAY_NAME, uniqueName)
38
- put(MediaStore.Downloads.MIME_TYPE, mimeType)
39
- put(MediaStore.Downloads.IS_PENDING, 1)
40
- }
41
-
121
+ val contentValues =
122
+ ContentValues().apply {
123
+ put(MediaStore.Downloads.DISPLAY_NAME, uniqueName)
124
+ put(MediaStore.Downloads.MIME_TYPE, mimeType)
125
+ put(MediaStore.Downloads.IS_PENDING, 1)
126
+ }
127
+
42
128
  val collection = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
43
129
  val itemUri = resolver.insert(collection, contentValues)
44
-
130
+
45
131
  if (itemUri == null) {
46
132
  promise.reject("SAVE_ERROR", "Failed to create destination file in MediaStore.")
47
133
  return
48
134
  }
49
-
135
+
50
136
  resolver.openOutputStream(itemUri)?.use { outputStream ->
51
137
  inputFile.inputStream().use { inputStream ->
52
138
  inputStream.copyTo(outputStream)
53
139
  }
54
140
  }
55
-
141
+
56
142
  // Mark the item as not pending so it's visible to user
57
143
  contentValues.clear()
58
144
  contentValues.put(MediaStore.Downloads.IS_PENDING, 0)
59
145
  resolver.update(itemUri, contentValues, null, null)
60
-
146
+
61
147
  promise.resolve(itemUri.toString())
62
148
  } catch (e: Exception) {
63
149
  promise.reject("DOWNLOAD_ERROR", "Failed to save file via MediaStore: ${e.message}", e)
64
150
  }
65
151
  }
66
-
67
- private fun getUniqueFileName(resolver: ContentResolver, baseName: String): String {
152
+
153
+ private fun getUniqueFileName(
154
+ resolver: ContentResolver,
155
+ baseName: String,
156
+ ): String {
68
157
  var name = baseName
69
158
  val nameWithoutExtension = File(baseName).nameWithoutExtension
70
159
  val extension = File(baseName).extension
71
160
  var index = 1
72
-
161
+
73
162
  val collection = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
74
-
163
+
75
164
  val projection = arrayOf(MediaStore.Downloads.DISPLAY_NAME)
76
165
  val selection = "${MediaStore.Downloads.DISPLAY_NAME} = ?"
77
166
  val selectionArgs = arrayOf(name)
78
-
167
+
79
168
  while (resolver.query(collection, projection, selection, selectionArgs, null)?.use { it.moveToFirst() } == true) {
80
- name = if (extension.isNotEmpty()) {
81
- "$nameWithoutExtension ($index).$extension"
82
- } else {
83
- "$nameWithoutExtension ($index)"
84
- }
169
+ name =
170
+ if (extension.isNotEmpty()) {
171
+ "$nameWithoutExtension ($index).$extension"
172
+ } else {
173
+ "$nameWithoutExtension ($index)"
174
+ }
85
175
  selectionArgs[0] = name
86
176
  index++
87
177
  }
88
-
178
+
89
179
  return name
90
180
  }
91
181
 
@@ -26,10 +26,39 @@ RCT_EXPORT_MODULE()
26
26
  });
27
27
  }
28
28
 
29
+ #pragma mark - Android-only FD stubs (iOS)
30
+
31
+ - (void)getContentFd:(NSString *)uri
32
+ resolve:(RCTPromiseResolveBlock)resolve
33
+ reject:(RCTPromiseRejectBlock)reject
34
+ {
35
+ reject(
36
+ @"UNSUPPORTED_PLATFORM",
37
+ @"getContentFd is not supported on iOS",
38
+ nil
39
+ );
40
+ }
41
+
42
+ - (void)closeFd:(NSString *)fdOrPath
43
+ resolve:(RCTPromiseResolveBlock)resolve
44
+ reject:(RCTPromiseRejectBlock)reject
45
+ {
46
+ // No-op stub for iOS
47
+ resolve(@(YES));
48
+ }
49
+
29
50
  - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
30
51
  (const facebook::react::ObjCTurboModule::InitParams &)params
31
52
  {
32
53
  return std::make_shared<facebook::react::NativeReactNativeLocalDownloadSpecJSI>(params);
33
54
  }
34
55
 
56
+ - (void)persistContentPermission:(NSString *)uri
57
+ resolve:(RCTPromiseResolveBlock)resolve
58
+ reject:(RCTPromiseRejectBlock)reject
59
+ {
60
+ // iOS sandbox does not support persistable URI permissions
61
+ resolve(@(YES));
62
+ }
63
+
35
64
  @end
@@ -30,10 +30,31 @@ namespace facebook::react {
30
30
  return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, PromiseKind, "localDownload", @selector(localDownload:resolve:reject:), args, count);
31
31
  }
32
32
 
33
+ static facebook::jsi::Value __hostFunction_NativeReactNativeLocalDownloadSpecJSI_getContentFd(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
34
+ return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, PromiseKind, "getContentFd", @selector(getContentFd:resolve:reject:), args, count);
35
+ }
36
+
37
+ static facebook::jsi::Value __hostFunction_NativeReactNativeLocalDownloadSpecJSI_closeFd(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
38
+ return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, PromiseKind, "closeFd", @selector(closeFd:resolve:reject:), args, count);
39
+ }
40
+
41
+ static facebook::jsi::Value __hostFunction_NativeReactNativeLocalDownloadSpecJSI_persistContentPermission(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
42
+ return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, PromiseKind, "persistContentPermission", @selector(persistContentPermission:resolve:reject:), args, count);
43
+ }
44
+
33
45
  NativeReactNativeLocalDownloadSpecJSI::NativeReactNativeLocalDownloadSpecJSI(const ObjCTurboModule::InitParams &params)
34
46
  : ObjCTurboModule(params) {
35
47
 
36
48
  methodMap_["localDownload"] = MethodMetadata {1, __hostFunction_NativeReactNativeLocalDownloadSpecJSI_localDownload};
37
49
 
50
+
51
+ methodMap_["getContentFd"] = MethodMetadata {1, __hostFunction_NativeReactNativeLocalDownloadSpecJSI_getContentFd};
52
+
53
+
54
+ methodMap_["closeFd"] = MethodMetadata {1, __hostFunction_NativeReactNativeLocalDownloadSpecJSI_closeFd};
55
+
56
+
57
+ methodMap_["persistContentPermission"] = MethodMetadata {1, __hostFunction_NativeReactNativeLocalDownloadSpecJSI_persistContentPermission};
58
+
38
59
  }
39
60
  } // namespace facebook::react
@@ -38,6 +38,15 @@ NS_ASSUME_NONNULL_BEGIN
38
38
  - (void)localDownload:(NSString *)uri
39
39
  resolve:(RCTPromiseResolveBlock)resolve
40
40
  reject:(RCTPromiseRejectBlock)reject;
41
+ - (void)getContentFd:(NSString *)uri
42
+ resolve:(RCTPromiseResolveBlock)resolve
43
+ reject:(RCTPromiseRejectBlock)reject;
44
+ - (void)closeFd:(NSString *)fd
45
+ resolve:(RCTPromiseResolveBlock)resolve
46
+ reject:(RCTPromiseRejectBlock)reject;
47
+ - (void)persistContentPermission:(NSString *)uri
48
+ resolve:(RCTPromiseResolveBlock)resolve
49
+ reject:(RCTPromiseRejectBlock)reject;
41
50
 
42
51
  @end
43
52
 
@@ -17,10 +17,31 @@ static jsi::Value __hostFunction_NativeReactNativeLocalDownloadCxxSpecJSI_localD
17
17
  count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt)
18
18
  );
19
19
  }
20
+ static jsi::Value __hostFunction_NativeReactNativeLocalDownloadCxxSpecJSI_getContentFd(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
21
+ return static_cast<NativeReactNativeLocalDownloadCxxSpecJSI *>(&turboModule)->getContentFd(
22
+ rt,
23
+ count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt)
24
+ );
25
+ }
26
+ static jsi::Value __hostFunction_NativeReactNativeLocalDownloadCxxSpecJSI_closeFd(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
27
+ return static_cast<NativeReactNativeLocalDownloadCxxSpecJSI *>(&turboModule)->closeFd(
28
+ rt,
29
+ count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt)
30
+ );
31
+ }
32
+ static jsi::Value __hostFunction_NativeReactNativeLocalDownloadCxxSpecJSI_persistContentPermission(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
33
+ return static_cast<NativeReactNativeLocalDownloadCxxSpecJSI *>(&turboModule)->persistContentPermission(
34
+ rt,
35
+ count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt)
36
+ );
37
+ }
20
38
 
21
39
  NativeReactNativeLocalDownloadCxxSpecJSI::NativeReactNativeLocalDownloadCxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker)
22
40
  : TurboModule("ReactNativeLocalDownload", jsInvoker) {
23
41
  methodMap_["localDownload"] = MethodMetadata {1, __hostFunction_NativeReactNativeLocalDownloadCxxSpecJSI_localDownload};
42
+ methodMap_["getContentFd"] = MethodMetadata {1, __hostFunction_NativeReactNativeLocalDownloadCxxSpecJSI_getContentFd};
43
+ methodMap_["closeFd"] = MethodMetadata {1, __hostFunction_NativeReactNativeLocalDownloadCxxSpecJSI_closeFd};
44
+ methodMap_["persistContentPermission"] = MethodMetadata {1, __hostFunction_NativeReactNativeLocalDownloadCxxSpecJSI_persistContentPermission};
24
45
  }
25
46
 
26
47
 
@@ -21,6 +21,9 @@ protected:
21
21
 
22
22
  public:
23
23
  virtual jsi::Value localDownload(jsi::Runtime &rt, jsi::String uri) = 0;
24
+ virtual jsi::Value getContentFd(jsi::Runtime &rt, jsi::String uri) = 0;
25
+ virtual jsi::Value closeFd(jsi::Runtime &rt, jsi::String fd) = 0;
26
+ virtual jsi::Value persistContentPermission(jsi::Runtime &rt, jsi::String uri) = 0;
24
27
 
25
28
  };
26
29
 
@@ -59,6 +62,30 @@ private:
59
62
  return bridging::callFromJs<jsi::Value>(
60
63
  rt, &T::localDownload, jsInvoker_, instance_, std::move(uri));
61
64
  }
65
+ jsi::Value getContentFd(jsi::Runtime &rt, jsi::String uri) override {
66
+ static_assert(
67
+ bridging::getParameterCount(&T::getContentFd) == 2,
68
+ "Expected getContentFd(...) to have 2 parameters");
69
+
70
+ return bridging::callFromJs<jsi::Value>(
71
+ rt, &T::getContentFd, jsInvoker_, instance_, std::move(uri));
72
+ }
73
+ jsi::Value closeFd(jsi::Runtime &rt, jsi::String fd) override {
74
+ static_assert(
75
+ bridging::getParameterCount(&T::closeFd) == 2,
76
+ "Expected closeFd(...) to have 2 parameters");
77
+
78
+ return bridging::callFromJs<jsi::Value>(
79
+ rt, &T::closeFd, jsInvoker_, instance_, std::move(fd));
80
+ }
81
+ jsi::Value persistContentPermission(jsi::Runtime &rt, jsi::String uri) override {
82
+ static_assert(
83
+ bridging::getParameterCount(&T::persistContentPermission) == 2,
84
+ "Expected persistContentPermission(...) to have 2 parameters");
85
+
86
+ return bridging::callFromJs<jsi::Value>(
87
+ rt, &T::persistContentPermission, jsInvoker_, instance_, std::move(uri));
88
+ }
62
89
 
63
90
  private:
64
91
  friend class NativeReactNativeLocalDownloadCxxSpec;
@@ -2,7 +2,12 @@ import type { TurboModule } from 'react-native';
2
2
  import { TurboModuleRegistry } from 'react-native';
3
3
 
4
4
  export interface Spec extends TurboModule {
5
- localDownload(uri: string): Promise<void>
5
+ localDownload(uri: string): Promise<void>;
6
+ getContentFd(uri: string): Promise<string>;
7
+ closeFd(fd: string): Promise<void>;
8
+ persistContentPermission(uri: string): Promise<void>;
6
9
  }
7
10
 
8
- export default TurboModuleRegistry.getEnforcing<Spec>('ReactNativeLocalDownload');
11
+ export default TurboModuleRegistry.getEnforcing<Spec>(
12
+ 'ReactNativeLocalDownload'
13
+ );
@@ -2,6 +2,21 @@
2
2
 
3
3
  import ReactNativeLocalDownload from './NativeReactNativeLocalDownload';
4
4
  import { PermissionsAndroid, Platform, Linking, Alert } from 'react-native';
5
+ export async function getContentFd(uri) {
6
+ if (Platform.OS !== 'android') return;
7
+ if (!uri.includes('content://')) {
8
+ throw new TypeError('URI provided is not a content URI');
9
+ }
10
+ return ReactNativeLocalDownload.getContentFd(uri);
11
+ }
12
+ export async function closeFd(uri) {
13
+ if (Platform.OS !== 'android') return;
14
+ return ReactNativeLocalDownload.closeFd(uri);
15
+ }
16
+ export async function persistContentPermission(uri) {
17
+ if (Platform.OS !== 'android') return;
18
+ return ReactNativeLocalDownload.persistContentPermission(uri);
19
+ }
5
20
  /**
6
21
  *
7
22
  * @param uri path to content to be sent to downloads
@@ -1 +1 @@
1
- {"version":3,"names":["ReactNativeLocalDownload","PermissionsAndroid","Platform","Linking","Alert","localDownload","uri","never_ask_function","granted","requestStoragePermission","RESULTS","NEVER_ASK_AGAIN","alert","text","style","onPress","openSettings","Error","OS","GRANTED","Number","Version","hasPermission","check","PERMISSIONS","WRITE_EXTERNAL_STORAGE","request","title","message","buttonPositive","err","console","warn","DENIED"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,OAAOA,wBAAwB,MAAM,kCAAkC;AAEvE,SAASC,kBAAkB,EAAEC,QAAQ,EAAEC,OAAO,EAAEC,KAAK,QAAQ,cAAc;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,eAAeC,aAAaA,CAC/BC,GAAW,EACXC,kBAA2C,EAC9B;EACb,MAAMC,OAAO,GAAG,MAAMC,wBAAwB,CAAC,CAAC;EAChD,IAAID,OAAO,KAAK,SAAS,EAAE;IACvB,IAAIA,OAAO,KAAKP,kBAAkB,CAACS,OAAO,CAACC,eAAe,EAAE;MACxD,IAAI,CAACJ,kBAAkB,EACnBH,KAAK,CAACQ,KAAK,CACP,qBAAqB,EACrB,6EAA6E,EAC7E,CACI;QAAEC,IAAI,EAAE,QAAQ;QAAEC,KAAK,EAAE;MAAS,CAAC,EACnC;QAAED,IAAI,EAAE,eAAe;QAAEE,OAAO,EAAEA,CAAA,KAAMZ,OAAO,CAACa,YAAY,CAAC;MAAE,CAAC,CAExE,CAAC,MACAT,kBAAkB,CAAC,CAAC;IAC7B;IACA,MAAM,IAAIU,KAAK,CAAC,yBAAyB,CAAC;EAC9C;EACA,OAAOjB,wBAAwB,CAACK,aAAa,CAACC,GAAG,CAAC;AACtD;AAEA,OAAO,MAAMG,wBAAwB,GAAG,MAAAA,CAAA,KAEnC;EACD,IAAIP,QAAQ,CAACgB,EAAE,KAAK,SAAS,EAAE,OAAOjB,kBAAkB,CAACS,OAAO,CAACS,OAAO;EAExE,IAAI;IACA,IAAIC,MAAM,CAAClB,QAAQ,CAACmB,OAAO,CAAC,IAAI,EAAE,EAAE;MAChC,OAAO,SAAS;IACpB;IAEA,MAAMC,aAAa,GAAG,MAAMrB,kBAAkB,CAACsB,KAAK,CAChDtB,kBAAkB,CAACuB,WAAW,CAACC,sBACnC,CAAC;IACD,IAAIH,aAAa,EAAE;MACf,OAAO,SAAS;IACpB;IAEA,MAAMd,OAAO,GAAG,MAAMP,kBAAkB,CAACyB,OAAO,CAC5CzB,kBAAkB,CAACuB,WAAW,CAACC,sBAAsB,EACrD;MACIE,KAAK,EAAE,oBAAoB;MAC3BC,OAAO,EAAE,0DAA0D;MACnEC,cAAc,EAAE;IACpB,CACJ,CAAC;IACD,OAAOrB,OAAO;EAClB,CAAC,CAAC,OAAOsB,GAAG,EAAE;IACVC,OAAO,CAACC,IAAI,CAACF,GAAG,CAAC;IACjB,OAAO7B,kBAAkB,CAACS,OAAO,CAACuB,MAAM;EAC5C;AACJ,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["ReactNativeLocalDownload","PermissionsAndroid","Platform","Linking","Alert","getContentFd","uri","OS","includes","TypeError","closeFd","persistContentPermission","localDownload","never_ask_function","granted","requestStoragePermission","RESULTS","NEVER_ASK_AGAIN","alert","text","style","onPress","openSettings","Error","GRANTED","Number","Version","hasPermission","check","PERMISSIONS","WRITE_EXTERNAL_STORAGE","request","title","message","buttonPositive","err","console","warn","DENIED"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,OAAOA,wBAAwB,MAAM,kCAAkC;AAEvE,SAASC,kBAAkB,EAAEC,QAAQ,EAAEC,OAAO,EAAEC,KAAK,QAAQ,cAAc;AAE3E,OAAO,eAAeC,YAAYA,CAACC,GAAW,EAAE;EAC9C,IAAIJ,QAAQ,CAACK,EAAE,KAAK,SAAS,EAAE;EAC/B,IAAI,CAACD,GAAG,CAACE,QAAQ,CAAC,YAAY,CAAC,EAAE;IAC/B,MAAM,IAAIC,SAAS,CAAC,mCAAmC,CAAC;EAC1D;EACA,OAAOT,wBAAwB,CAACK,YAAY,CAACC,GAAG,CAAC;AACnD;AAEA,OAAO,eAAeI,OAAOA,CAACJ,GAAW,EAAE;EACzC,IAAIJ,QAAQ,CAACK,EAAE,KAAK,SAAS,EAAE;EAC/B,OAAOP,wBAAwB,CAACU,OAAO,CAACJ,GAAG,CAAC;AAC9C;AAEA,OAAO,eAAeK,wBAAwBA,CAACL,GAAW,EAAE;EAC1D,IAAIJ,QAAQ,CAACK,EAAE,KAAK,SAAS,EAAE;EAC/B,OAAOP,wBAAwB,CAACW,wBAAwB,CAACL,GAAG,CAAC;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,eAAeM,aAAaA,CACjCN,GAAW,EACXO,kBAA2C,EAC5B;EACf,MAAMC,OAAO,GAAG,MAAMC,wBAAwB,CAAC,CAAC;EAChD,IAAID,OAAO,KAAK,SAAS,EAAE;IACzB,IAAIA,OAAO,KAAKb,kBAAkB,CAACe,OAAO,CAACC,eAAe,EAAE;MAC1D,IAAI,CAACJ,kBAAkB,EACrBT,KAAK,CAACc,KAAK,CACT,qBAAqB,EACrB,6EAA6E,EAC7E,CACE;QAAEC,IAAI,EAAE,QAAQ;QAAEC,KAAK,EAAE;MAAS,CAAC,EACnC;QAAED,IAAI,EAAE,eAAe;QAAEE,OAAO,EAAEA,CAAA,KAAMlB,OAAO,CAACmB,YAAY,CAAC;MAAE,CAAC,CAEpE,CAAC,CAAC,KACCT,kBAAkB,CAAC,CAAC;IAC3B;IACA,MAAM,IAAIU,KAAK,CAAC,yBAAyB,CAAC;EAC5C;EACA,OAAOvB,wBAAwB,CAACY,aAAa,CAACN,GAAG,CAAC;AACpD;AAEA,OAAO,MAAMS,wBAAwB,GAAG,MAAAA,CAAA,KAEnC;EACH,IAAIb,QAAQ,CAACK,EAAE,KAAK,SAAS,EAAE,OAAON,kBAAkB,CAACe,OAAO,CAACQ,OAAO;EAExE,IAAI;IACF,IAAIC,MAAM,CAACvB,QAAQ,CAACwB,OAAO,CAAC,IAAI,EAAE,EAAE;MAClC,OAAO,SAAS;IAClB;IAEA,MAAMC,aAAa,GAAG,MAAM1B,kBAAkB,CAAC2B,KAAK,CAClD3B,kBAAkB,CAAC4B,WAAW,CAACC,sBACjC,CAAC;IACD,IAAIH,aAAa,EAAE;MACjB,OAAO,SAAS;IAClB;IAEA,MAAMb,OAAO,GAAG,MAAMb,kBAAkB,CAAC8B,OAAO,CAC9C9B,kBAAkB,CAAC4B,WAAW,CAACC,sBAAsB,EACrD;MACEE,KAAK,EAAE,oBAAoB;MAC3BC,OAAO,EAAE,0DAA0D;MACnEC,cAAc,EAAE;IAClB,CACF,CAAC;IACD,OAAOpB,OAAO;EAChB,CAAC,CAAC,OAAOqB,GAAG,EAAE;IACZC,OAAO,CAACC,IAAI,CAACF,GAAG,CAAC;IACjB,OAAOlC,kBAAkB,CAACe,OAAO,CAACsB,MAAM;EAC1C;AACF,CAAC","ignoreList":[]}
@@ -1,6 +1,9 @@
1
1
  import type { TurboModule } from 'react-native';
2
2
  export interface Spec extends TurboModule {
3
3
  localDownload(uri: string): Promise<void>;
4
+ getContentFd(uri: string): Promise<string>;
5
+ closeFd(fd: string): Promise<void>;
6
+ persistContentPermission(uri: string): Promise<void>;
4
7
  }
5
8
  declare const _default: Spec;
6
9
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"file":"NativeReactNativeLocalDownload.d.ts","sourceRoot":"","sources":["../../../src/NativeReactNativeLocalDownload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1C;;AAED,wBAAkF"}
1
+ {"version":3,"file":"NativeReactNativeLocalDownload.d.ts","sourceRoot":"","sources":["../../../src/NativeReactNativeLocalDownload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3C,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD;;AAED,wBAEE"}
@@ -1,4 +1,7 @@
1
1
  import { PermissionsAndroid } from 'react-native';
2
+ export declare function getContentFd(uri: string): Promise<string | undefined>;
3
+ export declare function closeFd(uri: string): Promise<void>;
4
+ export declare function persistContentPermission(uri: string): Promise<void>;
2
5
  /**
3
6
  *
4
7
  * @param uri path to content to be sent to downloads
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAA4B,MAAM,cAAc,CAAA;AAC3E;;;;;GAKG;AACH,wBAAsB,aAAa,CAC/B,GAAG,EAAE,MAAM,EACX,kBAAkB,CAAC,EAAE,MAAM,IAAI,GAAG,SAAS,GAC5C,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED,eAAO,MAAM,wBAAwB,QAAa,OAAO,CACrD,OAAO,kBAAkB,CAAC,OAAO,CAAC,MAAM,CA6B3C,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAA4B,MAAM,cAAc,CAAC;AAE5E,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,+BAM7C;AAED,wBAAsB,OAAO,CAAC,GAAG,EAAE,MAAM,iBAGxC;AAED,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,MAAM,iBAGzD;AACD;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,kBAAkB,CAAC,EAAE,MAAM,IAAI,GAAG,SAAS,GAC1C,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED,eAAO,MAAM,wBAAwB,QAAa,OAAO,CACvD,OAAO,kBAAkB,CAAC,OAAO,CAAC,MAAM,CA6BzC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vali98/react-native-fs",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Download folder access for react-native",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/module/index.js",
@@ -2,7 +2,12 @@ import type { TurboModule } from 'react-native';
2
2
  import { TurboModuleRegistry } from 'react-native';
3
3
 
4
4
  export interface Spec extends TurboModule {
5
- localDownload(uri: string): Promise<void>
5
+ localDownload(uri: string): Promise<void>;
6
+ getContentFd(uri: string): Promise<string>;
7
+ closeFd(fd: string): Promise<void>;
8
+ persistContentPermission(uri: string): Promise<void>;
6
9
  }
7
10
 
8
- export default TurboModuleRegistry.getEnforcing<Spec>('ReactNativeLocalDownload');
11
+ export default TurboModuleRegistry.getEnforcing<Spec>(
12
+ 'ReactNativeLocalDownload'
13
+ );
package/src/index.tsx CHANGED
@@ -1,6 +1,24 @@
1
1
  import ReactNativeLocalDownload from './NativeReactNativeLocalDownload';
2
2
 
3
- import { PermissionsAndroid, Platform, Linking, Alert } from 'react-native'
3
+ import { PermissionsAndroid, Platform, Linking, Alert } from 'react-native';
4
+
5
+ export async function getContentFd(uri: string) {
6
+ if (Platform.OS !== 'android') return;
7
+ if (!uri.includes('content://')) {
8
+ throw new TypeError('URI provided is not a content URI');
9
+ }
10
+ return ReactNativeLocalDownload.getContentFd(uri);
11
+ }
12
+
13
+ export async function closeFd(uri: string) {
14
+ if (Platform.OS !== 'android') return;
15
+ return ReactNativeLocalDownload.closeFd(uri);
16
+ }
17
+
18
+ export async function persistContentPermission(uri: string) {
19
+ if (Platform.OS !== 'android') return;
20
+ return ReactNativeLocalDownload.persistContentPermission(uri);
21
+ }
4
22
  /**
5
23
  *
6
24
  * @param uri path to content to be sent to downloads
@@ -8,57 +26,56 @@ import { PermissionsAndroid, Platform, Linking, Alert } from 'react-native'
8
26
  * @returns
9
27
  */
10
28
  export async function localDownload(
11
- uri: string,
12
- never_ask_function?: () => void | undefined
29
+ uri: string,
30
+ never_ask_function?: () => void | undefined
13
31
  ): Promise<void> {
14
- const granted = await requestStoragePermission()
15
- if (granted !== 'granted') {
16
- if (granted === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN) {
17
- if (!never_ask_function)
18
- Alert.alert(
19
- 'Permission Required',
20
- 'You have permanently denied storage access. Please enable it from settings.',
21
- [
22
- { text: 'Cancel', style: 'cancel' },
23
- { text: 'Open Settings', onPress: () => Linking.openSettings() },
24
- ]
25
- )
26
- else never_ask_function()
27
- }
28
- throw new Error('Permissions not granted')
32
+ const granted = await requestStoragePermission();
33
+ if (granted !== 'granted') {
34
+ if (granted === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN) {
35
+ if (!never_ask_function)
36
+ Alert.alert(
37
+ 'Permission Required',
38
+ 'You have permanently denied storage access. Please enable it from settings.',
39
+ [
40
+ { text: 'Cancel', style: 'cancel' },
41
+ { text: 'Open Settings', onPress: () => Linking.openSettings() },
42
+ ]
43
+ );
44
+ else never_ask_function();
29
45
  }
30
- return ReactNativeLocalDownload.localDownload(uri)
46
+ throw new Error('Permissions not granted');
47
+ }
48
+ return ReactNativeLocalDownload.localDownload(uri);
31
49
  }
32
50
 
33
51
  export const requestStoragePermission = async (): Promise<
34
- typeof PermissionsAndroid.RESULTS.DENIED
52
+ typeof PermissionsAndroid.RESULTS.DENIED
35
53
  > => {
36
- if (Platform.OS !== 'android') return PermissionsAndroid.RESULTS.GRANTED
37
-
38
- try {
39
- if (Number(Platform.Version) >= 33) {
40
- return 'granted'
41
- }
54
+ if (Platform.OS !== 'android') return PermissionsAndroid.RESULTS.GRANTED;
42
55
 
43
- const hasPermission = await PermissionsAndroid.check(
44
- PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE
45
- )
46
- if (hasPermission) {
47
- return 'granted'
48
- }
56
+ try {
57
+ if (Number(Platform.Version) >= 33) {
58
+ return 'granted';
59
+ }
49
60
 
50
- const granted = await PermissionsAndroid.request(
51
- PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
52
- {
53
- title: 'Storage Permission',
54
- message: 'This app needs access to your storage to download files.',
55
- buttonPositive: 'OK',
56
- }
57
- )
58
- return granted
59
- } catch (err) {
60
- console.warn(err)
61
- return PermissionsAndroid.RESULTS.DENIED
61
+ const hasPermission = await PermissionsAndroid.check(
62
+ PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE
63
+ );
64
+ if (hasPermission) {
65
+ return 'granted';
62
66
  }
63
- }
64
67
 
68
+ const granted = await PermissionsAndroid.request(
69
+ PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
70
+ {
71
+ title: 'Storage Permission',
72
+ message: 'This app needs access to your storage to download files.',
73
+ buttonPositive: 'OK',
74
+ }
75
+ );
76
+ return granted;
77
+ } catch (err) {
78
+ console.warn(err);
79
+ return PermissionsAndroid.RESULTS.DENIED;
80
+ }
81
+ };