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