@amazon-devices/expo-file-system 2.0.1758683737

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 (111) hide show
  1. package/.eslintrc.js +2 -0
  2. package/CHANGELOG.md +317 -0
  3. package/CMakeLists.txt +52 -0
  4. package/LICENSE +7 -0
  5. package/README.kepler.md +260 -0
  6. package/README.md +46 -0
  7. package/THIRD_PARTY_NOTICES.txt +24 -0
  8. package/android/build.gradle +118 -0
  9. package/android/src/main/AndroidManifest.xml +17 -0
  10. package/android/src/main/java/expo/modules/filesystem/AppDirectoriesModule.kt +26 -0
  11. package/android/src/main/java/expo/modules/filesystem/CountingRequestBody.kt +52 -0
  12. package/android/src/main/java/expo/modules/filesystem/FilePermissionModule.kt +48 -0
  13. package/android/src/main/java/expo/modules/filesystem/FileSystemExceptions.kt +48 -0
  14. package/android/src/main/java/expo/modules/filesystem/FileSystemFileProvider.kt +5 -0
  15. package/android/src/main/java/expo/modules/filesystem/FileSystemModule.kt +1110 -0
  16. package/android/src/main/java/expo/modules/filesystem/FileSystemPackage.kt +10 -0
  17. package/android/src/main/java/expo/modules/filesystem/FileSystemRecords.kt +92 -0
  18. package/android/src/main/res/xml/file_system_provider_paths.xml +5 -0
  19. package/app.plugin.js +1 -0
  20. package/build/ExponentFileSystem.d.ts +4 -0
  21. package/build/ExponentFileSystem.d.ts.map +1 -0
  22. package/build/ExponentFileSystem.js +5 -0
  23. package/build/ExponentFileSystem.js.map +1 -0
  24. package/build/ExponentFileSystem.kepler.d.ts +4 -0
  25. package/build/ExponentFileSystem.kepler.d.ts.map +1 -0
  26. package/build/ExponentFileSystem.kepler.js +112 -0
  27. package/build/ExponentFileSystem.kepler.js.map +1 -0
  28. package/build/ExponentFileSystem.web.d.ts +3 -0
  29. package/build/ExponentFileSystem.web.d.ts.map +1 -0
  30. package/build/ExponentFileSystem.web.js +3 -0
  31. package/build/ExponentFileSystem.web.js.map +1 -0
  32. package/build/ExponentFileSystemShim.d.ts +4 -0
  33. package/build/ExponentFileSystemShim.d.ts.map +1 -0
  34. package/build/ExponentFileSystemShim.js +21 -0
  35. package/build/ExponentFileSystemShim.js.map +1 -0
  36. package/build/FileSystem.d.ts +361 -0
  37. package/build/FileSystem.d.ts.map +1 -0
  38. package/build/FileSystem.js +616 -0
  39. package/build/FileSystem.js.map +1 -0
  40. package/build/FileSystem.types.d.ts +304 -0
  41. package/build/FileSystem.types.d.ts.map +1 -0
  42. package/build/FileSystem.types.js +47 -0
  43. package/build/FileSystem.types.js.map +1 -0
  44. package/build/index.d.ts +3 -0
  45. package/build/index.d.ts.map +1 -0
  46. package/build/index.js +3 -0
  47. package/build/index.js.map +1 -0
  48. package/build/types.d.ts +35 -0
  49. package/build/types.d.ts.map +1 -0
  50. package/build/types.js +2 -0
  51. package/build/types.js.map +1 -0
  52. package/expo-module.config.json +11 -0
  53. package/ios/EXFilePermissionModule.h +16 -0
  54. package/ios/EXFilePermissionModule.m +68 -0
  55. package/ios/EXFileSystem.h +25 -0
  56. package/ios/EXFileSystem.m +1084 -0
  57. package/ios/EXFileSystemAssetLibraryHandler.h +6 -0
  58. package/ios/EXFileSystemAssetLibraryHandler.m +142 -0
  59. package/ios/EXFileSystemHandler.h +17 -0
  60. package/ios/EXFileSystemLocalFileHandler.h +7 -0
  61. package/ios/EXFileSystemLocalFileHandler.m +79 -0
  62. package/ios/EXSessionTasks/EXSessionCancelableUploadTaskDelegate.h +16 -0
  63. package/ios/EXSessionTasks/EXSessionCancelableUploadTaskDelegate.m +55 -0
  64. package/ios/EXSessionTasks/EXSessionDownloadTaskDelegate.h +13 -0
  65. package/ios/EXSessionTasks/EXSessionDownloadTaskDelegate.m +64 -0
  66. package/ios/EXSessionTasks/EXSessionHandler.h +19 -0
  67. package/ios/EXSessionTasks/EXSessionHandler.m +49 -0
  68. package/ios/EXSessionTasks/EXSessionResumableDownloadTaskDelegate.h +18 -0
  69. package/ios/EXSessionTasks/EXSessionResumableDownloadTaskDelegate.m +66 -0
  70. package/ios/EXSessionTasks/EXSessionTaskDelegate.h +32 -0
  71. package/ios/EXSessionTasks/EXSessionTaskDelegate.m +58 -0
  72. package/ios/EXSessionTasks/EXSessionTaskDispatcher.h +19 -0
  73. package/ios/EXSessionTasks/EXSessionTaskDispatcher.m +96 -0
  74. package/ios/EXSessionTasks/EXSessionUploadTaskDelegate.h +8 -0
  75. package/ios/EXSessionTasks/EXSessionUploadTaskDelegate.m +52 -0
  76. package/ios/EXSessionTasks/EXTaskHandlersManager.h +21 -0
  77. package/ios/EXSessionTasks/EXTaskHandlersManager.m +56 -0
  78. package/ios/Encoding.swift +88 -0
  79. package/ios/ExpoFileSystem.h +1 -0
  80. package/ios/ExpoFileSystem.podspec +32 -0
  81. package/ios/FileSystemBackgroundSessionHandler.swift +25 -0
  82. package/ios/FileSystemExceptions.swift +69 -0
  83. package/ios/FileSystemHelpers.swift +66 -0
  84. package/ios/FileSystemModule.swift +263 -0
  85. package/ios/FileSystemRecords.swift +74 -0
  86. package/ios/NSData+EXFileSystem.h +9 -0
  87. package/ios/NSData+EXFileSystem.m +19 -0
  88. package/ios/NetworkingHelpers.swift +84 -0
  89. package/ios/Tests/EXFileSystemSpec.swift +55 -0
  90. package/jest.config.js +3 -0
  91. package/kepler/ExponentFileSystem.cpp +498 -0
  92. package/kepler/ExponentFileSystem.h +60 -0
  93. package/kepler/KeplerInit.cpp +28 -0
  94. package/kepler/NetworkingRequestHandler.cpp +47 -0
  95. package/kepler/test/FileSystem.cpp +425 -0
  96. package/package.json +1 -0
  97. package/plugin/build/withFileSystem.d.ts +3 -0
  98. package/plugin/build/withFileSystem.js +12 -0
  99. package/plugin/src/withFileSystem.ts +13 -0
  100. package/plugin/tsconfig.json +9 -0
  101. package/plugin/tsconfig.tsbuildinfo +1 -0
  102. package/react-native.config.js +21 -0
  103. package/src/ExponentFileSystem.kepler.ts +167 -0
  104. package/src/ExponentFileSystem.ts +7 -0
  105. package/src/ExponentFileSystem.web.ts +2 -0
  106. package/src/ExponentFileSystemShim.ts +23 -0
  107. package/src/FileSystem.ts +756 -0
  108. package/src/FileSystem.types.ts +339 -0
  109. package/src/index.ts +2 -0
  110. package/src/types.ts +34 -0
  111. package/tsconfig.json +9 -0
@@ -0,0 +1,1084 @@
1
+ // Copyright 2016-present 650 Industries. All rights reserved.
2
+
3
+ #import <ExpoModulesCore/EXModuleRegistry.h>
4
+
5
+ #import <ExpoFileSystem/EXFileSystem.h>
6
+
7
+ #import <CommonCrypto/CommonDigest.h>
8
+ #import <MobileCoreServices/MobileCoreServices.h>
9
+
10
+ #import <ExpoFileSystem/EXFileSystemLocalFileHandler.h>
11
+ #import <ExpoFileSystem/EXFileSystemAssetLibraryHandler.h>
12
+
13
+ #import <ExpoModulesCore/EXFileSystemInterface.h>
14
+ #import <ExpoModulesCore/EXFilePermissionModuleInterface.h>
15
+
16
+ #import <ExpoModulesCore/EXEventEmitterService.h>
17
+
18
+ #import <ExpoFileSystem/EXTaskHandlersManager.h>
19
+ #import <ExpoFileSystem/EXSessionTaskDispatcher.h>
20
+ #import <ExpoFileSystem/EXSessionDownloadTaskDelegate.h>
21
+ #import <ExpoFileSystem/EXSessionResumableDownloadTaskDelegate.h>
22
+ #import <ExpoFileSystem/EXSessionUploadTaskDelegate.h>
23
+ #import <ExpoFileSystem/EXSessionCancelableUploadTaskDelegate.h>
24
+
25
+ NSString * const EXDownloadProgressEventName = @"expo-file-system.downloadProgress";
26
+ NSString * const EXUploadProgressEventName = @"expo-file-system.uploadProgress";
27
+
28
+ typedef NS_ENUM(NSInteger, EXFileSystemSessionType) {
29
+ EXFileSystemBackgroundSession = 0,
30
+ EXFileSystemForegroundSession = 1,
31
+ };
32
+
33
+ typedef NS_ENUM(NSInteger, EXFileSystemUploadType) {
34
+ EXFileSystemInvalidType = -1,
35
+ EXFileSystemBinaryContent = 0,
36
+ EXFileSystemMultipart = 1,
37
+ };
38
+
39
+ @interface EXFileSystem ()
40
+
41
+ @property (nonatomic, strong) NSURLSession *backgroundSession;
42
+ @property (nonatomic, strong) NSURLSession *foregroundSession;
43
+ @property (nonatomic, strong) EXSessionTaskDispatcher *sessionTaskDispatcher;
44
+ @property (nonatomic, strong) EXTaskHandlersManager *taskHandlersManager;
45
+ @property (nonatomic, weak) EXModuleRegistry *moduleRegistry;
46
+ @property (nonatomic, weak) id<EXEventEmitterService> eventEmitter;
47
+ @property (nonatomic, strong) NSString *documentDirectory;
48
+ @property (nonatomic, strong) NSString *cachesDirectory;
49
+ @property (nonatomic, strong) NSString *bundleDirectory;
50
+
51
+ @end
52
+
53
+ @implementation EXFileSystem
54
+
55
+ EX_REGISTER_MODULE();
56
+
57
+ + (const NSString *)exportedModuleName
58
+ {
59
+ return @"ExponentFileSystem";
60
+ }
61
+
62
+ + (const NSArray<Protocol *> *)exportedInterfaces
63
+ {
64
+ return @[@protocol(EXFileSystemInterface)];
65
+ }
66
+
67
+ - (instancetype)initWithDocumentDirectory:(NSString *)documentDirectory cachesDirectory:(NSString *)cachesDirectory bundleDirectory:(NSString *)bundleDirectory
68
+ {
69
+ if (self = [super init]) {
70
+ _documentDirectory = documentDirectory;
71
+ _cachesDirectory = cachesDirectory;
72
+ _bundleDirectory = bundleDirectory;
73
+
74
+ _taskHandlersManager = [EXTaskHandlersManager new];
75
+
76
+ [EXFileSystem ensureDirExistsWithPath:_documentDirectory];
77
+ [EXFileSystem ensureDirExistsWithPath:_cachesDirectory];
78
+ }
79
+ return self;
80
+ }
81
+
82
+ - (instancetype)init
83
+ {
84
+ NSArray<NSString *> *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
85
+ NSString *documentDirectory = [documentPaths objectAtIndex:0];
86
+
87
+ NSArray<NSString *> *cachesPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
88
+ NSString *cacheDirectory = [cachesPaths objectAtIndex:0];
89
+
90
+ return [self initWithDocumentDirectory:documentDirectory
91
+ cachesDirectory:cacheDirectory
92
+ bundleDirectory:[NSBundle mainBundle].bundlePath];
93
+ }
94
+
95
+ - (void)setModuleRegistry:(EXModuleRegistry *)moduleRegistry
96
+ {
97
+ _moduleRegistry = moduleRegistry;
98
+ _eventEmitter = [_moduleRegistry getModuleImplementingProtocol:@protocol(EXEventEmitterService)];
99
+
100
+ _sessionTaskDispatcher = [[EXSessionTaskDispatcher alloc] initWithSessionHandler:[moduleRegistry getSingletonModuleForName:@"SessionHandler"]];
101
+ _backgroundSession = [self _createSession:EXFileSystemBackgroundSession delegate:_sessionTaskDispatcher];
102
+ _foregroundSession = [self _createSession:EXFileSystemForegroundSession delegate:_sessionTaskDispatcher];
103
+ }
104
+
105
+ - (NSDictionary *)constantsToExport
106
+ {
107
+ return @{
108
+ @"documentDirectory": _documentDirectory ? [NSURL fileURLWithPath:_documentDirectory].absoluteString : [NSNull null],
109
+ @"cacheDirectory": _cachesDirectory ? [NSURL fileURLWithPath:_cachesDirectory].absoluteString : [NSNull null],
110
+ @"bundleDirectory": _bundleDirectory ? [NSURL fileURLWithPath:_bundleDirectory].absoluteString : [NSNull null]
111
+ };
112
+ }
113
+
114
+ - (NSArray<NSString *> *)supportedEvents
115
+ {
116
+ return @[EXDownloadProgressEventName, EXUploadProgressEventName];
117
+ }
118
+
119
+ - (void)startObserving {
120
+
121
+ }
122
+
123
+
124
+ - (void)stopObserving {
125
+
126
+ }
127
+
128
+ - (void)dealloc
129
+ {
130
+ [_sessionTaskDispatcher deactivate];
131
+ [_backgroundSession invalidateAndCancel];
132
+ [_foregroundSession invalidateAndCancel];
133
+ }
134
+
135
+ - (NSDictionary *)encodingMap
136
+ {
137
+ /*
138
+ TODO:Bacon: match node.js fs
139
+ https://github.com/nodejs/node/blob/master/lib/buffer.js
140
+ ascii
141
+ base64
142
+ binary
143
+ hex
144
+ ucs2/ucs-2
145
+ utf16le/utf-16le
146
+ utf8/utf-8
147
+ latin1 (ISO8859-1, only in node 6.4.0+)
148
+ */
149
+ return @{
150
+ @"ascii": @(NSASCIIStringEncoding),
151
+ @"nextstep": @(NSNEXTSTEPStringEncoding),
152
+ @"japaneseeuc": @(NSJapaneseEUCStringEncoding),
153
+ @"utf8": @(NSUTF8StringEncoding),
154
+ @"isolatin1": @(NSISOLatin1StringEncoding),
155
+ @"symbol": @(NSSymbolStringEncoding),
156
+ @"nonlossyascii": @(NSNonLossyASCIIStringEncoding),
157
+ @"shiftjis": @(NSShiftJISStringEncoding),
158
+ @"isolatin2": @(NSISOLatin2StringEncoding),
159
+ @"unicode": @(NSUnicodeStringEncoding),
160
+ @"windowscp1251": @(NSWindowsCP1251StringEncoding),
161
+ @"windowscp1252": @(NSWindowsCP1252StringEncoding),
162
+ @"windowscp1253": @(NSWindowsCP1253StringEncoding),
163
+ @"windowscp1254": @(NSWindowsCP1254StringEncoding),
164
+ @"windowscp1250": @(NSWindowsCP1250StringEncoding),
165
+ @"iso2022jp": @(NSISO2022JPStringEncoding),
166
+ @"macosroman": @(NSMacOSRomanStringEncoding),
167
+ @"utf16": @(NSUTF16StringEncoding),
168
+ @"utf16bigendian": @(NSUTF16BigEndianStringEncoding),
169
+ @"utf16littleendian": @(NSUTF16LittleEndianStringEncoding),
170
+ @"utf32": @(NSUTF32StringEncoding),
171
+ @"utf32bigendian": @(NSUTF32BigEndianStringEncoding),
172
+ @"utf32littleendian": @(NSUTF32LittleEndianStringEncoding),
173
+ };
174
+ }
175
+
176
+ EX_EXPORT_METHOD_AS(getInfoAsync,
177
+ getInfoAsyncWithURI:(NSString *)uriString
178
+ withOptions:(NSDictionary *)options
179
+ resolver:(EXPromiseResolveBlock)resolve
180
+ rejecter:(EXPromiseRejectBlock)reject)
181
+ {
182
+ NSURL *uri = [self percentEncodedURLFromURIString:uriString];
183
+ // no scheme provided in uri, handle as a local path and add 'file://' scheme
184
+ if (!uri.scheme) {
185
+ uri = [NSURL fileURLWithPath:uriString isDirectory:false];
186
+ }
187
+ if (!([self permissionsForURI:uri] & EXFileSystemPermissionRead)) {
188
+ reject(@"ERR_FILESYSTEM_NO_PERMISSIONS",
189
+ [NSString stringWithFormat:@"File '%@' isn't readable.", uri],
190
+ nil);
191
+ return;
192
+ }
193
+
194
+ if ([uri.scheme isEqualToString:@"file"]) {
195
+ [EXFileSystemLocalFileHandler getInfoForFile:uri withOptions:options resolver:resolve rejecter:reject];
196
+ } else if ([uri.scheme isEqualToString:@"assets-library"] || [uri.scheme isEqualToString:@"ph"]) {
197
+ [EXFileSystemAssetLibraryHandler getInfoForFile:uri withOptions:options resolver:resolve rejecter:reject];
198
+ } else {
199
+ reject(@"ERR_FILESYSTEM_INVALID_URI",
200
+ [NSString stringWithFormat:@"Unsupported URI scheme for '%@'", uri],
201
+ nil);
202
+ }
203
+ }
204
+
205
+ EX_EXPORT_METHOD_AS(readAsStringAsync,
206
+ readAsStringAsyncWithURI:(NSString *)uriString
207
+ withOptions:(NSDictionary *)options
208
+ resolver:(EXPromiseResolveBlock)resolve
209
+ rejecter:(EXPromiseRejectBlock)reject)
210
+ {
211
+ NSURL *uri = [self percentEncodedURLFromURIString:uriString];
212
+ // no scheme provided in uri, handle as a local path and add 'file://' scheme
213
+ if (!uri.scheme) {
214
+ uri = [NSURL fileURLWithPath:uriString isDirectory:false];
215
+ }
216
+ if (!([self permissionsForURI:uri] & EXFileSystemPermissionRead)) {
217
+ reject(@"ERR_FILESYSTEM_NO_PERMISSIONS",
218
+ [NSString stringWithFormat:@"File '%@' isn't readable.", uri],
219
+ nil);
220
+ return;
221
+ }
222
+
223
+ if ([uri.scheme isEqualToString:@"file"]) {
224
+ NSString *encodingType = @"utf8";
225
+ if (options[@"encoding"] && [options[@"encoding"] isKindOfClass:[NSString class]]) {
226
+ encodingType = [options[@"encoding"] lowercaseString];
227
+ }
228
+ if ([encodingType isEqualToString:@"base64"]) {
229
+ NSFileHandle *file = [NSFileHandle fileHandleForReadingAtPath:uri.path];
230
+ if (file == nil) {
231
+ reject(@"ERR_FILESYSTEM_CANNOT_READ_FILE",
232
+ [NSString stringWithFormat:@"File '%@' could not be read.", uri.path],
233
+ nil);
234
+ return;
235
+ }
236
+ // position and length are used as a cursor/paging system.
237
+ if ([options[@"position"] isKindOfClass:[NSNumber class]]) {
238
+ [file seekToFileOffset:[options[@"position"] intValue]];
239
+ }
240
+
241
+ NSData *data;
242
+ if ([options[@"length"] isKindOfClass:[NSNumber class]]) {
243
+ data = [file readDataOfLength:[options[@"length"] intValue]];
244
+ } else {
245
+ data = [file readDataToEndOfFile];
246
+ }
247
+ resolve([data base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]);
248
+ } else {
249
+ NSUInteger encoding = NSUTF8StringEncoding;
250
+ id possibleEncoding = [[self encodingMap] valueForKey:encodingType];
251
+ if (possibleEncoding != nil) {
252
+ encoding = [possibleEncoding integerValue];
253
+ }
254
+ NSError *error;
255
+ NSString *string = [NSString stringWithContentsOfFile:uri.path encoding:encoding error:&error];
256
+ if (string) {
257
+ resolve(string);
258
+ } else {
259
+ reject(@"ERR_FILESYSTEM_CANNOT_READ_FILE",
260
+ [NSString stringWithFormat:@"File '%@' could not be read.", uri],
261
+ error);
262
+ }
263
+ }
264
+ } else {
265
+ reject(@"ERR_FILESYSTEM_INVALID_URI",
266
+ [NSString stringWithFormat:@"Unsupported URI scheme for '%@'", uri],
267
+ nil);
268
+ }
269
+ }
270
+
271
+ EX_EXPORT_METHOD_AS(writeAsStringAsync,
272
+ writeAsStringAsyncWithURI:(NSString *)uriString
273
+ withString:(NSString *)string
274
+ withOptions:(NSDictionary *)options
275
+ resolver:(EXPromiseResolveBlock)resolve
276
+ rejecter:(EXPromiseRejectBlock)reject)
277
+ {
278
+ NSURL *uri = [self percentEncodedURLFromURIString:uriString];
279
+ if (!([self permissionsForURI:uri] & EXFileSystemPermissionWrite)) {
280
+ reject(@"ERR_FILESYSTEM_NO_PERMISSIONS",
281
+ [NSString stringWithFormat:@"File '%@' isn't writable.", uri],
282
+ nil);
283
+ return;
284
+ }
285
+
286
+ if ([uri.scheme isEqualToString:@"file"]) {
287
+ NSString *encodingType = @"utf8";
288
+ if ([options[@"encoding"] isKindOfClass:[NSString class]]) {
289
+ encodingType = [options[@"encoding"] lowercaseString];
290
+ }
291
+ if ([encodingType isEqualToString:@"base64"]) {
292
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
293
+ NSData *imageData = [[NSData alloc] initWithBase64EncodedString:string options:NSDataBase64DecodingIgnoreUnknownCharacters];
294
+ if (imageData) {
295
+ // TODO:Bacon: Should we surface `attributes`?
296
+ if ([[NSFileManager defaultManager] createFileAtPath:uri.path contents:imageData attributes:nil]) {
297
+ resolve([NSNull null]);
298
+ } else {
299
+ return reject(@"ERR_FILESYSTEM_UNKNOWN_FILE",
300
+ [NSString stringWithFormat:@"No such file or directory '%@'", uri.path],
301
+ nil);
302
+ }
303
+ } else {
304
+ reject(@"ERR_FILESYSTEM_INVALID_FORMAT",
305
+ @"Failed to parse base64 string.",
306
+ nil);
307
+ }
308
+ });
309
+ } else {
310
+ NSUInteger encoding = NSUTF8StringEncoding;
311
+ id possibleEncoding = [[self encodingMap] valueForKey:encodingType];
312
+ if (possibleEncoding != nil) {
313
+ encoding = [possibleEncoding integerValue];
314
+ }
315
+
316
+ NSError *error;
317
+ if ([string writeToFile:uri.path atomically:YES encoding:encoding error:&error]) {
318
+ resolve([NSNull null]);
319
+ } else {
320
+ reject(@"ERR_FILESYSTEM_CANNOT_WRITE_TO_FILE",
321
+ [NSString stringWithFormat:@"File '%@' could not be written.", uri],
322
+ error);
323
+ }
324
+ }
325
+ } else {
326
+ reject(@"ERR_FILESYSTEM_INVALID_URI",
327
+ [NSString stringWithFormat:@"Unsupported URI scheme for '%@'", uri],
328
+ nil);
329
+ }
330
+ }
331
+
332
+ EX_EXPORT_METHOD_AS(deleteAsync,
333
+ deleteAsyncWithURI:(NSString *)uriString
334
+ withOptions:(NSDictionary *)options
335
+ resolver:(EXPromiseResolveBlock)resolve
336
+ rejecter:(EXPromiseRejectBlock)reject)
337
+ {
338
+ NSURL *uri = [self percentEncodedURLFromURIString:uriString];
339
+ if (!([self permissionsForURI:[uri URLByAppendingPathComponent:@".."]] & EXFileSystemPermissionWrite)) {
340
+ reject(@"ERR_FILESYSTEM_NO_PERMISSIONS",
341
+ [NSString stringWithFormat:@"Location '%@' isn't deletable.", uri],
342
+ nil);
343
+ return;
344
+ }
345
+
346
+ if ([uri.scheme isEqualToString:@"file"]) {
347
+ NSString *path = uri.path;
348
+ if ([self _checkIfFileExists:path]) {
349
+ NSError *error;
350
+ if ([[NSFileManager defaultManager] removeItemAtPath:path error:&error]) {
351
+ resolve([NSNull null]);
352
+ } else {
353
+ reject(@"ERR_FILESYSTEM_CANNOT_DELETE_FILE",
354
+ [NSString stringWithFormat:@"File '%@' could not be deleted.", uri],
355
+ error);
356
+ }
357
+ } else {
358
+ if (options[@"idempotent"]) {
359
+ resolve([NSNull null]);
360
+ } else {
361
+ reject(@"ERR_FILESYSTEM_CANNOT_FIND_FILE",
362
+ [NSString stringWithFormat:@"File '%@' could not be deleted because it could not be found.", uri],
363
+ nil);
364
+ }
365
+ }
366
+ } else {
367
+ reject(@"ERR_FILESYSTEM_INVALID_URI",
368
+ [NSString stringWithFormat:@"Unsupported URI scheme for '%@'", uri],
369
+ nil);
370
+ }
371
+ }
372
+
373
+ EX_EXPORT_METHOD_AS(moveAsync,
374
+ moveAsyncWithOptions:(NSDictionary *)options
375
+ resolver:(EXPromiseResolveBlock)resolve
376
+ rejecter:(EXPromiseRejectBlock)reject)
377
+ {
378
+ NSURL *from = [NSURL URLWithString:options[@"from"]];
379
+ if (!from) {
380
+ reject(@"ERR_FILESYSTEM_MISSING_PARAMETER", @"Need a `from` location.", nil);
381
+ return;
382
+ }
383
+ if (!([self permissionsForURI:[from URLByAppendingPathComponent:@".."]] & EXFileSystemPermissionWrite)) {
384
+ reject(@"ERR_FILESYSTEM_NO_PERMISSIONS",
385
+ [NSString stringWithFormat:@"Location '%@' isn't movable.", from],
386
+ nil);
387
+ return;
388
+ }
389
+ NSURL *to = [NSURL URLWithString:options[@"to"]];
390
+ if (!to) {
391
+ reject(@"ERR_FILESYSTEM_MISSING_PARAMETER", @"Need a `to` location.", nil);
392
+ return;
393
+ }
394
+ if (!([self permissionsForURI:to] & EXFileSystemPermissionWrite)) {
395
+ reject(@"ERR_FILESYSTEM_NO_PERMISSIONS",
396
+ [NSString stringWithFormat:@"File '%@' isn't writable.", to],
397
+ nil);
398
+ return;
399
+ }
400
+
401
+ // NOTE: The destination-delete and the move should happen atomically, but we hope for the best for now
402
+ if ([from.scheme isEqualToString:@"file"]) {
403
+ NSString *fromPath = [from.path stringByStandardizingPath];
404
+ NSString *toPath = [to.path stringByStandardizingPath];
405
+ NSError *error;
406
+ if ([self _checkIfFileExists:toPath]) {
407
+ if (![[NSFileManager defaultManager] removeItemAtPath:toPath error:&error]) {
408
+ reject(@"ERR_FILESYSTEM_CANNOT_MOVE_FILE",
409
+ [NSString stringWithFormat:@"File '%@' could not be moved to '%@' because a file already exists at "
410
+ "the destination and could not be deleted.", from, to],
411
+ error);
412
+ return;
413
+ }
414
+ }
415
+ if ([[NSFileManager defaultManager] moveItemAtPath:fromPath toPath:toPath error:&error]) {
416
+ resolve([NSNull null]);
417
+ } else {
418
+ reject(@"ERR_FILESYSTEM_CANNOT_MOVE_FILE",
419
+ [NSString stringWithFormat:@"File '%@' could not be moved to '%@'.", from, to],
420
+ error);
421
+ }
422
+ } else {
423
+ reject(@"ERR_FILESYSTEM_INVALID_URI",
424
+ [NSString stringWithFormat:@"Unsupported URI scheme for '%@'", from],
425
+ nil);
426
+ }
427
+ }
428
+
429
+ EX_EXPORT_METHOD_AS(copyAsync,
430
+ copyAsyncWithOptions:(NSDictionary *)options
431
+ resolver:(EXPromiseResolveBlock)resolve
432
+ rejecter:(EXPromiseRejectBlock)reject)
433
+ {
434
+ NSURL *from = [NSURL URLWithString:options[@"from"]];
435
+ if (!from) {
436
+ reject(@"ERR_FILESYSTEM_MISSING_PARAMETER", @"Need a `from` location.", nil);
437
+ return;
438
+ }
439
+ if (!([self permissionsForURI:from] & EXFileSystemPermissionRead)) {
440
+ reject(@"ERR_FILESYSTEM_NO_PERMISSIONS",
441
+ [NSString stringWithFormat:@"File '%@' isn't readable.", from],
442
+ nil);
443
+ return;
444
+ }
445
+ NSURL *to = [NSURL URLWithString:options[@"to"]];
446
+ if (!to) {
447
+ reject(@"ERR_FILESYSTEM_MISSING_PARAMETER", @"Need a `to` location.", nil);
448
+ return;
449
+ }
450
+ if (!([self permissionsForURI:to] & EXFileSystemPermissionWrite)) {
451
+ reject(@"ERR_FILESYSTEM_NO_PERMISSIONS",
452
+ [NSString stringWithFormat:@"File '%@' isn't writable.", to],
453
+ nil);
454
+ return;
455
+ }
456
+
457
+ if ([from.scheme isEqualToString:@"file"]) {
458
+ [EXFileSystemLocalFileHandler copyFrom:from to:to resolver:resolve rejecter:reject];
459
+ } else if ([from.scheme isEqualToString:@"assets-library"] || [from.scheme isEqualToString:@"ph"]) {
460
+ [EXFileSystemAssetLibraryHandler copyFrom:from to:to resolver:resolve rejecter:reject];
461
+ } else {
462
+ reject(@"ERR_FILESYSTEM_INVALID_URI",
463
+ [NSString stringWithFormat:@"Unsupported URI scheme for '%@'", from],
464
+ nil);
465
+ }
466
+ }
467
+
468
+ EX_EXPORT_METHOD_AS(makeDirectoryAsync,
469
+ makeDirectoryAsyncWithURI:(NSString *)uriString
470
+ withOptions:(NSDictionary *)options
471
+ resolver:(EXPromiseResolveBlock)resolve
472
+ rejecter:(EXPromiseRejectBlock)reject)
473
+ {
474
+
475
+ NSURL *uri = [self percentEncodedURLFromURIString:uriString];
476
+ if (!([self permissionsForURI:uri] & EXFileSystemPermissionWrite)) {
477
+ reject(@"ERR_FILESYSTEM_NO_PERMISSIONS",
478
+ [NSString stringWithFormat:@"Directory '%@' could not be created because the location isn't writable.", uri],
479
+ nil);
480
+ return;
481
+ }
482
+
483
+ if ([uri.scheme isEqualToString:@"file"]) {
484
+ NSError *error;
485
+ if ([[NSFileManager defaultManager] createDirectoryAtPath:uri.path
486
+ withIntermediateDirectories:[options[@"intermediates"] boolValue]
487
+ attributes:nil
488
+ error:&error]) {
489
+ resolve([NSNull null]);
490
+ } else {
491
+ reject(@"ERR_FILESYSTEM_CANNOT_CREATE_DIRECTORY",
492
+ [NSString stringWithFormat:@"Directory '%@' could not be created.", uri],
493
+ error);
494
+ }
495
+ } else {
496
+ reject(@"ERR_FILESYSTEM_INVALID_URI",
497
+ [NSString stringWithFormat:@"Unsupported URI scheme for '%@'", uri],
498
+ nil);
499
+ }
500
+ }
501
+
502
+ EX_EXPORT_METHOD_AS(readDirectoryAsync,
503
+ readDirectoryAsyncWithURI:(NSString *)uriString
504
+ resolver:(EXPromiseResolveBlock)resolve
505
+ rejecter:(EXPromiseRejectBlock)reject)
506
+ {
507
+ NSURL *uri = [self percentEncodedURLFromURIString:uriString];
508
+ if (!([self permissionsForURI:uri] & EXFileSystemPermissionRead)) {
509
+ reject(@"ERR_FILESYSTEM_NO_PERMISSIONS",
510
+ [NSString stringWithFormat:@"Location '%@' isn't readable.", uri],
511
+ nil);
512
+ return;
513
+ }
514
+
515
+ if ([uri.scheme isEqualToString:@"file"]) {
516
+ NSError *error;
517
+ NSArray<NSString *> *children = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:uri.path error:&error];
518
+ if (children) {
519
+ resolve(children);
520
+ } else {
521
+ reject(@"ERR_FILESYSTEM_CANNOT_READ_DIRECTORY",
522
+ [NSString stringWithFormat:@"Directory '%@' could not be read.", uri],
523
+ error);
524
+ }
525
+ } else {
526
+ reject(@"ERR_FILESYSTEM_INVALID_URI",
527
+ [NSString stringWithFormat:@"Unsupported URI scheme for '%@'", uri],
528
+ nil);
529
+ }
530
+ }
531
+
532
+ EX_EXPORT_METHOD_AS(downloadAsync,
533
+ downloadAsyncWithUrl:(NSString *)urlString
534
+ localURI:(NSString *)localUriString
535
+ options:(NSDictionary *)options
536
+ resolver:(EXPromiseResolveBlock)resolve
537
+ rejecter:(EXPromiseRejectBlock)reject)
538
+ {
539
+ NSURL *url = [NSURL URLWithString:urlString];
540
+ NSURL *localUri = [self percentEncodedURLFromURIString:localUriString];
541
+ if (!([self checkIfFileDirExists:localUri.path])) {
542
+ reject(@"ERR_FILESYSTEM_WRONG_DESTINATION",
543
+ [NSString stringWithFormat:@"Directory for '%@' doesn't exist. Please make sure directory '%@' exists before calling downloadAsync.", localUriString, [localUri.path stringByDeletingLastPathComponent]],
544
+ nil);
545
+ return;
546
+ }
547
+ if (!([self permissionsForURI:localUri] & EXFileSystemPermissionWrite)) {
548
+ reject(@"ERR_FILESYSTEM_NO_PERMISSIONS",
549
+ [NSString stringWithFormat:@"File '%@' isn't writable.", localUri],
550
+ nil);
551
+ return;
552
+ }
553
+ if (![self _checkHeadersDictionary:options[@"headers"]]) {
554
+ reject(@"ERR_FILESYSTEM_INVALID_HEADERS",
555
+ @"Invalid headers dictionary. Keys and values should be strings.",
556
+ nil);
557
+ return;
558
+ }
559
+
560
+ NSURLSession *session = [self _sessionForType:[options[@"sessionType"] intValue]];
561
+ if (!session) {
562
+ reject(@"ERR_FILESYSTEM_INVALID_SESSION_TYPE",
563
+ [NSString stringWithFormat:@"Invalid session type: '%@'", options[@"sessionType"]],
564
+ nil);
565
+ return;
566
+ }
567
+
568
+ NSURLRequest *request = [self _createRequest:url headers:options[@"headers"]];
569
+ NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request];
570
+ EXSessionTaskDelegate *taskDelegate = [[EXSessionDownloadTaskDelegate alloc] initWithResolve:resolve
571
+ reject:reject
572
+ localUrl:localUri
573
+ shouldCalculateMd5:[options[@"md5"] boolValue]];
574
+ [_sessionTaskDispatcher registerTaskDelegate:taskDelegate forTask:task];
575
+ [task resume];
576
+ }
577
+
578
+ EX_EXPORT_METHOD_AS(uploadAsync,
579
+ uploadAsync:(NSString *)urlString
580
+ localURI:(NSString *)fileUriString
581
+ options:(NSDictionary *)options
582
+ resolver:(EXPromiseResolveBlock)resolve
583
+ rejecter:(EXPromiseRejectBlock)reject)
584
+ {
585
+ NSURLSessionUploadTask *task = [self createUploadTask:urlString localURI:fileUriString options:options rejecter:reject];
586
+ if (!task) {
587
+ return;
588
+ }
589
+
590
+ EXSessionTaskDelegate *taskDelegate = [[EXSessionUploadTaskDelegate alloc] initWithResolve:resolve reject:reject];
591
+ [_sessionTaskDispatcher registerTaskDelegate:taskDelegate forTask:task];
592
+ [task resume];
593
+ }
594
+
595
+ EX_EXPORT_METHOD_AS(uploadTaskStartAsync,
596
+ uploadTaskStartAsync:(NSString *)urlString
597
+ localURI:(NSString *)fileUriString
598
+ uuid:(NSString *)uuid
599
+ options:(NSDictionary *)options
600
+ resolver:(EXPromiseResolveBlock)resolve
601
+ rejecter:(EXPromiseRejectBlock)reject)
602
+ {
603
+ NSURLSessionUploadTask *task = [self createUploadTask:urlString localURI:fileUriString options:options rejecter:reject];
604
+ if (!task) {
605
+ return;
606
+ }
607
+
608
+ EX_WEAKIFY(self);
609
+ EXUploadDelegateOnSendCallback onSend = ^(NSURLSessionUploadTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) {
610
+ EX_ENSURE_STRONGIFY(self);
611
+ [self sendEventWithName:EXUploadProgressEventName
612
+ body:@{
613
+ @"uuid": uuid,
614
+ @"data": @{
615
+ @"totalBytesSent": @(totalBytesSent),
616
+ @"totalBytesExpectedToSend": @(totalBytesExpectedToSend),
617
+ },
618
+ }];
619
+ };
620
+
621
+ EXSessionTaskDelegate *taskDelegate = [[EXSessionCancelableUploadTaskDelegate alloc] initWithResolve:resolve
622
+ reject:reject
623
+ onSendCallback:onSend
624
+ resumableManager:_taskHandlersManager
625
+ uuid:uuid];
626
+
627
+ [_sessionTaskDispatcher registerTaskDelegate:taskDelegate forTask:task];
628
+ [_taskHandlersManager registerTask:task uuid:uuid];
629
+ [task resume];
630
+ }
631
+
632
+ - (NSURLSessionUploadTask * _Nullable)createUploadTask:(NSString *)urlString
633
+ localURI:(NSString *)fileUriString
634
+ options:(NSDictionary *)options
635
+ rejecter:(EXPromiseRejectBlock)reject
636
+ {
637
+ NSURL *fileUri = [self percentEncodedURLFromURIString:fileUriString];
638
+ NSString *httpMethod = options[@"httpMethod"];
639
+ EXFileSystemUploadType type = [self _getUploadTypeFrom:options[@"uploadType"]];
640
+ if (![fileUri.scheme isEqualToString:@"file"]) {
641
+ reject(@"ERR_FILESYSTEM_NO_PERMISSIONS",
642
+ [NSString stringWithFormat:@"Cannot upload file '%@'. Only 'file://' URI are supported.", fileUri],
643
+ nil);
644
+ return nil;
645
+ }
646
+ if (!([self _checkIfFileExists:fileUri.path])) {
647
+ reject(@"ERR_FILE_NOT_EXISTS",
648
+ [NSString stringWithFormat:@"File '%@' does not exist.", fileUri],
649
+ nil);
650
+ return nil;
651
+ }
652
+ if (![self _checkHeadersDictionary:options[@"headers"]]) {
653
+ reject(@"ERR_FILESYSTEM_INVALID_HEADERS_DICTIONARY",
654
+ @"Invalid headers dictionary. Keys and values should be strings.",
655
+ nil);
656
+ return nil;
657
+ }
658
+ if (!httpMethod) {
659
+ reject(@"ERR_FILESYSTEM_MISSING_HTTP_METHOD", @"Missing HTTP method.", nil);
660
+ return nil;
661
+ }
662
+
663
+ NSMutableURLRequest *request = [self _createRequest:[NSURL URLWithString:urlString] headers:options[@"headers"]];
664
+ [request setHTTPMethod:httpMethod];
665
+ NSURLSession *session = [self _sessionForType:[options[@"sessionType"] intValue]];
666
+ if (!session) {
667
+ reject(@"ERR_FILESYSTEM_INVALID_SESSION_TYPE",
668
+ [NSString stringWithFormat:@"Invalid session type: '%@'", options[@"sessionType"]],
669
+ nil);
670
+ return nil;
671
+ }
672
+
673
+ NSURLSessionUploadTask *task;
674
+ if (type == EXFileSystemBinaryContent) {
675
+ task = [session uploadTaskWithRequest:request fromFile:fileUri];
676
+ } else if (type == EXFileSystemMultipart) {
677
+ NSString *boundaryString = [[NSUUID UUID] UUIDString];
678
+ [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundaryString] forHTTPHeaderField:@"Content-Type"];
679
+ NSData *httpBody = [self _createBodyWithBoundary:boundaryString
680
+ fileUri:fileUri
681
+ parameters:options[@"parameters"]
682
+ fieldName:options[@"fieldName"]
683
+ mimeType:options[@"mimeType"]];
684
+ [request setHTTPBody:httpBody];
685
+ task = [session uploadTaskWithStreamedRequest:request];
686
+ } else {
687
+ reject(@"ERR_FILESYSTEM_INVALID_UPLOAD_TYPE",
688
+ [NSString stringWithFormat:@"Invalid upload type: '%@'.", options[@"uploadType"]],
689
+ nil);
690
+ }
691
+ return task;
692
+ }
693
+
694
+ EX_EXPORT_METHOD_AS(downloadResumableStartAsync,
695
+ downloadResumableStartAsyncWithUrl:(NSString *)urlString
696
+ fileURI:(NSString *)fileUri
697
+ uuid:(NSString *)uuid
698
+ options:(NSDictionary *)options
699
+ resumeData:(NSString *)data
700
+ resolver:(EXPromiseResolveBlock)resolve
701
+ rejecter:(EXPromiseRejectBlock)reject)
702
+ {
703
+ NSURL *url = [NSURL URLWithString:urlString];
704
+ NSURL *localUrl = [self percentEncodedURLFromURIString:fileUri];
705
+ if (!([self checkIfFileDirExists:localUrl.path])) {
706
+ reject(@"ERR_FILESYSTEM_WRONG_DESTINATION",
707
+ [NSString stringWithFormat:@"Directory for '%@' doesn't exist. Please make sure directory '%@' exists before calling downloadAsync.", fileUri, [localUrl.path stringByDeletingLastPathComponent]],
708
+ nil);
709
+ return;
710
+ }
711
+ if (![localUrl.scheme isEqualToString:@"file"]) {
712
+ reject(@"ERR_FILESYSTEM_NO_PERMISSIONS",
713
+ [NSString stringWithFormat:@"Cannot download to '%@': only 'file://' URI destinations are supported.", fileUri],
714
+ nil);
715
+ return;
716
+ }
717
+
718
+ NSString *path = localUrl.path;
719
+ if (!([self _permissionsForPath:path] & EXFileSystemPermissionWrite)) {
720
+ reject(@"ERR_FILESYSTEM_NO_PERMISSIONS",
721
+ [NSString stringWithFormat:@"File '%@' isn't writable.", fileUri],
722
+ nil);
723
+ return;
724
+ }
725
+
726
+ if (![self _checkHeadersDictionary:options[@"headers"]]) {
727
+ reject(@"ERR_FILESYSTEM_INVALID_HEADERS_DICTIONARY",
728
+ @"Invalid headers dictionary. Keys and values should be strings.",
729
+ nil);
730
+ return;
731
+ }
732
+
733
+ NSData *resumeData = data ? [[NSData alloc] initWithBase64EncodedString:data options:0] : nil;
734
+ [self _downloadResumableCreateSessionWithUrl:url
735
+ fileUrl:localUrl
736
+ uuid:uuid
737
+ optins:options
738
+ resumeData:resumeData
739
+ resolve:resolve
740
+ reject:reject];
741
+ }
742
+
743
+ EX_EXPORT_METHOD_AS(downloadResumablePauseAsync,
744
+ downloadResumablePauseAsyncWithUUID:(NSString *)uuid
745
+ resolver:(EXPromiseResolveBlock)resolve
746
+ rejecter:(EXPromiseRejectBlock)reject)
747
+ {
748
+ NSURLSessionDownloadTask *task = [_taskHandlersManager downloadTaskForId:uuid];
749
+ if (!task) {
750
+ reject(@"ERR_FILESYSTEM_CANNOT_FIND_TASK",
751
+ [NSString stringWithFormat:@"There is no download object with UUID: %@", uuid],
752
+ nil);
753
+ return;
754
+ }
755
+
756
+ EX_WEAKIFY(self);
757
+ [task cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
758
+ EX_ENSURE_STRONGIFY(self);
759
+ resolve(@{ @"resumeData": EXNullIfNil([resumeData base64EncodedStringWithOptions:0]) });
760
+ }];
761
+ }
762
+
763
+ EX_EXPORT_METHOD_AS(networkTaskCancelAsync,
764
+ networkTaskCancelAsyncWithUUID:(NSString *)uuid
765
+ resolver:(EXPromiseResolveBlock)resolve
766
+ rejecter:(EXPromiseRejectBlock)reject)
767
+ {
768
+ NSURLSessionTask *task = [_taskHandlersManager taskForId:uuid];
769
+ if (task) {
770
+ [task cancel];
771
+ }
772
+ resolve([NSNull null]);
773
+ }
774
+
775
+ EX_EXPORT_METHOD_AS(getFreeDiskStorageAsync, getFreeDiskStorageAsyncWithResolver:(EXPromiseResolveBlock)resolve rejecter:(EXPromiseRejectBlock)reject)
776
+ {
777
+ NSError *error = nil;
778
+ NSNumber *freeDiskStorage = [self freeDiskStorageWithError:&error];
779
+
780
+ if(!freeDiskStorage || error) {
781
+ reject(@"ERR_FILESYSTEM_CANNOT_DETERMINE_DISK_CAPACITY", @"Unable to determine free disk storage capacity", error);
782
+ } else {
783
+ resolve(freeDiskStorage);
784
+ }
785
+ }
786
+
787
+ EX_EXPORT_METHOD_AS(getTotalDiskCapacityAsync, getTotalDiskCapacityAsyncWithResolver:(EXPromiseResolveBlock)resolve rejecter:(EXPromiseRejectBlock)reject)
788
+ {
789
+ NSError *error = nil;
790
+ NSNumber *diskCapacity = [self totalDiskCapacityWithError:&error];
791
+
792
+ if (!diskCapacity || error) {
793
+ reject(@"ERR_FILESYSTEM_CANNOT_DETERMINE_DISK_CAPACITY", @"Unable to determine total disk capacity", error);
794
+ } else {
795
+ resolve(diskCapacity);
796
+ }
797
+ }
798
+
799
+ #pragma mark - Internal methods
800
+
801
+ - (EXFileSystemUploadType)_getUploadTypeFrom:(NSNumber * _Nullable)type
802
+ {
803
+ switch ([type intValue]) {
804
+ case EXFileSystemBinaryContent:
805
+ case EXFileSystemMultipart:
806
+ return [type intValue];
807
+ }
808
+
809
+ return EXFileSystemInvalidType;
810
+ }
811
+
812
+ // Borrowed from http://stackoverflow.com/questions/2439020/wheres-the-iphone-mime-type-database
813
+ - (NSString *)_guessMIMETypeFromPath:(NSString *)path
814
+ {
815
+ CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[path pathExtension], NULL);
816
+ CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType);
817
+ if (UTI) {
818
+ CFRelease(UTI);
819
+ }
820
+ if (!MIMEType) {
821
+ return @"application/octet-stream";
822
+ }
823
+ return (__bridge NSString *)(MIMEType);
824
+ }
825
+
826
+ - (NSData *)_createBodyWithBoundary:(NSString *)boundary
827
+ fileUri:(NSURL *)fileUri
828
+ parameters:(NSDictionary * _Nullable)parameters
829
+ fieldName:(NSString * _Nullable)fieldName
830
+ mimeType:(NSString * _Nullable)mimetype
831
+ {
832
+
833
+ NSMutableData *body = [NSMutableData data];
834
+ NSData *data = [NSData dataWithContentsOfURL:fileUri];
835
+ NSString *filename = [[fileUri path] lastPathComponent];
836
+
837
+ if (!mimetype) {
838
+ mimetype = [self _guessMIMETypeFromPath:[fileUri path]];
839
+ }
840
+
841
+ [parameters enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) {
842
+ [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
843
+ [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", parameterKey] dataUsingEncoding:NSUTF8StringEncoding]];
844
+ [body appendData:[[NSString stringWithFormat:@"%@\r\n", parameterValue] dataUsingEncoding:NSUTF8StringEncoding]];
845
+ }];
846
+
847
+ [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
848
+ [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", fieldName ?: filename, filename] dataUsingEncoding:NSUTF8StringEncoding]];
849
+ [body appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", mimetype] dataUsingEncoding:NSUTF8StringEncoding]];
850
+ [body appendData:data];
851
+ [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
852
+ [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
853
+
854
+ return body;
855
+ }
856
+
857
+ - (NSMutableURLRequest *)_createRequest:(NSURL *)url headers:(NSDictionary * _Nullable)headers
858
+ {
859
+ NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
860
+ if (headers != nil) {
861
+ for (NSString *headerKey in headers) {
862
+ [request setValue:[headers valueForKey:headerKey] forHTTPHeaderField:headerKey];
863
+ }
864
+ }
865
+
866
+ return request;
867
+ }
868
+
869
+ - (NSURLSession *)_sessionForType:(EXFileSystemSessionType)type
870
+ {
871
+ switch (type) {
872
+ case EXFileSystemBackgroundSession:
873
+ return _backgroundSession;
874
+ case EXFileSystemForegroundSession:
875
+ return _foregroundSession;
876
+ }
877
+ return nil;
878
+ }
879
+
880
+ - (BOOL)_checkHeadersDictionary:(NSDictionary * _Nullable)headers
881
+ {
882
+ for (id key in [headers allKeys]) {
883
+ if (![key isKindOfClass:[NSString class]] || ![headers[key] isKindOfClass:[NSString class]]) {
884
+ return false;
885
+ }
886
+ }
887
+
888
+ return true;
889
+ }
890
+
891
+ - (NSURLSession *)_createSession:(EXFileSystemSessionType)type delegate:(id<NSURLSessionDelegate>)delegate
892
+ {
893
+ NSURLSessionConfiguration *sessionConfiguration;
894
+ if (type == EXFileSystemForegroundSession) {
895
+ sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
896
+ } else {
897
+ sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[[NSUUID UUID] UUIDString]];
898
+ }
899
+ sessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
900
+ sessionConfiguration.URLCache = nil;
901
+ return [NSURLSession sessionWithConfiguration:sessionConfiguration
902
+ delegate:delegate
903
+ delegateQueue:nil];
904
+ }
905
+
906
+ - (BOOL)_checkIfFileExists:(NSString *)path
907
+ {
908
+ return [[NSFileManager defaultManager] fileExistsAtPath:path];
909
+ }
910
+
911
+ - (void)_downloadResumableCreateSessionWithUrl:(NSURL *)url
912
+ fileUrl:(NSURL *)fileUrl
913
+ uuid:(NSString *)uuid
914
+ optins:(NSDictionary *)options
915
+ resumeData:(NSData * _Nullable)resumeData
916
+ resolve:(EXPromiseResolveBlock)resolve
917
+ reject:(EXPromiseRejectBlock)reject
918
+ {
919
+ EX_WEAKIFY(self);
920
+ EXDownloadDelegateOnWriteCallback onWrite = ^(NSURLSessionDownloadTask *task, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) {
921
+ EX_ENSURE_STRONGIFY(self);
922
+ [self sendEventWithName:EXDownloadProgressEventName
923
+ body:@{
924
+ @"uuid": uuid,
925
+ @"data": @{
926
+ @"totalBytesWritten": @(totalBytesWritten),
927
+ @"totalBytesExpectedToWrite": @(totalBytesExpectedToWrite),
928
+ },
929
+ }];
930
+ };
931
+
932
+ NSURLSessionDownloadTask *downloadTask;
933
+ NSURLSession *session = [self _sessionForType:[options[@"sessionType"] intValue]];
934
+ if (!session) {
935
+ reject(@"ERR_FILESYSTEM_INVALID_SESSION_TYPE",
936
+ [NSString stringWithFormat:@"Invalid session type: '%@'", options[@"sessionType"]],
937
+ nil);
938
+ return;
939
+ }
940
+
941
+ if (resumeData) {
942
+ downloadTask = [session downloadTaskWithResumeData:resumeData];
943
+ } else {
944
+ NSURLRequest *request = [self _createRequest:url headers:options[@"headers"]];
945
+ downloadTask = [session downloadTaskWithRequest:request];
946
+ }
947
+ EXSessionTaskDelegate *taskDelegate = [[EXSessionResumableDownloadTaskDelegate alloc] initWithResolve:resolve
948
+ reject:reject
949
+ localUrl:fileUrl
950
+ shouldCalculateMd5:[options[@"md5"] boolValue]
951
+ onWriteCallback:onWrite
952
+ resumableManager:_taskHandlersManager
953
+ uuid:uuid];
954
+ [_sessionTaskDispatcher registerTaskDelegate:taskDelegate forTask:downloadTask];
955
+ [_taskHandlersManager registerTask:downloadTask uuid:uuid];
956
+ [downloadTask resume];
957
+ }
958
+
959
+ - (EXFileSystemPermissionFlags)_permissionsForPath:(NSString *)path
960
+ {
961
+ return [[_moduleRegistry getModuleImplementingProtocol:@protocol(EXFilePermissionModuleInterface)] getPathPermissions:(NSString *)path];
962
+ }
963
+
964
+ - (void)sendEventWithName:(NSString *)eventName body:(id)body
965
+ {
966
+ if (_eventEmitter != nil) {
967
+ [_eventEmitter sendEventWithName:eventName body:body];
968
+ }
969
+ }
970
+
971
+ - (NSDictionary<NSURLResourceKey, id> *)documentFileResourcesForKeys:(NSArray<NSURLResourceKey> *)keys
972
+ error:(out NSError * __autoreleasing *)error
973
+ {
974
+ if (!keys.count) {
975
+ return @{};
976
+ }
977
+
978
+ NSURL *documentDirectoryUrl = [NSURL fileURLWithPath:_documentDirectory];
979
+ NSDictionary *results = [documentDirectoryUrl resourceValuesForKeys:keys
980
+ error:error];
981
+
982
+ if (!results) {
983
+ return @{};
984
+ }
985
+
986
+ return results;
987
+ }
988
+
989
+ #pragma mark - Public utils
990
+
991
+ - (EXFileSystemPermissionFlags)permissionsForURI:(NSURL *)uri
992
+ {
993
+ NSArray *validSchemas = @[
994
+ @"assets-library",
995
+ @"http",
996
+ @"https",
997
+ @"ph",
998
+ ];
999
+ if ([validSchemas containsObject:uri.scheme]) {
1000
+ return EXFileSystemPermissionRead;
1001
+ }
1002
+ if ([uri.scheme isEqualToString:@"file"]) {
1003
+ return [self _permissionsForPath:uri.path];
1004
+ }
1005
+ return EXFileSystemPermissionNone;
1006
+ }
1007
+
1008
+ - (BOOL)checkIfFileDirExists:(NSString *)path
1009
+ {
1010
+ NSString *dir = [path stringByDeletingLastPathComponent];
1011
+ return [self _checkIfFileExists:dir];
1012
+ }
1013
+
1014
+ /**
1015
+ Given an URI string, returns a percent-encoded URL as an NSURL object.
1016
+ Only encodes characters that are outside the set of allowed characters defined by RFC3986.
1017
+ */
1018
+ - (nullable NSURL *)percentEncodedURLFromURIString:(nonnull NSString *)uri
1019
+ {
1020
+ NSMutableCharacterSet *allowedCharacterSet = [NSMutableCharacterSet alphanumericCharacterSet];
1021
+ [allowedCharacterSet addCharactersInString:@"-._~:/?#[]@!$&'()*+,;="];
1022
+ NSString *encodedUriString = [uri stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
1023
+
1024
+ return [NSURL URLWithString:[encodedUriString stringByReplacingOccurrencesOfString:@"%25" withString:@"%"]];
1025
+ }
1026
+
1027
+ #pragma mark - Class methods
1028
+
1029
+ - (BOOL)ensureDirExistsWithPath:(NSString *)path
1030
+ {
1031
+ return [EXFileSystem ensureDirExistsWithPath:path];
1032
+ }
1033
+
1034
+ + (BOOL)ensureDirExistsWithPath:(NSString *)path
1035
+ {
1036
+ BOOL isDir = NO;
1037
+ NSError *error;
1038
+ BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir];
1039
+ if (!(exists && isDir)) {
1040
+ [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error];
1041
+ if (error) {
1042
+ return NO;
1043
+ }
1044
+ }
1045
+ return YES;
1046
+ }
1047
+
1048
+ - (NSString *)generatePathInDirectory:(NSString *)directory withExtension:(NSString *)extension
1049
+ {
1050
+ return [EXFileSystem generatePathInDirectory:directory withExtension:extension];
1051
+ }
1052
+
1053
+ + (NSString *)generatePathInDirectory:(NSString *)directory withExtension:(NSString *)extension
1054
+ {
1055
+ NSString *fileName = [[[NSUUID UUID] UUIDString] stringByAppendingString:extension];
1056
+ [EXFileSystem ensureDirExistsWithPath:directory];
1057
+ return [directory stringByAppendingPathComponent:fileName];
1058
+ }
1059
+
1060
+ // '<ARCType> *__autoreleasing*' problem solution: https://stackoverflow.com/a/8862061/4337317
1061
+ - (NSNumber *)totalDiskCapacityWithError:(out NSError * __autoreleasing *)error
1062
+ {
1063
+ NSDictionary *results = [self documentFileResourcesForKeys:@[NSURLVolumeTotalCapacityKey]
1064
+ error:error];
1065
+
1066
+ return results[NSURLVolumeTotalCapacityKey];
1067
+ }
1068
+
1069
+ // '<ARCType> *__autoreleasing*' problem solution: https://stackoverflow.com/a/8862061/4337317
1070
+ - (NSNumber *)freeDiskStorageWithError:(out NSError * __autoreleasing *)error
1071
+ {
1072
+ #if TARGET_OS_TV
1073
+ NSURLResourceKey key = NSURLVolumeAvailableCapacityKey;
1074
+ #else
1075
+ NSURLResourceKey key = NSURLVolumeAvailableCapacityForImportantUsageKey;
1076
+ #endif
1077
+
1078
+ NSDictionary *results = [self documentFileResourcesForKeys:@[key]
1079
+ error:error];
1080
+
1081
+ return results[key];
1082
+ }
1083
+
1084
+ @end