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