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