@capacitor/filesystem 7.0.2-nightly-20250526T150552.0 → 7.1.0-dev.2

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 (39) hide show
  1. package/CapacitorFilesystem.podspec +4 -3
  2. package/LICENSE +17 -19
  3. package/Package.swift +10 -4
  4. package/README.md +149 -78
  5. package/android/build.gradle +12 -22
  6. package/android/src/main/kotlin/com/capacitorjs/plugins/filesystem/FilesystemErrors.kt +101 -0
  7. package/android/src/main/kotlin/com/capacitorjs/plugins/filesystem/FilesystemMethodOptions.kt +129 -0
  8. package/android/src/main/kotlin/com/capacitorjs/plugins/filesystem/FilesystemMethodResults.kt +65 -0
  9. package/android/src/main/kotlin/com/capacitorjs/plugins/filesystem/FilesystemPlugin.kt +412 -0
  10. package/android/src/main/kotlin/com/capacitorjs/plugins/filesystem/LegacyFilesystemImplementation.kt +169 -0
  11. package/android/src/main/kotlin/com/capacitorjs/plugins/filesystem/PluginResultExtensions.kt +25 -0
  12. package/dist/docs.json +227 -145
  13. package/dist/esm/definitions.d.ts +102 -64
  14. package/dist/esm/definitions.js +25 -3
  15. package/dist/esm/definitions.js.map +1 -1
  16. package/dist/esm/index.js +3 -1
  17. package/dist/esm/index.js.map +1 -1
  18. package/dist/esm/web.d.ts +3 -1
  19. package/dist/esm/web.js +10 -10
  20. package/dist/esm/web.js.map +1 -1
  21. package/dist/plugin.cjs.js +40 -14
  22. package/dist/plugin.cjs.js.map +1 -1
  23. package/dist/plugin.js +41 -16
  24. package/dist/plugin.js.map +1 -1
  25. package/ios/Sources/FilesystemPlugin/CAPPluginCall+Accelerators.swift +73 -0
  26. package/ios/Sources/FilesystemPlugin/FilesystemConstants.swift +61 -0
  27. package/ios/Sources/FilesystemPlugin/FilesystemError.swift +57 -0
  28. package/ios/Sources/FilesystemPlugin/FilesystemLocationResolver.swift +39 -0
  29. package/ios/Sources/FilesystemPlugin/FilesystemOperation.swift +24 -0
  30. package/ios/Sources/FilesystemPlugin/FilesystemOperationExecutor.swift +116 -0
  31. package/ios/Sources/FilesystemPlugin/FilesystemPlugin.swift +103 -264
  32. package/ios/Sources/FilesystemPlugin/IONFileStructures+Converters.swift +60 -0
  33. package/ios/Sources/FilesystemPlugin/{Filesystem.swift → LegacyFilesystemImplementation.swift} +18 -179
  34. package/package.json +28 -24
  35. package/android/src/main/java/com/capacitorjs/plugins/filesystem/Filesystem.java +0 -414
  36. package/android/src/main/java/com/capacitorjs/plugins/filesystem/FilesystemPlugin.java +0 -551
  37. package/android/src/main/java/com/capacitorjs/plugins/filesystem/exceptions/CopyFailedException.java +0 -16
  38. package/android/src/main/java/com/capacitorjs/plugins/filesystem/exceptions/DirectoryExistsException.java +0 -16
  39. package/android/src/main/java/com/capacitorjs/plugins/filesystem/exceptions/DirectoryNotFoundException.java +0 -16
@@ -0,0 +1,116 @@
1
+ import Capacitor
2
+ import Foundation
3
+ import IONFilesystemLib
4
+ import Combine
5
+
6
+ class FilesystemOperationExecutor {
7
+ let service: FileService
8
+ private var cancellables = Set<AnyCancellable>()
9
+
10
+ init(service: FileService) {
11
+ self.service = service
12
+ }
13
+
14
+ func execute(_ operation: FilesystemOperation, _ call: CAPPluginCall) {
15
+ do {
16
+ var resultData: PluginCallResultData?
17
+
18
+ switch operation {
19
+ case .readFile(let url, let encoding):
20
+ let data = try service.readEntireFile(atURL: url, withEncoding: encoding).textValue
21
+ resultData = [Constants.ResultDataKey.data: data]
22
+ case .readFileInChunks(let url, let encoding, let chunkSize):
23
+ try processFileInChunks(at: url, withEncoding: encoding, chunkSize: chunkSize, for: operation, call)
24
+ return
25
+ case .write(let url, let encodingMapper, let recursive):
26
+ try service.saveFile(atURL: url, withEncodingAndData: encodingMapper, includeIntermediateDirectories: recursive)
27
+ resultData = [Constants.ResultDataKey.uri: url.absoluteString]
28
+ case .append(let url, let encodingMapper, let recursive):
29
+ try service.appendData(encodingMapper, atURL: url, includeIntermediateDirectories: recursive)
30
+ case .delete(let url):
31
+ try service.deleteFile(atURL: url)
32
+ case .mkdir(let url, let recursive):
33
+ try service.createDirectory(atURL: url, includeIntermediateDirectories: recursive)
34
+ case .rmdir(let url, let recursive):
35
+ try service.removeDirectory(atURL: url, includeIntermediateDirectories: recursive)
36
+ case .readdir(let url):
37
+ let directoryAttributes = try service.listDirectory(atURL: url)
38
+ .map { try fetchItemAttributesJSObject(using: service, atURL: $0) }
39
+ resultData = [Constants.ResultDataKey.files: directoryAttributes]
40
+ case .stat(let url):
41
+ resultData = try fetchItemAttributesJSObject(using: service, atURL: url)
42
+ case .getUri(let url):
43
+ resultData = [Constants.ResultDataKey.uri: url.absoluteString]
44
+ case .rename(let source, let destination):
45
+ try service.renameItem(fromURL: source, toURL: destination)
46
+ case .copy(let source, let destination):
47
+ try service.copyItem(fromURL: source, toURL: destination)
48
+ resultData = [Constants.ResultDataKey.uri: destination.absoluteString]
49
+ }
50
+
51
+ call.handleSuccess(resultData)
52
+ } catch {
53
+ call.handleError(mapError(error, for: operation))
54
+ }
55
+ }
56
+ }
57
+
58
+ private extension FilesystemOperationExecutor {
59
+ func processFileInChunks(at url: URL, withEncoding encoding: IONFILEEncoding, chunkSize: Int, for operation: FilesystemOperation, _ call: CAPPluginCall) throws {
60
+ let chunkSizeToUse = chunkSizeToUse(basedOn: chunkSize, and: encoding)
61
+ try service.readFileInChunks(atURL: url, withEncoding: encoding, andChunkSize: chunkSizeToUse)
62
+ .sink(receiveCompletion: { completion in
63
+ switch completion {
64
+ case .finished:
65
+ call.handleSuccess([Constants.ResultDataKey.data: Constants.ConfigurationValue.endOfFile])
66
+ case .failure(let error):
67
+ call.handleError(self.mapError(error, for: operation))
68
+ }
69
+ }, receiveValue: {
70
+ call.handleSuccess([Constants.ResultDataKey.data: $0.textValue], true)
71
+ })
72
+ .store(in: &cancellables)
73
+ }
74
+
75
+ private func chunkSizeToUse(basedOn chunkSize: Int, and encoding: IONFILEEncoding) -> Int {
76
+ // When dealing with byte buffers, we need chunk size that are multiples of 3
77
+ // We're treating byte buffers as base64 data, and size multiple of 3 makes it so that chunks can be concatenated
78
+ encoding == .byteBuffer ? chunkSize - chunkSize % 3 + 3 : chunkSize
79
+ }
80
+
81
+ func mapError(_ error: Error, for operation: FilesystemOperation) -> FilesystemError {
82
+ var path = ""
83
+ var method: IONFileMethod = IONFileMethod.getUri
84
+ switch operation {
85
+ case .readFile(let url, _): path = url.absoluteString; method = .readFile
86
+ case .readFileInChunks(let url, _, _): path = url.absoluteString; method = .readFileInChunks
87
+ case .write(let url, _, _): path = url.absoluteString; method = .writeFile
88
+ case .append(let url, _, _): path = url.absoluteString; method = .appendFile
89
+ case .delete(let url): path = url.absoluteString; method = .deleteFile
90
+ case .mkdir(let url, _): path = url.absoluteString; method = .mkdir
91
+ case .rmdir(let url, _): path = url.absoluteString; method = .rmdir
92
+ case .readdir(let url): path = url.absoluteString; method = .readdir
93
+ case .stat(let url): path = url.absoluteString; method = .stat
94
+ case .getUri(let url): return FilesystemError.invalidPath(url.absoluteString)
95
+ case .rename(let sourceUrl, _): path = sourceUrl.absoluteString; method = .rename
96
+ case .copy(let sourceUrl, _): path = sourceUrl.absoluteString; method = .copy
97
+ }
98
+
99
+ return mapError(error, withPath: path, andMethod: method)
100
+ }
101
+
102
+ private func mapError(_ error: Error, withPath path: String, andMethod method: IONFileMethod) -> FilesystemError {
103
+ return switch error {
104
+ case IONFILEDirectoryManagerError.notEmpty: .cannotDeleteChildren
105
+ case IONFILEDirectoryManagerError.alreadyExists: .directoryAlreadyExists(path)
106
+ case IONFILEFileManagerError.missingParentFolder: .parentDirectoryMissing
107
+ case IONFILEFileManagerError.fileNotFound: .fileNotFound(method: method, path)
108
+ default: .operationFailed(method: method, error)
109
+ }
110
+ }
111
+
112
+ func fetchItemAttributesJSObject(using service: FileService, atURL url: URL) throws -> IONFILEItemAttributeModel.JSResult {
113
+ let attributes = try service.getItemAttributes(atURL: url)
114
+ return attributes.toJSResult(with: url)
115
+ }
116
+ }
@@ -1,5 +1,8 @@
1
1
  import Foundation
2
2
  import Capacitor
3
+ import IONFilesystemLib
4
+
5
+ typealias FileService = any IONFILEDirectoryManager & IONFILEFileManager
3
6
 
4
7
  /**
5
8
  * Please read the Capacitor iOS Plugin Development Guide
@@ -11,6 +14,7 @@ public class FilesystemPlugin: CAPPlugin, CAPBridgedPlugin {
11
14
  public let jsName = "Filesystem"
12
15
  public let pluginMethods: [CAPPluginMethod] = [
13
16
  CAPPluginMethod(name: "readFile", returnType: CAPPluginReturnPromise),
17
+ CAPPluginMethod(name: "readFileInChunks", returnType: CAPPluginReturnCallback),
14
18
  CAPPluginMethod(name: "writeFile", returnType: CAPPluginReturnPromise),
15
19
  CAPPluginMethod(name: "appendFile", returnType: CAPPluginReturnPromise),
16
20
  CAPPluginMethod(name: "deleteFile", returnType: CAPPluginReturnPromise),
@@ -25,31 +29,48 @@ public class FilesystemPlugin: CAPPlugin, CAPBridgedPlugin {
25
29
  CAPPluginMethod(name: "requestPermissions", returnType: CAPPluginReturnPromise),
26
30
  CAPPluginMethod(name: "downloadFile", returnType: CAPPluginReturnPromise)
27
31
  ]
28
- private let implementation = Filesystem()
29
32
 
33
+ private let legacyImplementation = LegacyFilesystemImplementation()
34
+
35
+ private var fileService: FileService?
36
+
37
+ override public func load() {
38
+ self.fileService = IONFILEManager()
39
+ }
40
+
41
+ func getService() -> Result<FileService, FilesystemError> {
42
+ if fileService == nil { load() }
43
+ return fileService.map(Result.success) ?? .failure(.bridgeNotInitialised)
44
+ }
45
+
46
+ @objc override public func checkPermissions(_ call: CAPPluginCall) {
47
+ call.handlePermissionSuccess()
48
+ }
49
+
50
+ @objc override public func requestPermissions(_ call: CAPPluginCall) {
51
+ call.handlePermissionSuccess()
52
+ }
53
+ }
54
+
55
+ // MARK: - Public API Methods
56
+ private extension FilesystemPlugin {
30
57
  /**
31
58
  * Read a file from the filesystem.
32
59
  */
33
60
  @objc func readFile(_ call: CAPPluginCall) {
34
- let encoding = call.getString("encoding")
35
-
36
- guard let file = call.getString("path") else {
37
- handleError(call, "path must be provided and must be a string.")
38
- return
61
+ let encoding = call.getEncoding(Constants.MethodParameter.encoding)
62
+ performSinglePathOperation(call) {
63
+ .readFile(url: $0, encoding: encoding)
39
64
  }
40
- let directory = call.getString("directory")
65
+ }
41
66
 
42
- guard let fileUrl = implementation.getFileUrl(at: file, in: directory) else {
43
- handleError(call, "Invalid path")
44
- return
67
+ @objc func readFileInChunks(_ call: CAPPluginCall) {
68
+ let encoding = call.getEncoding(Constants.MethodParameter.encoding)
69
+ guard let chunkSize = call.getInt(Constants.MethodParameter.chunkSize) else {
70
+ return call.handleError(.invalidInput(method: call.getIONFileMethod()))
45
71
  }
46
- do {
47
- let data = try implementation.readFile(at: fileUrl, with: encoding)
48
- call.resolve([
49
- "data": data
50
- ])
51
- } catch let error as NSError {
52
- handleError(call, error.localizedDescription, error)
72
+ performSinglePathOperation(call) {
73
+ .readFileInChunks(url: $0, encoding: encoding, chunkSize: chunkSize)
53
74
  }
54
75
  }
55
76
 
@@ -57,33 +78,13 @@ public class FilesystemPlugin: CAPPlugin, CAPBridgedPlugin {
57
78
  * Write a file to the filesystem.
58
79
  */
59
80
  @objc func writeFile(_ call: CAPPluginCall) {
60
- let encoding = call.getString("encoding")
61
- let recursive = call.getBool("recursive") ?? false
62
-
63
- guard let file = call.getString("path") else {
64
- handleError(call, "path must be provided and must be a string.")
65
- return
81
+ guard let encodingMapper = call.getEncodingMapper() else {
82
+ return call.handleError(.invalidInput(method: call.getIONFileMethod()))
66
83
  }
84
+ let recursive = call.getBool(Constants.MethodParameter.recursive, false)
67
85
 
68
- guard let data = call.getString("data") else {
69
- handleError(call, "Data must be provided and must be a string.")
70
- return
71
- }
72
-
73
- let directory = call.getString("directory")
74
-
75
- guard let fileUrl = implementation.getFileUrl(at: file, in: directory) else {
76
- handleError(call, "Invalid path")
77
- return
78
- }
79
-
80
- do {
81
- let path = try implementation.writeFile(at: fileUrl, with: data, recursive: recursive, with: encoding)
82
- call.resolve([
83
- "uri": path
84
- ])
85
- } catch let error as NSError {
86
- handleError(call, error.localizedDescription, error)
86
+ performSinglePathOperation(call) {
87
+ .write(url: $0, encodingMapper: encodingMapper, recursive: recursive)
87
88
  }
88
89
  }
89
90
 
@@ -91,29 +92,13 @@ public class FilesystemPlugin: CAPPlugin, CAPBridgedPlugin {
91
92
  * Append to a file.
92
93
  */
93
94
  @objc func appendFile(_ call: CAPPluginCall) {
94
- let encoding = call.getString("encoding")
95
-
96
- guard let file = call.getString("path") else {
97
- handleError(call, "path must be provided and must be a string.")
98
- return
99
- }
100
-
101
- guard let data = call.getString("data") else {
102
- handleError(call, "Data must be provided and must be a string.")
103
- return
95
+ guard let encodingMapper = call.getEncodingMapper() else {
96
+ return call.handleError(.invalidInput(method: call.getIONFileMethod()))
104
97
  }
98
+ let recursive = call.getBool(Constants.MethodParameter.recursive, false)
105
99
 
106
- let directory = call.getString("directory")
107
- guard let fileUrl = implementation.getFileUrl(at: file, in: directory) else {
108
- handleError(call, "Invalid path")
109
- return
110
- }
111
-
112
- do {
113
- try implementation.appendFile(at: fileUrl, with: data, recursive: false, with: encoding)
114
- call.resolve()
115
- } catch let error as NSError {
116
- handleError(call, error.localizedDescription, error)
100
+ performSinglePathOperation(call) {
101
+ .append(url: $0, encodingMapper: encodingMapper, recursive: recursive)
117
102
  }
118
103
  }
119
104
 
@@ -121,22 +106,8 @@ public class FilesystemPlugin: CAPPlugin, CAPBridgedPlugin {
121
106
  * Delete a file.
122
107
  */
123
108
  @objc func deleteFile(_ call: CAPPluginCall) {
124
- guard let file = call.getString("path") else {
125
- handleError(call, "path must be provided and must be a string.")
126
- return
127
- }
128
-
129
- let directory = call.getString("directory")
130
- guard let fileUrl = implementation.getFileUrl(at: file, in: directory) else {
131
- handleError(call, "Invalid path")
132
- return
133
- }
134
-
135
- do {
136
- try implementation.deleteFile(at: fileUrl)
137
- call.resolve()
138
- } catch let error as NSError {
139
- handleError(call, error.localizedDescription, error)
109
+ performSinglePathOperation(call) {
110
+ .delete(url: $0)
140
111
  }
141
112
  }
142
113
 
@@ -144,23 +115,10 @@ public class FilesystemPlugin: CAPPlugin, CAPBridgedPlugin {
144
115
  * Make a new directory, optionally creating parent folders first.
145
116
  */
146
117
  @objc func mkdir(_ call: CAPPluginCall) {
147
- guard let path = call.getString("path") else {
148
- handleError(call, "path must be provided and must be a string.")
149
- return
150
- }
151
-
152
- let recursive = call.getBool("recursive") ?? false
153
- let directory = call.getString("directory")
154
- guard let fileUrl = implementation.getFileUrl(at: path, in: directory) else {
155
- handleError(call, "Invalid path")
156
- return
157
- }
118
+ let recursive = call.getBool(Constants.MethodParameter.recursive, false)
158
119
 
159
- do {
160
- try implementation.mkdir(at: fileUrl, recursive: recursive)
161
- call.resolve()
162
- } catch let error as NSError {
163
- handleError(call, error.localizedDescription, error)
120
+ performSinglePathOperation(call) {
121
+ .mkdir(url: $0, recursive: recursive)
164
122
  }
165
123
  }
166
124
 
@@ -168,24 +126,10 @@ public class FilesystemPlugin: CAPPlugin, CAPBridgedPlugin {
168
126
  * Remove a directory.
169
127
  */
170
128
  @objc func rmdir(_ call: CAPPluginCall) {
171
- guard let path = call.getString("path") else {
172
- handleError(call, "path must be provided and must be a string.")
173
- return
174
- }
129
+ let recursive = call.getBool(Constants.MethodParameter.recursive, false)
175
130
 
176
- let directory = call.getString("directory")
177
- guard let fileUrl = implementation.getFileUrl(at: path, in: directory) else {
178
- handleError(call, "Invalid path")
179
- return
180
- }
181
-
182
- let recursive = call.getBool("recursive") ?? false
183
-
184
- do {
185
- try implementation.rmdir(at: fileUrl, recursive: recursive)
186
- call.resolve()
187
- } catch let error as NSError {
188
- handleError(call, error.localizedDescription, error)
131
+ performSinglePathOperation(call) {
132
+ .rmdir(url: $0, recursive: recursive)
189
133
  }
190
134
  }
191
135
 
@@ -193,130 +137,29 @@ public class FilesystemPlugin: CAPPlugin, CAPBridgedPlugin {
193
137
  * Read the contents of a directory.
194
138
  */
195
139
  @objc func readdir(_ call: CAPPluginCall) {
196
- guard let path = call.getString("path") else {
197
- handleError(call, "path must be provided and must be a string.")
198
- return
199
- }
200
-
201
- let directory = call.getString("directory")
202
- guard let fileUrl = implementation.getFileUrl(at: path, in: directory) else {
203
- handleError(call, "Invalid path")
204
- return
205
- }
206
-
207
- do {
208
- let directoryContents = try implementation.readdir(at: fileUrl)
209
- let directoryContent = try directoryContents.map {(url: URL) -> [String: Any] in
210
- let attr = try implementation.stat(at: url)
211
- var ctime: UInt64 = 0
212
- var mtime: UInt64 = 0
213
-
214
- if let ctimeSeconds = (attr[.creationDate] as? Date)?.timeIntervalSince1970 {
215
- ctime = UInt64((ctimeSeconds * 1000).rounded())
216
- }
217
-
218
- if let mtimeSeconds = (attr[.modificationDate] as? Date)?.timeIntervalSince1970 {
219
- mtime = UInt64((mtimeSeconds * 1000).rounded())
220
- }
221
- return [
222
- "name": url.lastPathComponent,
223
- "type": implementation.getType(from: attr),
224
- "size": attr[.size] as? UInt64 ?? 0,
225
- "ctime": ctime,
226
- "mtime": mtime,
227
- "uri": url.absoluteString
228
- ]
229
- }
230
- call.resolve([
231
- "files": directoryContent
232
- ])
233
- } catch {
234
- handleError(call, error.localizedDescription, error)
140
+ performSinglePathOperation(call) {
141
+ .readdir(url: $0)
235
142
  }
236
143
  }
237
144
 
238
145
  @objc func stat(_ call: CAPPluginCall) {
239
- guard let path = call.getString("path") else {
240
- handleError(call, "path must be provided and must be a string.")
241
- return
242
- }
243
-
244
- let directory = call.getString("directory")
245
- guard let fileUrl = implementation.getFileUrl(at: path, in: directory) else {
246
- handleError(call, "Invalid path")
247
- return
248
- }
249
-
250
- do {
251
- let attr = try implementation.stat(at: fileUrl)
252
-
253
- var ctime: UInt64 = 0
254
- var mtime: UInt64 = 0
255
-
256
- if let ctimeSeconds = (attr[.creationDate] as? Date)?.timeIntervalSince1970 {
257
- ctime = UInt64((ctimeSeconds * 1000).rounded())
258
- }
259
-
260
- if let mtimeSeconds = (attr[.modificationDate] as? Date)?.timeIntervalSince1970 {
261
- mtime = UInt64((mtimeSeconds * 1000).rounded())
262
- }
263
-
264
- call.resolve([
265
- "type": implementation.getType(from: attr),
266
- "size": attr[.size] as? UInt64 ?? 0,
267
- "ctime": ctime,
268
- "mtime": mtime,
269
- "uri": fileUrl.absoluteString
270
- ])
271
- } catch {
272
- handleError(call, error.localizedDescription, error)
146
+ performSinglePathOperation(call) {
147
+ .stat(url: $0)
273
148
  }
274
149
  }
275
150
 
276
151
  @objc func getUri(_ call: CAPPluginCall) {
277
- guard let path = call.getString("path") else {
278
- handleError(call, "path must be provided and must be a string.")
279
- return
280
- }
281
-
282
- let directory = call.getString("directory")
283
- guard let fileUrl = implementation.getFileUrl(at: path, in: directory) else {
284
- handleError(call, "Invalid path")
285
- return
152
+ performSinglePathOperation(call) {
153
+ .getUri(url: $0)
286
154
  }
287
-
288
- call.resolve([
289
- "uri": fileUrl.absoluteString
290
- ])
291
-
292
155
  }
293
156
 
294
157
  /**
295
158
  * Rename a file or directory.
296
159
  */
297
160
  @objc func rename(_ call: CAPPluginCall) {
298
- guard let from = call.getString("from"), let to = call.getString("to") else {
299
- handleError(call, "Both to and from must be provided")
300
- return
301
- }
302
-
303
- let directory = call.getString("directory")
304
- let toDirectory = call.getString("toDirectory") ?? directory
305
-
306
- guard let fromUrl = implementation.getFileUrl(at: from, in: directory) else {
307
- handleError(call, "Invalid from path")
308
- return
309
- }
310
-
311
- guard let toUrl = implementation.getFileUrl(at: to, in: toDirectory) else {
312
- handleError(call, "Invalid to path")
313
- return
314
- }
315
- do {
316
- try implementation.rename(at: fromUrl, to: toUrl)
317
- call.resolve()
318
- } catch let error as NSError {
319
- handleError(call, error.localizedDescription, error)
161
+ performDualPathOperation(call) {
162
+ .rename(source: $0, destination: $1)
320
163
  }
321
164
  }
322
165
 
@@ -324,48 +167,18 @@ public class FilesystemPlugin: CAPPlugin, CAPBridgedPlugin {
324
167
  * Copy a file or directory.
325
168
  */
326
169
  @objc func copy(_ call: CAPPluginCall) {
327
- guard let from = call.getString("from"), let to = call.getString("to") else {
328
- handleError(call, "Both to and from must be provided")
329
- return
170
+ performDualPathOperation(call) {
171
+ .copy(source: $0, destination: $1)
330
172
  }
331
-
332
- let directory = call.getString("directory")
333
- let toDirectory = call.getString("toDirectory") ?? directory
334
-
335
- guard let fromUrl = implementation.getFileUrl(at: from, in: directory) else {
336
- handleError(call, "Invalid from path")
337
- return
338
- }
339
-
340
- guard let toUrl = implementation.getFileUrl(at: to, in: toDirectory) else {
341
- handleError(call, "Invalid to path")
342
- return
343
- }
344
- do {
345
- try implementation.copy(at: fromUrl, to: toUrl)
346
- call.resolve([
347
- "uri": toUrl.absoluteString
348
- ])
349
- } catch let error as NSError {
350
- handleError(call, error.localizedDescription, error)
351
- }
352
- }
353
-
354
- @objc override public func checkPermissions(_ call: CAPPluginCall) {
355
- call.resolve([
356
- "publicStorage": "granted"
357
- ])
358
- }
359
-
360
- @objc override public func requestPermissions(_ call: CAPPluginCall) {
361
- call.resolve([
362
- "publicStorage": "granted"
363
- ])
364
173
  }
365
174
 
175
+ /**
176
+ * [DEPRECATED] Download a file
177
+ */
178
+ @available(*, deprecated, message: "Use @capacitor/file-transfer plugin instead.")
366
179
  @objc func downloadFile(_ call: CAPPluginCall) {
367
180
  guard let url = call.getString("url") else { return call.reject("Must provide a URL") }
368
- let progressEmitter: Filesystem.ProgressEmitter = { bytes, contentLength in
181
+ let progressEmitter: LegacyFilesystemImplementation.ProgressEmitter = { bytes, contentLength in
369
182
  self.notifyListeners("progress", data: [
370
183
  "url": url,
371
184
  "bytes": bytes,
@@ -374,17 +187,43 @@ public class FilesystemPlugin: CAPPlugin, CAPBridgedPlugin {
374
187
  }
375
188
 
376
189
  do {
377
- try implementation.downloadFile(call: call, emitter: progressEmitter, config: bridge?.config)
190
+ try legacyImplementation.downloadFile(call: call, emitter: progressEmitter, config: bridge?.config)
378
191
  } catch let error {
379
192
  call.reject(error.localizedDescription)
380
193
  }
381
194
  }
195
+ }
382
196
 
383
- /**
384
- * Helper for handling errors
385
- */
386
- private func handleError(_ call: CAPPluginCall, _ message: String, _ error: Error? = nil) {
387
- call.reject(message, nil, error)
197
+ // MARK: - Operation Execution
198
+ private extension FilesystemPlugin {
199
+ func performSinglePathOperation(_ call: CAPPluginCall, operationBuilder: (URL) -> FilesystemOperation) {
200
+ executeOperation(call) { service in
201
+ FilesystemLocationResolver(service: service)
202
+ .resolveSinglePath(from: call)
203
+ .map { operationBuilder($0) }
204
+ }
388
205
  }
389
206
 
207
+ func performDualPathOperation(_ call: CAPPluginCall, operationBuilder: (URL, URL) -> FilesystemOperation) {
208
+ executeOperation(call) { service in
209
+ FilesystemLocationResolver(service: service)
210
+ .resolveDualPaths(from: call)
211
+ .map { operationBuilder($0.source, $0.destination) }
212
+ }
213
+ }
214
+
215
+ func executeOperation(_ call: CAPPluginCall, operationProvider: (FileService) -> Result<FilesystemOperation, FilesystemError>) {
216
+ switch getService() {
217
+ case .success(let service):
218
+ switch operationProvider(service) {
219
+ case .success(let operation):
220
+ let executor = FilesystemOperationExecutor(service: service)
221
+ executor.execute(operation, call)
222
+ case .failure(let error):
223
+ call.handleError(error)
224
+ }
225
+ case .failure(let error):
226
+ call.handleError(error)
227
+ }
228
+ }
390
229
  }
@@ -0,0 +1,60 @@
1
+ import Foundation
2
+ import IONFilesystemLib
3
+
4
+ extension IONFILEStringEncoding {
5
+ static func create(from text: String) -> Self {
6
+ switch text {
7
+ case Constants.StringEncodingValue.ascii: .ascii
8
+ case Constants.StringEncodingValue.utf16: .utf16
9
+ case Constants.StringEncodingValue.utf8: .utf8
10
+ default: .utf8
11
+ }
12
+ }
13
+ }
14
+
15
+ extension IONFILEDirectoryType {
16
+ static func create(from text: String) -> Self? {
17
+ switch text {
18
+ case Constants.DirectoryTypeValue.cache: .cache
19
+ case Constants.DirectoryTypeValue.data, Constants.DirectoryTypeValue.documents, Constants.DirectoryTypeValue.external, Constants.DirectoryTypeValue.externalCache, Constants.DirectoryTypeValue.externalStorage: .document
20
+ case Constants.DirectoryTypeValue.library: .library
21
+ case Constants.DirectoryTypeValue.libraryNoCloud: .notSyncedLibrary
22
+ case Constants.DirectoryTypeValue.temporary: .temporary
23
+ default: nil
24
+ }
25
+ }
26
+ }
27
+
28
+ extension IONFILEEncodingValueMapper {
29
+ var textValue: String {
30
+ switch self {
31
+ case .byteBuffer(let data): data.base64EncodedString()
32
+ case .string(_, let text): text
33
+ @unknown default: ""
34
+ }
35
+ }
36
+ }
37
+
38
+ extension IONFILEItemAttributeModel {
39
+ typealias JSResult = [String: Any]
40
+ func toJSResult(with url: URL) -> JSResult {
41
+ [
42
+ Constants.ItemAttributeJSONKey.name: url.lastPathComponent,
43
+ Constants.ItemAttributeJSONKey.type: type.description,
44
+ Constants.ItemAttributeJSONKey.size: size,
45
+ Constants.ItemAttributeJSONKey.ctime: UInt64(creationDateTimestamp.rounded()),
46
+ Constants.ItemAttributeJSONKey.mtime: UInt64(modificationDateTimestamp.rounded()),
47
+ Constants.ItemAttributeJSONKey.uri: url.absoluteString
48
+ ]
49
+ }
50
+ }
51
+
52
+ extension IONFILEItemType {
53
+ var description: String {
54
+ switch self {
55
+ case .directory: Constants.FileItemTypeValue.directory
56
+ case .file: Constants.FileItemTypeValue.file
57
+ @unknown default: Constants.FileItemTypeValue.fallback
58
+ }
59
+ }
60
+ }