react-native-cloud-storage 1.4.1 → 1.5.0

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.
Files changed (103) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +7 -6
  3. package/ios/CloudStorage.swift +52 -262
  4. package/ios/CloudStorage.xcodeproj/project.pbxproj +28 -0
  5. package/ios/CloudStorageEventEmitter.m +1 -1
  6. package/ios/CloudStorageEventEmitter.swift +4 -4
  7. package/ios/Utils/CloudKitUtils.swift +112 -0
  8. package/ios/Utils/CloudStorageError.swift +78 -0
  9. package/ios/Utils/FileUtils.swift +132 -0
  10. package/ios/Utils/Promise.swift +58 -0
  11. package/ios/Utils/Types.swift +36 -0
  12. package/lib/commonjs/RNCloudStorage.js +361 -66
  13. package/lib/commonjs/RNCloudStorage.js.map +1 -1
  14. package/lib/commonjs/expo-plugin/types/index.js.map +1 -1
  15. package/lib/commonjs/expo-plugin/withRNCloudStorage.js +2 -3
  16. package/lib/commonjs/expo-plugin/withRNCloudStorage.js.map +1 -1
  17. package/lib/commonjs/expo-plugin/withRNCloudStorageIos.js +4 -7
  18. package/lib/commonjs/expo-plugin/withRNCloudStorageIos.js.map +1 -1
  19. package/lib/commonjs/google-drive/client.js +16 -20
  20. package/lib/commonjs/google-drive/client.js.map +1 -1
  21. package/lib/commonjs/google-drive/index.js +42 -64
  22. package/lib/commonjs/google-drive/index.js.map +1 -1
  23. package/lib/commonjs/google-drive/types.js +1 -2
  24. package/lib/commonjs/google-drive/types.js.map +1 -1
  25. package/lib/commonjs/hooks/useCloudFile.js +14 -17
  26. package/lib/commonjs/hooks/useCloudFile.js.map +1 -1
  27. package/lib/commonjs/hooks/useIsCloudAvailable.js +11 -21
  28. package/lib/commonjs/hooks/useIsCloudAvailable.js.map +1 -1
  29. package/lib/commonjs/index.js +1 -7
  30. package/lib/commonjs/index.js.map +1 -1
  31. package/lib/commonjs/package.json +1 -0
  32. package/lib/commonjs/types/main.js +8 -3
  33. package/lib/commonjs/types/main.js.map +1 -1
  34. package/lib/commonjs/types/native.js +3 -3
  35. package/lib/commonjs/types/native.js.map +1 -1
  36. package/lib/commonjs/utils/CloudStorageError.js +1 -2
  37. package/lib/commonjs/utils/CloudStorageError.js.map +1 -1
  38. package/lib/commonjs/utils/helpers.js +8 -15
  39. package/lib/commonjs/utils/helpers.js.map +1 -1
  40. package/lib/module/RNCloudStorage.js +362 -65
  41. package/lib/module/RNCloudStorage.js.map +1 -1
  42. package/lib/module/expo-plugin/types/index.js +1 -1
  43. package/lib/module/expo-plugin/types/index.js.map +1 -1
  44. package/lib/module/expo-plugin/withRNCloudStorage.js +2 -0
  45. package/lib/module/expo-plugin/withRNCloudStorage.js.map +1 -1
  46. package/lib/module/expo-plugin/withRNCloudStorageIos.js +5 -5
  47. package/lib/module/expo-plugin/withRNCloudStorageIos.js.map +1 -1
  48. package/lib/module/google-drive/client.js +18 -20
  49. package/lib/module/google-drive/client.js.map +1 -1
  50. package/lib/module/google-drive/index.js +41 -62
  51. package/lib/module/google-drive/index.js.map +1 -1
  52. package/lib/module/google-drive/types.js +2 -0
  53. package/lib/module/google-drive/types.js.map +1 -1
  54. package/lib/module/hooks/useCloudFile.js +15 -16
  55. package/lib/module/hooks/useCloudFile.js.map +1 -1
  56. package/lib/module/hooks/useIsCloudAvailable.js +13 -21
  57. package/lib/module/hooks/useIsCloudAvailable.js.map +1 -1
  58. package/lib/module/index.js +2 -5
  59. package/lib/module/index.js.map +1 -1
  60. package/lib/module/package.json +1 -0
  61. package/lib/module/types/main.js +9 -0
  62. package/lib/module/types/main.js.map +1 -1
  63. package/lib/module/types/native.js +4 -1
  64. package/lib/module/types/native.js.map +1 -1
  65. package/lib/module/utils/CloudStorageError.js +2 -0
  66. package/lib/module/utils/CloudStorageError.js.map +1 -1
  67. package/lib/module/utils/helpers.js +8 -13
  68. package/lib/module/utils/helpers.js.map +1 -1
  69. package/lib/typescript/RNCloudStorage.d.ts +159 -39
  70. package/lib/typescript/RNCloudStorage.d.ts.map +1 -1
  71. package/lib/typescript/google-drive/client.d.ts +3 -3
  72. package/lib/typescript/google-drive/client.d.ts.map +1 -1
  73. package/lib/typescript/google-drive/index.d.ts +6 -18
  74. package/lib/typescript/google-drive/index.d.ts.map +1 -1
  75. package/lib/typescript/hooks/useCloudFile.d.ts +4 -7
  76. package/lib/typescript/hooks/useCloudFile.d.ts.map +1 -1
  77. package/lib/typescript/hooks/useIsCloudAvailable.d.ts +3 -2
  78. package/lib/typescript/hooks/useIsCloudAvailable.d.ts.map +1 -1
  79. package/lib/typescript/index.d.ts +0 -4
  80. package/lib/typescript/index.d.ts.map +1 -1
  81. package/lib/typescript/types/main.d.ts +33 -0
  82. package/lib/typescript/types/main.d.ts.map +1 -1
  83. package/lib/typescript/types/native.d.ts +2 -1
  84. package/lib/typescript/types/native.d.ts.map +1 -1
  85. package/lib/typescript/utils/helpers.d.ts +2 -9
  86. package/lib/typescript/utils/helpers.d.ts.map +1 -1
  87. package/package.json +9 -11
  88. package/src/RNCloudStorage.ts +387 -68
  89. package/src/google-drive/client.ts +8 -7
  90. package/src/google-drive/index.ts +38 -63
  91. package/src/hooks/useCloudFile.ts +13 -16
  92. package/src/hooks/useIsCloudAvailable.ts +12 -25
  93. package/src/index.ts +0 -5
  94. package/src/types/main.ts +38 -0
  95. package/src/types/native.ts +2 -1
  96. package/src/utils/helpers.ts +8 -15
  97. package/lib/commonjs/createRNCloudStorage.js +0 -48
  98. package/lib/commonjs/createRNCloudStorage.js.map +0 -1
  99. package/lib/module/createRNCloudStorage.js +0 -41
  100. package/lib/module/createRNCloudStorage.js.map +0 -1
  101. package/lib/typescript/createRNCloudStorage.d.ts +0 -3
  102. package/lib/typescript/createRNCloudStorage.d.ts.map +0 -1
  103. package/src/createRNCloudStorage.ts +0 -53
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 Kuatsu Digital Agency
3
+ Copyright (c) 2023-2024 Kuatsu Digital Agency
4
4
  Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  of this software and associated documentation files (the "Software"), to deal
6
6
  in the Software without restriction, including without limitation the rights
package/README.md CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  ![npm bundle size](https://img.shields.io/bundlephobia/min/react-native-cloud-storage?style=flat-square) ![GitHub](https://img.shields.io/github/license/kuatsu/react-native-cloud-storage?style=flat-square) ![GitHub last commit](https://img.shields.io/github/last-commit/kuatsu/react-native-cloud-storage?style=flat-square)
4
4
 
5
- This library provides a unified and streamlined API for accessing cloud storage services on iOS, Android and Web. It supports iCloud on iOS and Google Drive on all other platforms.
5
+ This library provides a unified and streamlined API for accessing cloud storage services on iOS, Android and Web. It supports iCloud (on iOS only) and Google Drive (all platforms).
6
6
 
7
7
  - 💾 Read and write files to the cloud
8
8
  - 🧪 Fully compatible with Expo
9
9
  - 📱 iOS, Android & Web support
10
10
  - 🏎️ Lightning fast iCloud performance using native iOS APIs
11
- - 🌐 Google Drive REST API implementation for other platforms
11
+ - 🌐 Google Drive REST API implementation for all platforms
12
12
  - 🧬 Easy to use React Hooks API, or use the imperative `fs`-style API
13
13
  - 👌 Zero dependencies, small bundle size
14
14
 
@@ -36,14 +36,15 @@ Afterwards, [add the provided config plugin](https://react-native-cloud-storage.
36
36
  ```jsx
37
37
  import React from 'react';
38
38
  import { Platform, View, Text, Button } from 'react-native';
39
- import { CloudStorage, useCloudAvailable } from 'react-native-cloud-storage';
39
+ import { CloudStorage, CloudStorageProvider, useIsCloudAvailable } from 'react-native-cloud-storage';
40
40
 
41
41
  const App = () => {
42
- const cloudAvailable = useCloudAvailable();
42
+ const cloudAvailable = useIsCloudAvailable();
43
43
 
44
44
  React.useEffect(() => {
45
- if (Platform.OS !== 'ios') {
46
- CloudStorage.setGoogleDriveAccessToken('some-access-token'); // get via @react-native-google-signin/google-signin or similar
45
+ if (CloudStorage.getDefaultProvider() === CloudStorageProvider.GoogleDrive) {
46
+ // get access token via @react-native-google-signin/google-signin or similar
47
+ CloudStorage.setProviderOptions({ accessToken: 'some-access-token' });
47
48
  }
48
49
  }, []);
49
50
 
@@ -3,311 +3,101 @@ import Foundation
3
3
  @objc(CloudStorage)
4
4
  class CloudStorage: NSObject {
5
5
  @objc(fileExists:withScope:withResolver:withRejecter:)
6
- func fileExists(path: String, scope: String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
7
- let fileUrl: URL?
8
- do {
9
- fileUrl = try getFileURL(path, scope)
10
- } catch let error as NSError {
11
- reject(error.domain, error.userInfo["message"] as? String, error)
12
- return
6
+ func fileExists(path: String, scope: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
7
+ withPromise(resolve: resolve, reject: reject) {
8
+ let fileUrl = try CloudKitUtils.getFileURL(path: path, scope: scope)
9
+ return try FileUtils.checkFileExists(fileUrl: fileUrl)
13
10
  }
14
-
15
- let fileManager = FileManager.default
16
- let fileExists = fileManager.fileExists(atPath: fileUrl!.path)
17
- resolve(fileExists)
18
11
  }
19
12
 
20
13
  @objc(appendToFile:withData:withScope:withResolver:withRejecter:)
21
- func appendToFile(path: String, data: String, scope: String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
22
- // Append data to the file at path. If the file doesn't exist, create it.
23
- let fileUrl: URL?
24
- do {
25
- fileUrl = try getFileURL(path, scope)
26
- } catch let error as NSError {
27
- reject(error.domain, error.userInfo["message"] as? String, error)
28
- return
29
- }
14
+ func appendToFile(path: String, data: String, scope: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
15
+ withPromise(resolve: resolve, reject: reject) {
16
+ let fileUrl = try CloudKitUtils.getFileURL(path: path, scope: scope)
30
17
 
31
- let fileManager = FileManager.default
32
- if (!fileManager.fileExists(atPath: fileUrl!.path)) {
33
- do {
34
- try data.write(to: fileUrl!, atomically: true, encoding: .utf8)
35
- resolve(true)
36
- } catch {
37
- reject("ERR_WRITE_ERROR", "Error writing file \(path)", error)
38
- }
39
- } else {
40
- do {
41
- let fileHandle = try FileHandle(forWritingTo: fileUrl!)
42
- fileHandle.seekToEndOfFile()
43
- fileHandle.write(data.data(using: .utf8)!)
44
- fileHandle.closeFile()
45
- resolve(true)
46
- } catch {
47
- reject("ERR_WRITE_ERROR", "Error writing file \(path)", error)
18
+ var existingData = ""
19
+ if try FileUtils.checkFileExists(fileUrl: fileUrl) {
20
+ existingData = try FileUtils.readFile(fileUrl: fileUrl)
48
21
  }
22
+
23
+ let newData = existingData + data
24
+ return try FileUtils.writeFile(fileUrl: fileUrl, content: newData)
49
25
  }
50
26
  }
51
27
 
52
28
  @objc(createFile:withData:withScope:withOverwrite:withResolver:withRejecter:)
53
- func createFile(path: String, data: String, scope: String, overwrite: Bool, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
54
- let fileUrl: URL?
55
- do {
56
- fileUrl = try getFileURL(path, scope, overwrite ? nil : false)
57
- } catch let error as NSError {
58
- reject(error.domain, error.userInfo["message"] as? String, error)
59
- return
60
- }
29
+ func createFile(path: String, data: String, scope: String, overwrite: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
30
+ withPromise(resolve: resolve, reject: reject) {
31
+ let fileUrl = try CloudKitUtils.getFileURL(path: path, scope: scope)
32
+
33
+ if try (FileUtils.checkFileExists(fileUrl: fileUrl) && !overwrite) {
34
+ throw CloudStorageError.fileAlreadyExists(path: path)
35
+ }
61
36
 
62
- do {
63
- try data.write(to: fileUrl!, atomically: true, encoding: .utf8)
64
- resolve(true)
65
- } catch {
66
- reject("ERR_WRITE_ERROR", "Error writing file \(path)", error)
37
+ return try FileUtils.writeFile(fileUrl: fileUrl, content: data)
67
38
  }
68
39
  }
69
40
 
70
41
  @objc(createDirectory:withScope:withResolver:withRejecter:)
71
- func createDirectory(path: String, scope: String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
72
- let directoryUrl: URL?
73
- do {
74
- directoryUrl = try getFileURL(path, scope, false)
75
- } catch let error as NSError {
76
- reject(error.domain, error.userInfo["message"] as? String, error)
77
- return
78
- }
79
-
80
- let fileManager = FileManager.default
81
- do {
82
- try fileManager.createDirectory(at: directoryUrl!, withIntermediateDirectories: true, attributes: nil)
83
- resolve(true)
84
- } catch {
85
- reject("ERR_WRITE_ERROR", "Error creating directory \(path)", error)
42
+ func createDirectory(path: String, scope: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
43
+ withPromise(resolve: resolve, reject: reject) {
44
+ let fileUrl = try CloudKitUtils.getFileURL(path: path, scope: scope)
45
+ return try FileUtils.createDirectory(directoryUrl: fileUrl)
86
46
  }
87
47
  }
88
48
 
89
49
  @objc(listFiles:withScope:withResolver:withRejecter:)
90
- func listFiles(path: String, scope: String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
91
- let directoryUrl: URL?
92
- do {
93
- directoryUrl = try getFileURL(path, scope, true)
94
- } catch let error as NSError {
95
- reject(error.domain, error.userInfo["message"] as? String, error)
96
- return
97
- }
98
-
99
- let fileManager = FileManager.default
100
- do {
101
- let files = try fileManager.contentsOfDirectory(atPath: directoryUrl!.path)
102
- resolve(files)
103
- } catch {
104
- reject("ERR_READ_ERROR", "Error reading directory \(path)", error)
50
+ func listFiles(path: String, scope: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
51
+ withPromise(resolve: resolve, reject: reject) {
52
+ let fileUrl = try CloudKitUtils.getFileURL(path: path, scope: scope)
53
+ return try FileUtils.listFiles(directoryUrl: fileUrl)
105
54
  }
106
55
  }
107
56
 
108
57
  @objc(readFile:withScope:withResolver:withRejecter:)
109
- func readFile(path: String, scope: String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
110
- let fileUrl: URL?
111
- do {
112
- fileUrl = try getFileURL(path, scope, true)
113
- } catch let error as NSError {
114
- reject(error.domain, error.userInfo["message"] as? String, error)
115
- return
116
- }
117
-
118
- do {
119
- let fileContents = try String(contentsOf: fileUrl!, encoding: .utf8)
120
- resolve(fileContents)
121
- } catch {
122
- reject("ERR_READ_ERROR", "Error reading file \(path)", error)
58
+ func readFile(path: String, scope: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
59
+ withPromise(resolve: resolve, reject: reject) {
60
+ let fileUrl = try CloudKitUtils.getFileURL(path: path, scope: scope)
61
+ return try FileUtils.readFile(fileUrl: fileUrl)
123
62
  }
124
63
  }
125
64
 
126
65
  @objc(downloadFile:withScope:withResolver:withRejecter:)
127
- func downloadFile(path: String, scope: String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
128
- let fileManager = FileManager.default
129
-
130
- guard let directory = getDirectory(scope) else {
131
- let error = NSError(domain: "", code: 200, userInfo: [NSLocalizedDescriptionKey : "Error reading directory \(scope)"])
132
- reject("ERR_READ_ERROR", "Error reading directory \(scope)", error)
133
- return
134
- }
135
-
136
- // remove leading slashes
137
- let path = path.replacingOccurrences(of: "^/+", with: "", options: .regularExpression)
138
-
139
- // append path to scope directory and return URL
140
- let filePath = directory.appendingPathComponent(path)
141
-
142
- let isDownloadable = fileManager.isUbiquitousItem(at: filePath)
143
-
144
- if (!isDownloadable) {
145
- reject("ERR_FILE_NOT_DOWNLOADABLE", "File or directory \(path) is not an iCloud file", NSError(domain: "", code: 202, userInfo: [NSLocalizedDescriptionKey : "File or directory \(path) is not an iCloud file"]))
146
- return
147
- }
148
- do {
149
- // trigger download of file
150
- try fileManager.startDownloadingUbiquitousItem(at: filePath)
151
- } catch {
152
- let error = NSError(domain: "", code: 202, userInfo: [NSLocalizedDescriptionKey : "File or directory \(path) not downloadable"])
153
- reject("ERR_FILE_NOT_DOWNLOADABLE", "File or directory \(path) not downloadable", error)
154
- return
66
+ func downloadFile(path: String, scope: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
67
+ withPromise(resolve: resolve, reject: reject) {
68
+ let fileUrl = try CloudKitUtils.getFileURL(path: path, scope: scope)
69
+ return try CloudKitUtils.downloadFile(fileUrl: fileUrl)
155
70
  }
156
- resolve(true)
157
71
  }
158
72
 
159
-
160
73
  @objc(deleteFile:withScope:withResolver:withRejecter:)
161
- func deleteFile(path: String, scope: String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
162
- let fileUrl: URL?
163
- do {
164
- fileUrl = try getFileURL(path, scope, true)
165
- } catch let error as NSError {
166
- reject(error.domain, error.userInfo["message"] as? String, error)
167
- return
168
- }
169
-
170
- if (fileUrl!.hasDirectoryPath) {
171
- reject("ERR_PATH_IS_DIRECTORY", "Path \(path) is a directory", nil)
172
- return
173
- }
174
-
175
- let fileManager = FileManager.default
176
- do {
177
- try fileManager.removeItem(at: fileUrl!)
178
- resolve(true)
179
- } catch {
180
- reject("ERR_DELETE_ERROR", "Error deleting file \(path)", error)
74
+ func deleteFile(path: String, scope: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
75
+ withPromise(resolve: resolve, reject: reject) {
76
+ let fileUrl = try CloudKitUtils.getFileURL(path: path, scope: scope)
77
+ return try FileUtils.deleteFileOrDirectory(fileUrl: fileUrl)
181
78
  }
182
79
  }
183
80
 
184
81
  @objc(deleteDirectory:withRecursive:withScope:withResolver:withRejecter:)
185
- func deleteDirectory(path: String, recursive: Bool, scope: String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
186
- let fileUrl: URL?
187
- do {
188
- fileUrl = try getFileURL(path, scope, true)
189
- } catch let error as NSError {
190
- reject(error.domain, error.userInfo["message"] as? String, error)
191
- return
192
- }
193
-
194
- if (!fileUrl!.hasDirectoryPath) {
195
- reject("ERR_PATH_IS_FILE", "Path \(path) is a file", nil)
196
- return
197
- }
198
-
199
- if (!recursive) {
200
- // check if directory is empty
201
- let fileManager = FileManager.default
202
- do {
203
- print(fileUrl!.path)
204
- let files = try fileManager.contentsOfDirectory(atPath: fileUrl!.path)
205
- print(files)
206
- if (files.count > 0) {
207
- reject("ERR_DIRECTORY_NOT_EMPTY", "Directory \(path) is not empty", nil)
208
- return
209
- }
210
- } catch {
211
- reject("ERR_UNKNOWN", "Error reading directory \(path)", error)
212
- return
213
- }
214
- }
215
-
216
- let fileManager = FileManager.default
217
- do {
218
- try fileManager.removeItem(at: fileUrl!)
219
- resolve(true)
220
- } catch {
221
- reject("ERR_DELETE_ERROR", "Error deleting directory \(path)", error)
82
+ func deleteDirectory(path: String, recursive _: Bool, scope: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
83
+ withPromise(resolve: resolve, reject: reject) {
84
+ let fileUrl = try CloudKitUtils.getFileURL(path: path, scope: scope)
85
+ return try FileUtils.deleteFileOrDirectory(fileUrl: fileUrl)
222
86
  }
223
87
  }
224
88
 
225
89
  @objc(statFile:withScope:withResolver:withRejecter:)
226
- func statFile(path: String, scope: String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
227
- let fileUrl: URL?
228
- do {
229
- fileUrl = try getFileURL(path, scope, true)
230
- } catch let error as NSError {
231
- reject(error.domain, error.userInfo["message"] as? String, error)
232
- return
233
- }
234
-
235
- let fileManager = FileManager.default
236
- do {
237
- let attributes = try fileManager.attributesOfItem(atPath: fileUrl!.path)
238
- let size = attributes[FileAttributeKey.size] as! UInt64
239
- let birthtime = attributes[FileAttributeKey.creationDate] as! Date
240
- let mtime = attributes[FileAttributeKey.modificationDate] as! Date
241
- let isDirectory = attributes[FileAttributeKey.type] as! FileAttributeType == FileAttributeType.typeDirectory
242
- let isFile = attributes[FileAttributeKey.type] as! FileAttributeType == FileAttributeType.typeRegular
243
- let result = [
244
- "size": size,
245
- "birthtimeMs": birthtime.timeIntervalSince1970 * 1000,
246
- "mtimeMs": mtime.timeIntervalSince1970 * 1000,
247
- "isDirectory": isDirectory,
248
- "isFile": isFile
249
- ] as [String : Any]
250
- resolve(result)
251
- } catch {
252
- reject("ERR_STAT_ERROR", "Error getting stats for file \(path)", error)
90
+ func statFile(path: String, scope: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
91
+ withPromise(resolve: resolve, reject: reject) {
92
+ let fileUrl = try CloudKitUtils.getFileURL(path: path, scope: scope)
93
+ return try FileUtils.statFile(fileUrl: fileUrl).toDictionary()
253
94
  }
254
95
  }
255
96
 
256
97
  @objc(isCloudAvailable:withRejecter:)
257
- func isCloudAvailable(resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
258
- let token = FileManager.default.ubiquityIdentityToken
259
- resolve(token != nil)
260
- }
261
-
262
- /**
263
- Returns the iCloud directory URL for the given scope.
264
-
265
- - Parameter scope: The scope of the directory. Can be either "documents" or "app_data".
266
- - Returns: The URL of the iCloud directory.
267
- */
268
- private func getDirectory(_ scope: String) -> URL? {
269
- let fileManager = FileManager.default
270
- let isDocumentDirectory = scope.caseInsensitiveCompare("documents") == .orderedSame
271
- let ubiquityURL = fileManager.url(forUbiquityContainerIdentifier: nil)
272
- print(ubiquityURL)
273
- if (isDocumentDirectory) {
274
- return fileManager.urls(for: .documentDirectory, in: .userDomainMask).first
275
- } else {
276
- return ubiquityURL
277
- }
278
- }
279
-
280
- /**
281
- Parses a given path and directory scope to a full file URL. Does not check if the file exists.
282
-
283
- - Parameter path: The path of the file.
284
- - Parameter scope: The scope of the directory. Can be either "documents" or "app_data".
285
- - Parameter shouldExist: Whether the file should exist. If true, throws an error if the file does not exist. If false, throws an error if the file exists. If nil, does not check if the file exists.
286
- - Returns: The full URL of the file.
287
- - Throws: An NSError if the scope directory couldn't be found or the file should exist but doesn't or vice versa.
288
- */
289
- private func getFileURL(_ path: String, _ scope: String, _ shouldExist: Bool? = nil) throws -> URL? {
290
- let fileManager = FileManager.default
291
-
292
- guard let directory = getDirectory(scope) else {
293
- throw NSError(domain: "ERR_DIRECTORY_NOT_FOUND", code: 0, userInfo: ["message": "Directory for scope \(scope) not found"])
98
+ func isCloudAvailable(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
99
+ withPromise(resolve: resolve, reject: reject) {
100
+ CloudKitUtils.isCloudKitAvailable()
294
101
  }
295
-
296
- // remove leading slashes
297
- let path = path.replacingOccurrences(of: "^/+", with: "", options: .regularExpression)
298
-
299
- // append path to scope directory and return URL
300
- let filePath = directory.appendingPathComponent(path)
301
-
302
- if (shouldExist != nil) {
303
- let fileExists = fileManager.fileExists(atPath: filePath.path)
304
- if (shouldExist! && !fileExists) {
305
- throw NSError(domain: "ERR_FILE_NOT_FOUND", code: 0, userInfo: ["message": "File or directory \(path) not found"])
306
- } else if (!shouldExist! && fileExists) {
307
- throw NSError(domain: "ERR_FILE_EXISTS", code: 0, userInfo: ["message": "File or directory \(path) already exists"])
308
- }
309
- }
310
-
311
- return filePath
312
102
  }
313
103
  }
@@ -8,6 +8,11 @@
8
8
 
9
9
  /* Begin PBXBuildFile section */
10
10
  50ADE3732B56EE1300DB5583 /* CloudStorageEventEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5009AB9B2B56ECD30058E83A /* CloudStorageEventEmitter.swift */; };
11
+ 50BC353B2CA6ED940085E8B9 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BC353A2CA6ED940085E8B9 /* FileUtils.swift */; };
12
+ 50BC353F2CA6EF760085E8B9 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BC353E2CA6EF760085E8B9 /* Types.swift */; };
13
+ 50BC35412CA6F0E80085E8B9 /* CloudKitUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BC35402CA6F0E80085E8B9 /* CloudKitUtils.swift */; };
14
+ 50BC35452CA6FA6D0085E8B9 /* CloudStorageError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BC35442CA6FA6D0085E8B9 /* CloudStorageError.swift */; };
15
+ 50BC35472CA6FF3F0085E8B9 /* Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BC35462CA6FF3F0085E8B9 /* Promise.swift */; };
11
16
  F4FF95D7245B92E800C19C63 /* CloudStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FF95D6245B92E800C19C63 /* CloudStorage.swift */; };
12
17
  /* End PBXBuildFile section */
13
18
 
@@ -27,6 +32,11 @@
27
32
  134814201AA4EA6300B7C361 /* libCloudStorage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCloudStorage.a; sourceTree = BUILT_PRODUCTS_DIR; };
28
33
  5009AB9B2B56ECD30058E83A /* CloudStorageEventEmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudStorageEventEmitter.swift; sourceTree = "<group>"; };
29
34
  50ADE3712B56EDFB00DB5583 /* CloudStorageEventEmitter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CloudStorageEventEmitter.m; sourceTree = "<group>"; };
35
+ 50BC353A2CA6ED940085E8B9 /* FileUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = "<group>"; };
36
+ 50BC353E2CA6EF760085E8B9 /* Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Types.swift; sourceTree = "<group>"; };
37
+ 50BC35402CA6F0E80085E8B9 /* CloudKitUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudKitUtils.swift; sourceTree = "<group>"; };
38
+ 50BC35442CA6FA6D0085E8B9 /* CloudStorageError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudStorageError.swift; sourceTree = "<group>"; };
39
+ 50BC35462CA6FF3F0085E8B9 /* Promise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Promise.swift; sourceTree = "<group>"; };
30
40
  B3E7B5891CC2AC0600A0062D /* CloudStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CloudStorage.m; sourceTree = "<group>"; };
31
41
  F4FF95D5245B92E700C19C63 /* CloudStorage-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CloudStorage-Bridging-Header.h"; sourceTree = "<group>"; };
32
42
  F4FF95D6245B92E800C19C63 /* CloudStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudStorage.swift; sourceTree = "<group>"; };
@@ -51,9 +61,22 @@
51
61
  name = Products;
52
62
  sourceTree = "<group>";
53
63
  };
64
+ 50BC35482CA704A60085E8B9 /* Utils */ = {
65
+ isa = PBXGroup;
66
+ children = (
67
+ 50BC35402CA6F0E80085E8B9 /* CloudKitUtils.swift */,
68
+ 50BC35442CA6FA6D0085E8B9 /* CloudStorageError.swift */,
69
+ 50BC353A2CA6ED940085E8B9 /* FileUtils.swift */,
70
+ 50BC35462CA6FF3F0085E8B9 /* Promise.swift */,
71
+ 50BC353E2CA6EF760085E8B9 /* Types.swift */,
72
+ );
73
+ path = Utils;
74
+ sourceTree = "<group>";
75
+ };
54
76
  58B511D21A9E6C8500147676 = {
55
77
  isa = PBXGroup;
56
78
  children = (
79
+ 50BC35482CA704A60085E8B9 /* Utils */,
57
80
  50ADE3712B56EDFB00DB5583 /* CloudStorageEventEmitter.m */,
58
81
  5009AB9B2B56ECD30058E83A /* CloudStorageEventEmitter.swift */,
59
82
  F4FF95D6245B92E800C19C63 /* CloudStorage.swift */,
@@ -120,8 +143,13 @@
120
143
  isa = PBXSourcesBuildPhase;
121
144
  buildActionMask = 2147483647;
122
145
  files = (
146
+ 50BC353B2CA6ED940085E8B9 /* FileUtils.swift in Sources */,
147
+ 50BC35412CA6F0E80085E8B9 /* CloudKitUtils.swift in Sources */,
148
+ 50BC35472CA6FF3F0085E8B9 /* Promise.swift in Sources */,
149
+ 50BC35452CA6FA6D0085E8B9 /* CloudStorageError.swift in Sources */,
123
150
  F4FF95D7245B92E800C19C63 /* CloudStorage.swift in Sources */,
124
151
  50ADE3732B56EE1300DB5583 /* CloudStorageEventEmitter.swift in Sources */,
152
+ 50BC353F2CA6EF760085E8B9 /* Types.swift in Sources */,
125
153
  );
126
154
  runOnlyForDeploymentPostprocessing = 0;
127
155
  };
@@ -6,7 +6,7 @@
6
6
  RCT_EXTERN_METHOD(supportedEvents)
7
7
  RCT_EXTERN_METHOD(startObserving)
8
8
  RCT_EXTERN_METHOD(stopObserving)
9
- RCT_EXTERN_METHOD(iCloudIdentityChanged)
9
+ RCT_EXTERN_METHOD(iCloudIdentityChanged:(NSNotification *)notification)
10
10
 
11
11
  + (BOOL)requiresMainQueueSetup
12
12
  {
@@ -10,7 +10,7 @@ class CloudStorageEventEmitter: RCTEventEmitter {
10
10
  }
11
11
 
12
12
  override func supportedEvents() -> [String]! {
13
- return ["RNCloudStorage.cloud.availability-changed"]
13
+ ["RNCloudStorage.cloud.availability-changed"]
14
14
  }
15
15
 
16
16
  override func startObserving() {
@@ -23,8 +23,8 @@ class CloudStorageEventEmitter: RCTEventEmitter {
23
23
  NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NSUbiquityIdentityDidChange, object: nil)
24
24
  }
25
25
 
26
- @objc func iCloudIdentityChanged(_ notification: Notification? = nil) {
27
- let token = FileManager.default.ubiquityIdentityToken
28
- CloudStorageEventEmitter.shared.sendEvent(withName: "RNCloudStorage.cloud.availability-changed", body: ["available": token != nil])
26
+ @objc func iCloudIdentityChanged(_: Notification? = nil) {
27
+ let isAvailable = CloudKitUtils.isCloudKitAvailable()
28
+ CloudStorageEventEmitter.shared.sendEvent(withName: "RNCloudStorage.cloud.availability-changed", body: ["available": isAvailable])
29
29
  }
30
30
  }
@@ -0,0 +1,112 @@
1
+ //
2
+ // CloudKitUtils.swift
3
+ // CloudStorage
4
+ //
5
+ // Created by Maximilian Krause on 27.09.24.
6
+ // Copyright © 2024 Kuatsu App Agency. All rights reserved.
7
+ //
8
+
9
+ import Foundation
10
+
11
+ enum CloudKitUtils {
12
+ private static let fileManager = FileManager.default
13
+
14
+ /**
15
+ Checks if the CloudKit service is available.
16
+
17
+ - Returns: True if the CloudKit service is available, false otherwise.
18
+ */
19
+ static func isCloudKitAvailable() -> Bool {
20
+ fileManager.ubiquityIdentityToken != nil
21
+ }
22
+
23
+ /**
24
+ Downloads a file from iCloud.
25
+
26
+ - Parameter fileUrl: The URL of the file to download.
27
+ - Throws: An NSError if the file is not downloadable or the download failed.
28
+ */
29
+ static func downloadFile(fileUrl: URL) throws {
30
+ let isDownloadable = fileManager.isUbiquitousItem(at: fileUrl)
31
+
32
+ if !isDownloadable {
33
+ throw CloudStorageError.fileNotDownloadable(path: fileUrl.path)
34
+ }
35
+
36
+ do {
37
+ // trigger download of file
38
+ try fileManager.startDownloadingUbiquitousItem(at: fileUrl)
39
+ } catch {
40
+ throw CloudStorageError.fileNotDownloadable(path: fileUrl.path)
41
+ }
42
+ }
43
+
44
+ /**
45
+ Returns the iCloud directory URL for the given scope.
46
+
47
+ - Parameter scope: The scope of the directory.
48
+ - Returns: The URL of the iCloud directory, or nil if no directory is found.
49
+ */
50
+ private static func getScopeDirectory(scope: DirectoryScope) -> URL? {
51
+ switch scope {
52
+ case .appData:
53
+ appDataDirectory
54
+ case .documents:
55
+ documentsDirectory
56
+ }
57
+ }
58
+
59
+ /**
60
+ Parses a given path and directory scope to a full file URL.
61
+
62
+ - Parameter path: The path of the file.
63
+ - Parameter scope: The scope of the directory.
64
+ - Parameter shouldExist: Whether the file should exist. If true, throws an error if the file does not exist. If false, throws an error if the file exists. If nil, does not check if the file exists.
65
+ - Returns: The full URL of the file.
66
+ - Throws: An NSError if the scope directory couldn't be found or the file should exist but doesn't or vice versa.
67
+ */
68
+ static func getFileURL(path: String, scope: DirectoryScope, _ shouldExist: Bool? = nil) throws -> URL {
69
+ guard let directory = getScopeDirectory(scope: scope) else {
70
+ throw CloudStorageError.directoryNotFound(path: path)
71
+ }
72
+
73
+ // append path to scope directory
74
+ let fileUrl = directory.appendingPathComponent(FileUtils.sanitizePath(path: path))
75
+
76
+ if shouldExist != nil {
77
+ let fileExists = try FileUtils.checkFileExists(fileUrl: fileUrl)
78
+ if shouldExist! && !fileExists {
79
+ throw CloudStorageError.fileNotFound(path: path)
80
+ } else if !shouldExist! && fileExists {
81
+ throw CloudStorageError.fileAlreadyExists(path: path)
82
+ }
83
+ }
84
+
85
+ return fileUrl
86
+ }
87
+
88
+ /**
89
+ Parses a given path and unchecked directory scope to a full file URL.
90
+
91
+ - Parameter path: The path of the file.
92
+ - Parameter scope: The scope of the directory. Will be checked for validity.
93
+ - Parameter shouldExist: Whether the file should exist. If true, throws an error if the file does not exist. If false, throws an error if the file exists. If nil, does not check if the file exists.
94
+ - Returns: The full URL of the file.
95
+ - Throws: An NSError if the scope directory couldn't be found or the file should exist but doesn't or vice versa.
96
+ */
97
+ static func getFileURL(path: String, scope: String, _ shouldExist: Bool? = nil) throws -> URL {
98
+ guard let directoryScope = DirectoryScope(rawValue: scope) else {
99
+ throw CloudStorageError.invalidScope(scope: scope)
100
+ }
101
+
102
+ return try getFileURL(path: path, scope: directoryScope, shouldExist)
103
+ }
104
+
105
+ static var appDataDirectory: URL? {
106
+ fileManager.url(forUbiquityContainerIdentifier: nil)
107
+ }
108
+
109
+ static var documentsDirectory: URL? {
110
+ fileManager.urls(for: .documentDirectory, in: .userDomainMask).first
111
+ }
112
+ }