@vali98/react-native-fs 0.2.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.
|
@@ -1,165 +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
|
|
9
|
-
import android.
|
|
10
|
-
import android.system.Os
|
|
4
|
+
import android.content.ContentValues
|
|
11
5
|
import android.content.Intent
|
|
12
|
-
import android.
|
|
6
|
+
import android.net.Uri
|
|
13
7
|
import android.os.ParcelFileDescriptor
|
|
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
|
|
14
13
|
import java.io.File
|
|
15
14
|
import java.io.FileInputStream
|
|
16
15
|
import java.io.FileOutputStream
|
|
17
16
|
import java.net.URLConnection
|
|
18
17
|
|
|
19
18
|
@ReactModule(name = ReactNativeLocalDownloadModule.NAME)
|
|
20
|
-
class ReactNativeLocalDownloadModule(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
override fun getName(): String
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
28
|
try {
|
|
29
|
-
|
|
29
|
+
val uri = Uri.parse(contentUri)
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
.
|
|
33
|
-
|
|
31
|
+
val pfd =
|
|
32
|
+
reactContext.contentResolver
|
|
33
|
+
.openFileDescriptor(uri, "r")
|
|
34
|
+
?: run {
|
|
34
35
|
promise.reject("FD_OPEN_FAILED", "Unable to open content URI")
|
|
35
36
|
return
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
val fd = pfd.detachFd()
|
|
39
|
-
promise.resolve("/proc/self/fd/$fd")
|
|
37
|
+
}
|
|
40
38
|
|
|
39
|
+
val fd = pfd.detachFd()
|
|
40
|
+
promise.resolve("$fd")
|
|
41
41
|
} catch (e: Exception) {
|
|
42
|
-
|
|
42
|
+
promise.reject(
|
|
43
43
|
"GET_FD_ERROR",
|
|
44
44
|
"Failed to get detached FD: ${e.message}",
|
|
45
|
-
e
|
|
46
|
-
|
|
45
|
+
e,
|
|
46
|
+
)
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
override fun closeFd(
|
|
50
|
+
override fun closeFd(
|
|
51
|
+
fdOrPath: String,
|
|
52
|
+
promise: Promise,
|
|
53
|
+
) {
|
|
51
54
|
try {
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
val fdInt =
|
|
56
|
+
when {
|
|
57
|
+
fdOrPath.startsWith("/proc/") -> {
|
|
54
58
|
fdOrPath.substringAfterLast("/").toInt()
|
|
55
|
-
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
else -> {
|
|
56
62
|
fdOrPath.toInt()
|
|
63
|
+
}
|
|
57
64
|
}
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
ParcelFileDescriptor.adoptFd(fdInt).close()
|
|
66
|
+
promise.resolve(true)
|
|
60
67
|
} catch (e: Exception) {
|
|
61
|
-
|
|
68
|
+
promise.reject(
|
|
62
69
|
"FD_CLOSE_ERROR",
|
|
63
70
|
"Failed to close FD: ${e.message}",
|
|
64
|
-
e
|
|
65
|
-
|
|
71
|
+
e,
|
|
72
|
+
)
|
|
66
73
|
}
|
|
67
74
|
}
|
|
68
75
|
|
|
69
|
-
|
|
76
|
+
override fun persistContentPermission(
|
|
77
|
+
uriString: String,
|
|
78
|
+
promise: Promise,
|
|
79
|
+
) {
|
|
70
80
|
try {
|
|
71
|
-
|
|
81
|
+
val uri = Uri.parse(uriString)
|
|
72
82
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
83
|
+
val flags =
|
|
84
|
+
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
|
85
|
+
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
|
76
86
|
|
|
77
|
-
|
|
87
|
+
reactContext.contentResolver
|
|
78
88
|
.takePersistableUriPermission(uri, flags)
|
|
79
89
|
|
|
80
|
-
|
|
81
|
-
|
|
90
|
+
promise.resolve(true)
|
|
82
91
|
} catch (e: SecurityException) {
|
|
83
|
-
|
|
92
|
+
promise.reject(
|
|
84
93
|
"PERSIST_PERMISSION_DENIED",
|
|
85
94
|
"Persistable permission not granted for this URI",
|
|
86
|
-
e
|
|
87
|
-
|
|
95
|
+
e,
|
|
96
|
+
)
|
|
88
97
|
} catch (e: Exception) {
|
|
89
|
-
|
|
98
|
+
promise.reject(
|
|
90
99
|
"PERSIST_PERMISSION_ERROR",
|
|
91
100
|
e.message,
|
|
92
|
-
e
|
|
93
|
-
|
|
101
|
+
e,
|
|
102
|
+
)
|
|
94
103
|
}
|
|
95
|
-
|
|
96
|
-
|
|
104
|
+
}
|
|
97
105
|
|
|
98
|
-
override fun localDownload(
|
|
106
|
+
override fun localDownload(
|
|
107
|
+
uri: String,
|
|
108
|
+
promise: Promise,
|
|
109
|
+
) {
|
|
99
110
|
try {
|
|
100
111
|
val inputFile = File(uri)
|
|
101
112
|
if (!inputFile.exists()) {
|
|
102
113
|
promise.reject("FILE_NOT_FOUND", "File does not exist at path: $uri")
|
|
103
114
|
return
|
|
104
115
|
}
|
|
105
|
-
|
|
116
|
+
|
|
106
117
|
val fileName = inputFile.name
|
|
107
118
|
val mimeType = URLConnection.guessContentTypeFromName(inputFile.name) ?: "application/octet-stream"
|
|
108
|
-
val resolver
|
|
119
|
+
val resolver: ContentResolver = reactContext.contentResolver
|
|
109
120
|
val uniqueName = getUniqueFileName(resolver, fileName)
|
|
110
|
-
val contentValues =
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
+
|
|
116
128
|
val collection = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
|
117
129
|
val itemUri = resolver.insert(collection, contentValues)
|
|
118
|
-
|
|
130
|
+
|
|
119
131
|
if (itemUri == null) {
|
|
120
132
|
promise.reject("SAVE_ERROR", "Failed to create destination file in MediaStore.")
|
|
121
133
|
return
|
|
122
134
|
}
|
|
123
|
-
|
|
135
|
+
|
|
124
136
|
resolver.openOutputStream(itemUri)?.use { outputStream ->
|
|
125
137
|
inputFile.inputStream().use { inputStream ->
|
|
126
138
|
inputStream.copyTo(outputStream)
|
|
127
139
|
}
|
|
128
140
|
}
|
|
129
|
-
|
|
141
|
+
|
|
130
142
|
// Mark the item as not pending so it's visible to user
|
|
131
143
|
contentValues.clear()
|
|
132
144
|
contentValues.put(MediaStore.Downloads.IS_PENDING, 0)
|
|
133
145
|
resolver.update(itemUri, contentValues, null, null)
|
|
134
|
-
|
|
146
|
+
|
|
135
147
|
promise.resolve(itemUri.toString())
|
|
136
148
|
} catch (e: Exception) {
|
|
137
149
|
promise.reject("DOWNLOAD_ERROR", "Failed to save file via MediaStore: ${e.message}", e)
|
|
138
150
|
}
|
|
139
151
|
}
|
|
140
|
-
|
|
141
|
-
private fun getUniqueFileName(
|
|
152
|
+
|
|
153
|
+
private fun getUniqueFileName(
|
|
154
|
+
resolver: ContentResolver,
|
|
155
|
+
baseName: String,
|
|
156
|
+
): String {
|
|
142
157
|
var name = baseName
|
|
143
158
|
val nameWithoutExtension = File(baseName).nameWithoutExtension
|
|
144
159
|
val extension = File(baseName).extension
|
|
145
160
|
var index = 1
|
|
146
|
-
|
|
161
|
+
|
|
147
162
|
val collection = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
|
148
|
-
|
|
163
|
+
|
|
149
164
|
val projection = arrayOf(MediaStore.Downloads.DISPLAY_NAME)
|
|
150
165
|
val selection = "${MediaStore.Downloads.DISPLAY_NAME} = ?"
|
|
151
166
|
val selectionArgs = arrayOf(name)
|
|
152
|
-
|
|
167
|
+
|
|
153
168
|
while (resolver.query(collection, projection, selection, selectionArgs, null)?.use { it.moveToFirst() } == true) {
|
|
154
|
-
name =
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
169
|
+
name =
|
|
170
|
+
if (extension.isNotEmpty()) {
|
|
171
|
+
"$nameWithoutExtension ($index).$extension"
|
|
172
|
+
} else {
|
|
173
|
+
"$nameWithoutExtension ($index)"
|
|
174
|
+
}
|
|
159
175
|
selectionArgs[0] = name
|
|
160
176
|
index++
|
|
161
177
|
}
|
|
162
|
-
|
|
178
|
+
|
|
163
179
|
return name
|
|
164
180
|
}
|
|
165
181
|
|