@onekeyfe/react-native-cloud-fs 3.0.14 → 3.0.16
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/android/build.gradle +10 -0
- package/android/src/main/java/com/rncloudfs/DriveServiceHelper.kt +108 -0
- package/android/src/main/java/com/rncloudfs/RNCloudFsModule.kt +389 -14
- package/ios/CloudFs.mm +28 -1
- package/lib/module/index.js +1 -0
- package/lib/typescript/src/NativeCloudFs.d.ts +43 -6
- package/lib/typescript/src/index.d.ts +2 -0
- package/package.json +1 -1
- package/src/NativeCloudFs.ts +42 -6
- package/src/index.tsx +1 -0
package/android/build.gradle
CHANGED
|
@@ -74,4 +74,14 @@ def kotlin_version = getExtOrDefault("kotlinVersion")
|
|
|
74
74
|
dependencies {
|
|
75
75
|
implementation "com.facebook.react:react-android"
|
|
76
76
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
77
|
+
|
|
78
|
+
// Google Drive / Sign-In
|
|
79
|
+
implementation "com.google.android.gms:play-services-auth:21.3.0"
|
|
80
|
+
implementation "com.google.http-client:google-http-client-gson:1.44.1"
|
|
81
|
+
implementation("com.google.api-client:google-api-client-android:2.7.0") {
|
|
82
|
+
exclude group: "org.apache.httpcomponents"
|
|
83
|
+
}
|
|
84
|
+
implementation("com.google.apis:google-api-services-drive:v3-rev20241027-2.0.0") {
|
|
85
|
+
exclude group: "org.apache.httpcomponents"
|
|
86
|
+
}
|
|
77
87
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
package com.rncloudfs
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.google.android.gms.tasks.Task
|
|
5
|
+
import com.google.android.gms.tasks.Tasks
|
|
6
|
+
import com.google.api.client.http.FileContent
|
|
7
|
+
import com.google.api.services.drive.Drive
|
|
8
|
+
import com.google.api.services.drive.model.FileList
|
|
9
|
+
import java.io.BufferedReader
|
|
10
|
+
import java.io.InputStreamReader
|
|
11
|
+
import java.util.concurrent.Executors
|
|
12
|
+
|
|
13
|
+
class DriveServiceHelper(private val driveService: Drive) {
|
|
14
|
+
|
|
15
|
+
private val executor = Executors.newSingleThreadExecutor()
|
|
16
|
+
|
|
17
|
+
fun saveFile(
|
|
18
|
+
sourcePath: String,
|
|
19
|
+
destinationPath: String,
|
|
20
|
+
mimeType: String?,
|
|
21
|
+
useDocumentsFolder: Boolean
|
|
22
|
+
): Task<String> {
|
|
23
|
+
var existingFileId: String? = null
|
|
24
|
+
val fileList = Tasks.await(queryFiles(useDocumentsFolder))
|
|
25
|
+
for (file in fileList.files) {
|
|
26
|
+
if (file.name.equals(destinationPath, ignoreCase = true)) {
|
|
27
|
+
existingFileId = file.id
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return createFile(sourcePath, destinationPath, mimeType, useDocumentsFolder, existingFileId)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fun createFile(
|
|
34
|
+
sourcePath: String,
|
|
35
|
+
destinationPath: String,
|
|
36
|
+
mimeType: String?,
|
|
37
|
+
useDocumentsFolder: Boolean,
|
|
38
|
+
fileId: String?
|
|
39
|
+
): Task<String> {
|
|
40
|
+
return Tasks.call(executor) {
|
|
41
|
+
try {
|
|
42
|
+
val sourceFile = java.io.File(sourcePath)
|
|
43
|
+
val mediaContent = FileContent(mimeType, sourceFile)
|
|
44
|
+
val parentFolder = listOf(if (useDocumentsFolder) "root" else "appDataFolder")
|
|
45
|
+
val metadata = com.google.api.services.drive.model.File()
|
|
46
|
+
.setMimeType(mimeType)
|
|
47
|
+
.setName(destinationPath)
|
|
48
|
+
if (fileId == null) {
|
|
49
|
+
metadata.parents = parentFolder
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
val googleFile = if (fileId != null) {
|
|
53
|
+
driveService.files().update(fileId, metadata, mediaContent).execute()
|
|
54
|
+
} else {
|
|
55
|
+
driveService.files().create(metadata, mediaContent).execute()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
googleFile?.id ?: throw java.io.IOException("Null result when requesting file creation.")
|
|
59
|
+
} catch (e: Exception) {
|
|
60
|
+
Log.e(TAG, e.toString())
|
|
61
|
+
throw e
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fun checkIfFileExists(fileId: String): Task<Boolean> {
|
|
67
|
+
return Tasks.call(executor) {
|
|
68
|
+
val metadata = driveService.files().get(fileId).execute()
|
|
69
|
+
metadata != null
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fun deleteFile(fileId: String): Task<Boolean> {
|
|
74
|
+
return Tasks.call(executor) {
|
|
75
|
+
driveService.files().delete(fileId).execute()
|
|
76
|
+
true
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fun readFile(fileId: String): Task<String> {
|
|
81
|
+
return Tasks.call(executor) {
|
|
82
|
+
driveService.files().get(fileId).executeMediaAsInputStream().use { inputStream ->
|
|
83
|
+
BufferedReader(InputStreamReader(inputStream)).use { reader ->
|
|
84
|
+
val sb = StringBuilder()
|
|
85
|
+
var line: String?
|
|
86
|
+
while (reader.readLine().also { line = it } != null) {
|
|
87
|
+
sb.append(line)
|
|
88
|
+
}
|
|
89
|
+
sb.toString()
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
fun queryFiles(useDocumentsFolder: Boolean): Task<FileList> {
|
|
96
|
+
return Tasks.call(executor) {
|
|
97
|
+
driveService.files().list()
|
|
98
|
+
.setSpaces(if (useDocumentsFolder) "drive" else "appDataFolder")
|
|
99
|
+
.setFields("nextPageToken, files(id, name, modifiedTime)")
|
|
100
|
+
.setPageSize(100)
|
|
101
|
+
.execute()
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
companion object {
|
|
106
|
+
private const val TAG = "DriveServiceHelper"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -1,50 +1,425 @@
|
|
|
1
1
|
package com.rncloudfs
|
|
2
2
|
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.net.Uri
|
|
6
|
+
import android.util.Log
|
|
7
|
+
import android.webkit.MimeTypeMap
|
|
8
|
+
import com.facebook.react.bridge.ActivityEventListener
|
|
9
|
+
import com.facebook.react.bridge.LifecycleEventListener
|
|
3
10
|
import com.facebook.react.bridge.Promise
|
|
4
11
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
12
|
import com.facebook.react.bridge.ReadableMap
|
|
13
|
+
import com.facebook.react.bridge.WritableNativeArray
|
|
14
|
+
import com.facebook.react.bridge.WritableNativeMap
|
|
6
15
|
import com.facebook.react.module.annotations.ReactModule
|
|
16
|
+
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
|
17
|
+
import com.google.android.gms.auth.api.signin.GoogleSignInClient
|
|
18
|
+
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
|
|
19
|
+
import com.google.android.gms.common.api.ApiException
|
|
20
|
+
import com.google.android.gms.common.api.Scope
|
|
21
|
+
import com.google.api.client.extensions.android.http.AndroidHttp
|
|
22
|
+
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential
|
|
23
|
+
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
|
|
24
|
+
import com.google.api.client.json.gson.GsonFactory
|
|
25
|
+
import com.google.api.services.drive.Drive
|
|
26
|
+
import com.google.api.services.drive.DriveScopes
|
|
27
|
+
import java.util.Collections
|
|
7
28
|
|
|
8
29
|
@ReactModule(name = RNCloudFsModule.NAME)
|
|
9
|
-
class RNCloudFsModule(reactContext: ReactApplicationContext) :
|
|
10
|
-
NativeRNCloudFsSpec(reactContext) {
|
|
30
|
+
class RNCloudFsModule(private val reactContext: ReactApplicationContext) :
|
|
31
|
+
NativeRNCloudFsSpec(reactContext), LifecycleEventListener, ActivityEventListener {
|
|
32
|
+
|
|
33
|
+
private var mDriveServiceHelper: DriveServiceHelper? = null
|
|
34
|
+
private var signInPromise: Promise? = null
|
|
35
|
+
private var mPendingPromise: Promise? = null
|
|
36
|
+
private var mPendingOptions: ReadableMap? = null
|
|
37
|
+
private var mPendingOperation: String? = null
|
|
38
|
+
|
|
39
|
+
init {
|
|
40
|
+
reactContext.addLifecycleEventListener(this)
|
|
41
|
+
reactContext.addActivityEventListener(this)
|
|
42
|
+
}
|
|
11
43
|
|
|
12
44
|
companion object {
|
|
13
45
|
const val NAME = "RNCloudFs"
|
|
14
|
-
private const val
|
|
46
|
+
private const val TAG = "RNCloudFs"
|
|
47
|
+
private const val REQUEST_CODE_SIGN_IN = 1
|
|
48
|
+
private const val REQUEST_AUTHORIZATION = 11
|
|
49
|
+
private const val COPY_TO_CLOUD = "CopyToCloud"
|
|
50
|
+
private const val LIST_FILES = "ListFiles"
|
|
15
51
|
}
|
|
16
52
|
|
|
17
53
|
override fun getName(): String = NAME
|
|
18
54
|
|
|
55
|
+
// region iOS-only stubs
|
|
56
|
+
|
|
19
57
|
override fun isAvailable(promise: Promise) {
|
|
20
58
|
promise.resolve(false)
|
|
21
59
|
}
|
|
22
60
|
|
|
23
61
|
override fun createFile(options: ReadableMap, promise: Promise) {
|
|
24
|
-
promise.reject("NOT_AVAILABLE",
|
|
62
|
+
promise.reject("NOT_AVAILABLE", "iCloud is not available on Android")
|
|
25
63
|
}
|
|
26
64
|
|
|
27
|
-
override fun
|
|
28
|
-
promise.reject("NOT_AVAILABLE",
|
|
65
|
+
override fun getIcloudDocument(filename: String, promise: Promise) {
|
|
66
|
+
promise.reject("NOT_AVAILABLE", "iCloud is not available on Android")
|
|
29
67
|
}
|
|
30
68
|
|
|
31
|
-
override fun
|
|
32
|
-
promise.reject("NOT_AVAILABLE",
|
|
69
|
+
override fun syncCloud(promise: Promise) {
|
|
70
|
+
promise.reject("NOT_AVAILABLE", "iCloud is not available on Android")
|
|
33
71
|
}
|
|
34
72
|
|
|
35
|
-
|
|
36
|
-
|
|
73
|
+
// endregion
|
|
74
|
+
|
|
75
|
+
// region Google Sign-In
|
|
76
|
+
|
|
77
|
+
override fun loginIfNeeded(promise: Promise) {
|
|
78
|
+
if (mDriveServiceHelper == null) {
|
|
79
|
+
val account = GoogleSignIn.getLastSignedInAccount(reactContext)
|
|
80
|
+
if (account == null) {
|
|
81
|
+
signInPromise = promise
|
|
82
|
+
requestSignIn()
|
|
83
|
+
} else {
|
|
84
|
+
val credential = GoogleAccountCredential.usingOAuth2(
|
|
85
|
+
reactContext, Collections.singleton(DriveScopes.DRIVE_APPDATA)
|
|
86
|
+
)
|
|
87
|
+
credential.selectedAccount = account.account
|
|
88
|
+
val googleDriveService = Drive.Builder(
|
|
89
|
+
AndroidHttp.newCompatibleTransport(),
|
|
90
|
+
GsonFactory(),
|
|
91
|
+
credential
|
|
92
|
+
).build()
|
|
93
|
+
mDriveServiceHelper = DriveServiceHelper(googleDriveService)
|
|
94
|
+
promise.resolve(true)
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
promise.resolve(true)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
override fun logout(promise: Promise) {
|
|
102
|
+
val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
|
103
|
+
.requestEmail()
|
|
104
|
+
.requestScopes(Scope(DriveScopes.DRIVE_FILE))
|
|
105
|
+
.build()
|
|
106
|
+
val client: GoogleSignInClient = GoogleSignIn.getClient(reactContext, signInOptions)
|
|
107
|
+
mDriveServiceHelper = null
|
|
108
|
+
client.signOut()
|
|
109
|
+
.addOnSuccessListener { promise.resolve(true) }
|
|
110
|
+
.addOnFailureListener { exception ->
|
|
111
|
+
Log.e(TAG, "Couldn't log out.", exception)
|
|
112
|
+
promise.reject(exception)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
override fun getCurrentlySignedInUserData(promise: Promise) {
|
|
117
|
+
val account = GoogleSignIn.getLastSignedInAccount(reactContext)
|
|
118
|
+
if (account == null) {
|
|
119
|
+
promise.resolve(null)
|
|
120
|
+
} else {
|
|
121
|
+
val photoUrl: Uri? = account.photoUrl
|
|
122
|
+
val resultData = WritableNativeMap()
|
|
123
|
+
resultData.putString("email", account.email)
|
|
124
|
+
resultData.putString("name", account.displayName)
|
|
125
|
+
resultData.putString("avatarUrl", photoUrl?.toString())
|
|
126
|
+
promise.resolve(resultData)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private fun requestSignIn() {
|
|
131
|
+
Log.d(TAG, "Requesting sign-in")
|
|
132
|
+
val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
|
133
|
+
.requestEmail()
|
|
134
|
+
.requestScopes(Scope(DriveScopes.DRIVE_FILE))
|
|
135
|
+
.build()
|
|
136
|
+
val client: GoogleSignInClient = GoogleSignIn.getClient(reactContext, signInOptions)
|
|
137
|
+
reactContext.startActivityForResult(client.signInIntent, REQUEST_CODE_SIGN_IN, null)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// endregion
|
|
141
|
+
|
|
142
|
+
// region Google Drive operations
|
|
143
|
+
|
|
144
|
+
override fun fileExists(options: ReadableMap, promise: Promise) {
|
|
145
|
+
val helper = mDriveServiceHelper
|
|
146
|
+
if (helper != null) {
|
|
147
|
+
val fileId = options.getString("fileId") ?: ""
|
|
148
|
+
Log.d(TAG, "Checking file $fileId")
|
|
149
|
+
helper.checkIfFileExists(fileId)
|
|
150
|
+
.addOnSuccessListener { exists -> promise.resolve(exists) }
|
|
151
|
+
.addOnFailureListener { exception ->
|
|
152
|
+
try {
|
|
153
|
+
val e = exception as UserRecoverableAuthIOException
|
|
154
|
+
reactContext.startActivityForResult(e.intent, REQUEST_AUTHORIZATION, null)
|
|
155
|
+
} catch (e: Exception) {
|
|
156
|
+
Log.e(TAG, "Couldn't check file.", exception)
|
|
157
|
+
promise.reject(exception)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
promise.reject("NOT_LOGGED_IN", "Google Drive not initialized. Call loginIfNeeded first.")
|
|
162
|
+
}
|
|
37
163
|
}
|
|
38
164
|
|
|
39
165
|
override fun deleteFromCloud(item: ReadableMap, promise: Promise) {
|
|
40
|
-
|
|
166
|
+
val helper = mDriveServiceHelper
|
|
167
|
+
if (helper != null) {
|
|
168
|
+
val fileId = item.getString("id") ?: ""
|
|
169
|
+
Log.d(TAG, "Deleting file $fileId")
|
|
170
|
+
helper.deleteFile(fileId)
|
|
171
|
+
.addOnSuccessListener { deleted -> promise.resolve(deleted) }
|
|
172
|
+
.addOnFailureListener { exception ->
|
|
173
|
+
try {
|
|
174
|
+
val e = exception as UserRecoverableAuthIOException
|
|
175
|
+
reactContext.startActivityForResult(e.intent, REQUEST_AUTHORIZATION, null)
|
|
176
|
+
} catch (e: Exception) {
|
|
177
|
+
Log.e(TAG, "Couldn't delete file.", exception)
|
|
178
|
+
promise.reject(exception)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
promise.reject("NOT_LOGGED_IN", "Google Drive not initialized. Call loginIfNeeded first.")
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
override fun listFiles(options: ReadableMap, promise: Promise) {
|
|
187
|
+
val helper = mDriveServiceHelper
|
|
188
|
+
if (helper != null) {
|
|
189
|
+
Log.d(TAG, "Querying for files.")
|
|
190
|
+
val useDocumentsFolder = if (options.hasKey("scope")) {
|
|
191
|
+
options.getString("scope")?.lowercase() == "visible"
|
|
192
|
+
} else {
|
|
193
|
+
true
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
helper.queryFiles(useDocumentsFolder)
|
|
197
|
+
.addOnSuccessListener { fileList ->
|
|
198
|
+
val files = WritableNativeArray()
|
|
199
|
+
for (file in fileList.files) {
|
|
200
|
+
val fileInfo = WritableNativeMap()
|
|
201
|
+
fileInfo.putString("name", file.name)
|
|
202
|
+
fileInfo.putString("id", file.id)
|
|
203
|
+
fileInfo.putString("lastModified", file.modifiedTime.toString())
|
|
204
|
+
files.pushMap(fileInfo)
|
|
205
|
+
}
|
|
206
|
+
val result = WritableNativeMap()
|
|
207
|
+
result.putArray("files", files)
|
|
208
|
+
promise.resolve(result)
|
|
209
|
+
clearPendingOperations()
|
|
210
|
+
}
|
|
211
|
+
.addOnFailureListener { exception ->
|
|
212
|
+
clearPendingOperations()
|
|
213
|
+
try {
|
|
214
|
+
Log.e(TAG, "Unable to query files: ${exception.cause?.message}")
|
|
215
|
+
val e = exception as UserRecoverableAuthIOException
|
|
216
|
+
mPendingPromise = promise
|
|
217
|
+
mPendingOptions = options
|
|
218
|
+
mPendingOperation = LIST_FILES
|
|
219
|
+
reactContext.startActivityForResult(e.intent, REQUEST_AUTHORIZATION, null)
|
|
220
|
+
} catch (e: Exception) {
|
|
221
|
+
promise.reject(e)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
} catch (exception: Exception) {
|
|
225
|
+
try {
|
|
226
|
+
val e = exception as java.util.concurrent.ExecutionException
|
|
227
|
+
mPendingPromise = promise
|
|
228
|
+
mPendingOptions = options
|
|
229
|
+
mPendingOperation = LIST_FILES
|
|
230
|
+
val intent = (e.cause as UserRecoverableAuthIOException).intent
|
|
231
|
+
reactContext.startActivityForResult(intent, REQUEST_AUTHORIZATION, null)
|
|
232
|
+
} catch (e: Exception) {
|
|
233
|
+
promise.reject(exception)
|
|
234
|
+
Log.e(TAG, "Unable to query files: ${exception.cause?.message}")
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
promise.reject("NOT_LOGGED_IN", "Google Drive not initialized. Call loginIfNeeded first.")
|
|
239
|
+
}
|
|
41
240
|
}
|
|
42
241
|
|
|
43
242
|
override fun copyToCloud(options: ReadableMap, promise: Promise) {
|
|
44
|
-
|
|
243
|
+
val helper = mDriveServiceHelper
|
|
244
|
+
if (helper != null) {
|
|
245
|
+
if (!options.hasKey("sourcePath")) {
|
|
246
|
+
promise.reject("error", "sourcePath not specified")
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
val source = options.getMap("sourcePath")
|
|
250
|
+
var uriOrPath = source?.getString("uri")
|
|
251
|
+
if (uriOrPath == null) {
|
|
252
|
+
uriOrPath = source?.getString("path")
|
|
253
|
+
}
|
|
254
|
+
if (uriOrPath == null) {
|
|
255
|
+
promise.reject("no path", "no source uri or path was specified")
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
if (!options.hasKey("targetPath")) {
|
|
259
|
+
promise.reject("error", "targetPath not specified")
|
|
260
|
+
return
|
|
261
|
+
}
|
|
262
|
+
val destinationPath = options.getString("targetPath") ?: ""
|
|
263
|
+
val mimeType = if (options.hasKey("mimetype")) options.getString("mimetype") else null
|
|
264
|
+
val useDocumentsFolder = if (options.hasKey("scope")) {
|
|
265
|
+
options.getString("scope")?.lowercase() == "visible"
|
|
266
|
+
} else {
|
|
267
|
+
true
|
|
268
|
+
}
|
|
269
|
+
val actualMimeType = if (mimeType == null) guessMimeType(uriOrPath) else null
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
helper.saveFile(uriOrPath, destinationPath, actualMimeType, useDocumentsFolder)
|
|
273
|
+
.addOnSuccessListener { fileId ->
|
|
274
|
+
Log.d(TAG, "Saving $fileId")
|
|
275
|
+
promise.resolve(fileId)
|
|
276
|
+
clearPendingOperations()
|
|
277
|
+
}
|
|
278
|
+
.addOnFailureListener { exception ->
|
|
279
|
+
clearPendingOperations()
|
|
280
|
+
try {
|
|
281
|
+
val e = exception as UserRecoverableAuthIOException
|
|
282
|
+
reactContext.startActivityForResult(e.intent, REQUEST_AUTHORIZATION, null)
|
|
283
|
+
} catch (e: Exception) {
|
|
284
|
+
Log.e(TAG, "Couldn't create file.", exception)
|
|
285
|
+
}
|
|
286
|
+
promise.reject(exception)
|
|
287
|
+
}
|
|
288
|
+
} catch (exception: Exception) {
|
|
289
|
+
try {
|
|
290
|
+
val e = exception as java.util.concurrent.ExecutionException
|
|
291
|
+
mPendingPromise = promise
|
|
292
|
+
mPendingOptions = options
|
|
293
|
+
mPendingOperation = COPY_TO_CLOUD
|
|
294
|
+
val intent = (e.cause as UserRecoverableAuthIOException).intent
|
|
295
|
+
reactContext.startActivityForResult(intent, REQUEST_AUTHORIZATION, null)
|
|
296
|
+
} catch (e: Exception) {
|
|
297
|
+
promise.reject(exception)
|
|
298
|
+
Log.e(TAG, "Couldn't create file.", exception)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
} else {
|
|
302
|
+
promise.reject("NOT_LOGGED_IN", "Google Drive not initialized. Call loginIfNeeded first.")
|
|
303
|
+
}
|
|
45
304
|
}
|
|
46
305
|
|
|
47
|
-
override fun
|
|
48
|
-
|
|
306
|
+
override fun getGoogleDriveDocument(fileId: String, promise: Promise) {
|
|
307
|
+
val helper = mDriveServiceHelper
|
|
308
|
+
if (helper != null) {
|
|
309
|
+
Log.d(TAG, "Reading file $fileId")
|
|
310
|
+
helper.readFile(fileId)
|
|
311
|
+
.addOnSuccessListener { content -> promise.resolve(content) }
|
|
312
|
+
.addOnFailureListener { exception ->
|
|
313
|
+
try {
|
|
314
|
+
val e = exception as UserRecoverableAuthIOException
|
|
315
|
+
reactContext.startActivityForResult(e.intent, REQUEST_AUTHORIZATION, null)
|
|
316
|
+
} catch (e: Exception) {
|
|
317
|
+
Log.e(TAG, "Couldn't read file.", exception)
|
|
318
|
+
promise.reject(exception)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
} else {
|
|
322
|
+
promise.reject("NOT_LOGGED_IN", "Google Drive not initialized. Call loginIfNeeded first.")
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// endregion
|
|
327
|
+
|
|
328
|
+
// region Activity result handling
|
|
329
|
+
|
|
330
|
+
private fun clearPendingOperations() {
|
|
331
|
+
mPendingOperation = null
|
|
332
|
+
mPendingPromise = null
|
|
333
|
+
mPendingOptions = null
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
override fun onActivityResult(activity: Activity?, requestCode: Int, resultCode: Int, data: Intent?) {
|
|
337
|
+
when (requestCode) {
|
|
338
|
+
REQUEST_CODE_SIGN_IN -> {
|
|
339
|
+
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
|
|
340
|
+
handleSignInResult(task)
|
|
341
|
+
}
|
|
342
|
+
REQUEST_AUTHORIZATION -> {
|
|
343
|
+
if (resultCode == Activity.RESULT_OK && data != null) {
|
|
344
|
+
val copiedPendingOperation = mPendingOperation
|
|
345
|
+
if (copiedPendingOperation != null) {
|
|
346
|
+
reactContext.runOnNativeModulesQueueThread {
|
|
347
|
+
mPendingOperation = null
|
|
348
|
+
when (copiedPendingOperation) {
|
|
349
|
+
COPY_TO_CLOUD -> {
|
|
350
|
+
try {
|
|
351
|
+
mPendingOptions?.let { copyToCloud(it, mPendingPromise!!) }
|
|
352
|
+
} catch (e: Exception) {
|
|
353
|
+
mPendingPromise?.reject(e)
|
|
354
|
+
clearPendingOperations()
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
LIST_FILES -> {
|
|
358
|
+
try {
|
|
359
|
+
mPendingOptions?.let { listFiles(it, mPendingPromise!!) }
|
|
360
|
+
} catch (e: Exception) {
|
|
361
|
+
mPendingPromise?.reject(e)
|
|
362
|
+
clearPendingOperations()
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
} else if (resultCode == Activity.RESULT_CANCELED && mPendingPromise != null) {
|
|
369
|
+
mPendingPromise?.reject("canceled", "User canceled")
|
|
370
|
+
} else if (mPendingPromise != null) {
|
|
371
|
+
mPendingPromise?.reject(
|
|
372
|
+
"unknown error",
|
|
373
|
+
"Operation failed: $mPendingOperation result code $resultCode"
|
|
374
|
+
)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private fun handleSignInResult(completedTask: com.google.android.gms.tasks.Task<com.google.android.gms.auth.api.signin.GoogleSignInAccount>) {
|
|
381
|
+
try {
|
|
382
|
+
val googleAccount = completedTask.getResult(ApiException::class.java)
|
|
383
|
+
Log.d(TAG, "Signed in as ${googleAccount.email}")
|
|
384
|
+
|
|
385
|
+
val credential = GoogleAccountCredential.usingOAuth2(
|
|
386
|
+
reactContext, Collections.singleton(DriveScopes.DRIVE_APPDATA)
|
|
387
|
+
)
|
|
388
|
+
credential.selectedAccount = googleAccount.account
|
|
389
|
+
val googleDriveService = Drive.Builder(
|
|
390
|
+
AndroidHttp.newCompatibleTransport(),
|
|
391
|
+
GsonFactory(),
|
|
392
|
+
credential
|
|
393
|
+
).build()
|
|
394
|
+
|
|
395
|
+
mDriveServiceHelper = DriveServiceHelper(googleDriveService)
|
|
396
|
+
|
|
397
|
+
signInPromise?.resolve(true)
|
|
398
|
+
signInPromise = null
|
|
399
|
+
} catch (e: ApiException) {
|
|
400
|
+
Log.w(TAG, "signInResult:failed code=${e.statusCode}")
|
|
401
|
+
signInPromise?.reject("signInResult:${e.statusCode}", e.message)
|
|
402
|
+
signInPromise = null
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// endregion
|
|
407
|
+
|
|
408
|
+
// region Lifecycle
|
|
409
|
+
|
|
410
|
+
override fun onHostResume() {}
|
|
411
|
+
override fun onHostPause() {}
|
|
412
|
+
override fun onHostDestroy() {}
|
|
413
|
+
override fun onNewIntent(intent: Intent?) {}
|
|
414
|
+
|
|
415
|
+
// endregion
|
|
416
|
+
|
|
417
|
+
private fun guessMimeType(url: String): String? {
|
|
418
|
+
val extension = MimeTypeMap.getFileExtensionFromUrl(url)
|
|
419
|
+
return if (extension != null) {
|
|
420
|
+
MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
|
421
|
+
} else {
|
|
422
|
+
null
|
|
423
|
+
}
|
|
49
424
|
}
|
|
50
425
|
}
|
package/ios/CloudFs.mm
CHANGED
|
@@ -302,10 +302,37 @@
|
|
|
302
302
|
for (NSMetadataItem *item in query.results) {
|
|
303
303
|
[self downloadFileIfNotAvailable:item];
|
|
304
304
|
}
|
|
305
|
-
return resolve(
|
|
305
|
+
return resolve(@YES);
|
|
306
306
|
}];
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
+
// MARK: - Android-only stubs
|
|
310
|
+
|
|
311
|
+
- (void)loginIfNeeded:(RCTPromiseResolveBlock)resolve
|
|
312
|
+
reject:(RCTPromiseRejectBlock)reject
|
|
313
|
+
{
|
|
314
|
+
resolve(@NO);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
- (void)logout:(RCTPromiseResolveBlock)resolve
|
|
318
|
+
reject:(RCTPromiseRejectBlock)reject
|
|
319
|
+
{
|
|
320
|
+
resolve(@NO);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
- (void)getGoogleDriveDocument:(NSString *)fileId
|
|
324
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
325
|
+
reject:(RCTPromiseRejectBlock)reject
|
|
326
|
+
{
|
|
327
|
+
reject(@"NOT_AVAILABLE", @"Google Drive is not available on iOS", nil);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
- (void)getCurrentlySignedInUserData:(RCTPromiseResolveBlock)resolve
|
|
331
|
+
reject:(RCTPromiseRejectBlock)reject
|
|
332
|
+
{
|
|
333
|
+
resolve([NSNull null]);
|
|
334
|
+
}
|
|
335
|
+
|
|
309
336
|
// MARK: - Private helpers
|
|
310
337
|
|
|
311
338
|
- (void)moveToICloudDirectory:(bool)documentsFolder
|
package/lib/module/index.js
CHANGED
|
@@ -1,13 +1,50 @@
|
|
|
1
1
|
import type { TurboModule } from 'react-native';
|
|
2
2
|
export interface Spec extends TurboModule {
|
|
3
3
|
isAvailable(): Promise<boolean>;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
syncCloud(): Promise<boolean>;
|
|
5
|
+
listFiles(options: {
|
|
6
|
+
scope: string;
|
|
7
|
+
targetPath?: string;
|
|
8
|
+
}): Promise<{
|
|
9
|
+
files: Array<{
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
lastModified: string;
|
|
13
|
+
isFile?: boolean;
|
|
14
|
+
}>;
|
|
15
|
+
}>;
|
|
16
|
+
deleteFromCloud(item: {
|
|
17
|
+
id: string;
|
|
18
|
+
path?: string;
|
|
19
|
+
}): Promise<boolean>;
|
|
20
|
+
fileExists(options: {
|
|
21
|
+
fileId?: string;
|
|
22
|
+
targetPath?: string;
|
|
23
|
+
scope?: string;
|
|
24
|
+
}): Promise<boolean>;
|
|
25
|
+
copyToCloud(options: {
|
|
26
|
+
mimetype?: string | null;
|
|
27
|
+
scope: string;
|
|
28
|
+
sourcePath: {
|
|
29
|
+
path?: string;
|
|
30
|
+
uri?: string;
|
|
31
|
+
};
|
|
32
|
+
targetPath: string;
|
|
33
|
+
}): Promise<string>;
|
|
34
|
+
createFile(options: {
|
|
35
|
+
targetPath: string;
|
|
36
|
+
content: string;
|
|
37
|
+
scope?: string;
|
|
38
|
+
}): Promise<string>;
|
|
7
39
|
getIcloudDocument(filename: string): Promise<string>;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
40
|
+
loginIfNeeded(): Promise<boolean>;
|
|
41
|
+
logout(): Promise<boolean>;
|
|
42
|
+
getGoogleDriveDocument(fileId: string): Promise<string>;
|
|
43
|
+
getCurrentlySignedInUserData(): Promise<{
|
|
44
|
+
email: string;
|
|
45
|
+
name: string;
|
|
46
|
+
avatarUrl: string | null;
|
|
47
|
+
} | null>;
|
|
11
48
|
}
|
|
12
49
|
declare const _default: Spec;
|
|
13
50
|
export default _default;
|
package/package.json
CHANGED
package/src/NativeCloudFs.ts
CHANGED
|
@@ -2,14 +2,50 @@ import { TurboModuleRegistry } from 'react-native';
|
|
|
2
2
|
import type { TurboModule } from 'react-native';
|
|
3
3
|
|
|
4
4
|
export interface Spec extends TurboModule {
|
|
5
|
+
// Shared
|
|
5
6
|
isAvailable(): Promise<boolean>;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
syncCloud(): Promise<boolean>;
|
|
8
|
+
listFiles(options: {
|
|
9
|
+
scope: string;
|
|
10
|
+
targetPath?: string;
|
|
11
|
+
}): Promise<{
|
|
12
|
+
files: Array<{
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
lastModified: string;
|
|
16
|
+
isFile?: boolean;
|
|
17
|
+
}>;
|
|
18
|
+
}>;
|
|
19
|
+
deleteFromCloud(item: { id: string; path?: string }): Promise<boolean>;
|
|
20
|
+
fileExists(options: {
|
|
21
|
+
fileId?: string;
|
|
22
|
+
targetPath?: string;
|
|
23
|
+
scope?: string;
|
|
24
|
+
}): Promise<boolean>;
|
|
25
|
+
copyToCloud(options: {
|
|
26
|
+
mimetype?: string | null;
|
|
27
|
+
scope: string;
|
|
28
|
+
sourcePath: { path?: string; uri?: string };
|
|
29
|
+
targetPath: string;
|
|
30
|
+
}): Promise<string>;
|
|
31
|
+
createFile(options: {
|
|
32
|
+
targetPath: string;
|
|
33
|
+
content: string;
|
|
34
|
+
scope?: string;
|
|
35
|
+
}): Promise<string>;
|
|
36
|
+
|
|
37
|
+
// iOS only
|
|
9
38
|
getIcloudDocument(filename: string): Promise<string>;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
39
|
+
|
|
40
|
+
// Android only
|
|
41
|
+
loginIfNeeded(): Promise<boolean>;
|
|
42
|
+
logout(): Promise<boolean>;
|
|
43
|
+
getGoogleDriveDocument(fileId: string): Promise<string>;
|
|
44
|
+
getCurrentlySignedInUserData(): Promise<{
|
|
45
|
+
email: string;
|
|
46
|
+
name: string;
|
|
47
|
+
avatarUrl: string | null;
|
|
48
|
+
} | null>;
|
|
13
49
|
}
|
|
14
50
|
|
|
15
51
|
export default TurboModuleRegistry.getEnforcing<Spec>('RNCloudFs');
|