// Copyright 2016-present 650 Industries. All rights reserved.
#import <UMCore/UMModuleRegistry.h>
#import <EXFileSystem/EXDownloadDelegate.h>
#import <EXFileSystem/EXFileSystem.h>
#import <CommonCrypto/CommonDigest.h>
#import <EXFileSystem/EXFileSystemLocalFileHandler.h>
#import <EXFileSystem/EXFileSystemAssetLibraryHandler.h>
#import <UMFileSystemInterface/UMFileSystemInterface.h>
#import <UMFileSystemInterface/UMFilePermissionModuleInterface.h>
#import <UMCore/UMEventEmitterService.h>
NSString * const EXDownloadProgressEventName = @"Exponent.downloadProgress";
@interface EXDownloadResumable : NSObject
@property (nonatomic, strong) NSString *uuid;
@property (nonatomic, strong) NSURLSession *session;
@property (nonatomic, strong) EXDownloadDelegate *delegate;
@end
@implementation EXDownloadResumable
- (instancetype)initWithId:(NSString *)uuid
withSession:(NSURLSession *)session
withDelegate:(EXDownloadDelegate *)delegate;
{
if ((self = [super init])) {
_uuid = uuid;
_session = session;
_delegate = delegate;
}
return self;
}
@end
@interface EXFileSystem ()
@property (nonatomic, strong) NSMutableDictionary<NSString *, EXDownloadResumable*> *downloadObjects;
@property (nonatomic, weak) UMModuleRegistry *moduleRegistry;
@property (nonatomic, weak) id<UMEventEmitterService> eventEmitter;
@property (nonatomic, strong) NSString *documentDirectory;
@property (nonatomic, strong) NSString *cachesDirectory;
@property (nonatomic, strong) NSString *bundleDirectory;
@end
@implementation NSData (EXFileSystem)
- (NSString *)md5String
{
unsigned char digest[CC_MD5_DIGEST_LENGTH];
CC_MD5(self.bytes, (CC_LONG) self.length, digest);
NSMutableString *md5 = [NSMutableString stringWithCapacity:2 * CC_MD5_DIGEST_LENGTH];
for (unsigned int i = 0; i < CC_MD5_DIGEST_LENGTH; ++i) {
[md5 appendFormat:@"%02x", digest[i]];
}
return md5;
}
@end
@implementation EXFileSystem
UM_REGISTER_MODULE();
+ (const NSString *)exportedModuleName
{
return @"ExponentFileSystem";
}
+ (const NSArray<Protocol *> *)exportedInterfaces
{
return @[@protocol(UMFileSystemInterface)];
}
- (instancetype)initWithDocumentDirectory:(NSString *)documentDirectory cachesDirectory:(NSString *)cachesDirectory bundleDirectory:(NSString *)bundleDirectory
{
if (self = [super init]) {
_documentDirectory = documentDirectory;
_cachesDirectory = cachesDirectory;
_bundleDirectory = bundleDirectory;
_downloadObjects = [NSMutableDictionary dictionary];
[EXFileSystem ensureDirExistsWithPath:_documentDirectory];
[EXFileSystem ensureDirExistsWithPath:_cachesDirectory];
}
return self;
}
- (instancetype)init
{
NSArray<NSString *> *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [documentPaths objectAtIndex:0];
NSArray<NSString *> *cachesPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cacheDirectory = [cachesPaths objectAtIndex:0];
return [self initWithDocumentDirectory:documentDirectory
cachesDirectory:cacheDirectory
bundleDirectory:[NSBundle mainBundle].bundlePath];
}
- (void)setModuleRegistry:(UMModuleRegistry *)moduleRegistry
{
_moduleRegistry = moduleRegistry;
_eventEmitter = [_moduleRegistry getModuleImplementingProtocol:@protocol(UMEventEmitterService)];
}
- (NSDictionary *)constantsToExport
{
return @{
@"documentDirectory": _documentDirectory ? [NSURL fileURLWithPath:_documentDirectory].absoluteString : [NSNull null],
@"cacheDirectory": _cachesDirectory ? [NSURL fileURLWithPath:_cachesDirectory].absoluteString : [NSNull null],
@"bundleDirectory": _bundleDirectory ? [NSURL fileURLWithPath:_bundleDirectory].absoluteString : [NSNull null]
};
}
- (NSArray<NSString *> *)supportedEvents
{
return @[EXDownloadProgressEventName];
}
- (void)startObserving {
}
- (void)stopObserving {
}
- (NSDictionary *)encodingMap
{
/*
TODO:Bacon: match node.js fs
https://github.com/nodejs/node/blob/master/lib/buffer.js
ascii
base64
binary
hex
ucs2/ucs-2
utf16le/utf-16le
utf8/utf-8
latin1 (ISO8859-1, only in node 6.4.0+)
*/
return @{
@"ascii": @(NSASCIIStringEncoding),
@"nextstep": @(NSNEXTSTEPStringEncoding),
@"japaneseeuc": @(NSJapaneseEUCStringEncoding),
@"utf8": @(NSUTF8StringEncoding),
@"isolatin1": @(NSISOLatin1StringEncoding),
@"symbol": @(NSSymbolStringEncoding),
@"nonlossyascii": @(NSNonLossyASCIIStringEncoding),
@"shiftjis": @(NSShiftJISStringEncoding),
@"isolatin2": @(NSISOLatin2StringEncoding),
@"unicode": @(NSUnicodeStringEncoding),
@"windowscp1251": @(NSWindowsCP1251StringEncoding),
@"windowscp1252": @(NSWindowsCP1252StringEncoding),
@"windowscp1253": @(NSWindowsCP1253StringEncoding),
@"windowscp1254": @(NSWindowsCP1254StringEncoding),
@"windowscp1250": @(NSWindowsCP1250StringEncoding),
@"iso2022jp": @(NSISO2022JPStringEncoding),
@"macosroman": @(NSMacOSRomanStringEncoding),
@"utf16": @(NSUTF16StringEncoding),
@"utf16bigendian": @(NSUTF16BigEndianStringEncoding),
@"utf16littleendian": @(NSUTF16LittleEndianStringEncoding),
@"utf32": @(NSUTF32StringEncoding),
@"utf32bigendian": @(NSUTF32BigEndianStringEncoding),
@"utf32littleendian": @(NSUTF32LittleEndianStringEncoding),
};
}
UM_EXPORT_METHOD_AS(getInfoAsync,
getInfoAsyncWithURI:(NSString *)uriString
withOptions:(NSDictionary *)options
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
NSURL *uri = [NSURL URLWithString:uriString];
if (!([self permissionsForURI:uri] & UMFileSystemPermissionRead)) {
reject(@"E_FILESYSTEM_PERMISSIONS",
[NSString stringWithFormat:@"File '%@' isn't readable.", uri],
nil);
return;
}
if ([uri.scheme isEqualToString:@"file"]) {
[EXFileSystemLocalFileHandler getInfoForFile:uri withOptions:options resolver:resolve rejecter:reject];
} else if ([uri.scheme isEqualToString:@"assets-library"] || [uri.scheme isEqualToString:@"ph"]) {
[EXFileSystemAssetLibraryHandler getInfoForFile:uri withOptions:options resolver:resolve rejecter:reject];
} else {
reject(@"E_FILESYSTEM_INVALID_URI",
[NSString stringWithFormat:@"Unsupported URI scheme for '%@'", uri],
nil);
}
}
UM_EXPORT_METHOD_AS(readAsStringAsync,
readAsStringAsyncWithURI:(NSString *)uriString
withOptions:(NSDictionary *)options
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
NSURL *uri = [NSURL URLWithString:uriString];
if (!([self permissionsForURI:uri] & UMFileSystemPermissionRead)) {
reject(@"E_FILESYSTEM_PERMISSIONS",
[NSString stringWithFormat:@"File '%@' isn't readable.", uri],
nil);
return;
}
if ([uri.scheme isEqualToString:@"file"]) {
NSString *encodingType = @"utf8";
if (options[@"encoding"] && [options[@"encoding"] isKindOfClass:[NSString class]]) {
encodingType = [options[@"encoding"] lowercaseString];
}
if ([encodingType isEqualToString:@"base64"]) {
NSFileHandle *file = [NSFileHandle fileHandleForReadingAtPath:uri.path];
if (file == nil) {
reject(@"E_FILE_NOT_READ",
[NSString stringWithFormat:@"File '%@' could not be read.", uri.path],
nil);
return;
}
// position and length are used as a cursor/paging system.
if ([options[@"position"] isKindOfClass:[NSNumber class]]) {
[file seekToFileOffset:[options[@"position"] intValue]];
}
NSData *data;
if ([options[@"length"] isKindOfClass:[NSNumber class]]) {
data = [file readDataOfLength:[options[@"length"] intValue]];
} else {
data = [file readDataToEndOfFile];
}
resolve([data base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]);
} else {
NSUInteger encoding = NSUTF8StringEncoding;
id possibleEncoding = [[self encodingMap] valueForKey:encodingType];
if (possibleEncoding != nil) {
encoding = [possibleEncoding integerValue];
}
NSError *error;
NSString *string = [NSString stringWithContentsOfFile:uri.path encoding:encoding error:&error];
if (string) {
resolve(string);
} else {
reject(@"E_FILE_NOT_READ",
[NSString stringWithFormat:@"File '%@' could not be read.", uri],
error);
}
}
} else {
reject(@"E_FILESYSTEM_INVALID_URI",
[NSString stringWithFormat:@"Unsupported URI scheme for '%@'", uri],
nil);
}
}
UM_EXPORT_METHOD_AS(writeAsStringAsync,
writeAsStringAsyncWithURI:(NSString *)uriString
withString:(NSString *)string
withOptions:(NSDictionary *)options
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
NSURL *uri = [NSURL URLWithString:uriString];
if (!([self permissionsForURI:uri] & UMFileSystemPermissionWrite)) {
reject(@"E_FILESYSTEM_PERMISSIONS",
[NSString stringWithFormat:@"File '%@' isn't writable.", uri],
nil);
return;
}
if ([uri.scheme isEqualToString:@"file"]) {
NSString *encodingType = @"utf8";
if ([options[@"encoding"] isKindOfClass:[NSString class]]) {
encodingType = [options[@"encoding"] lowercaseString];
}
if ([encodingType isEqualToString:@"base64"]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *imageData = [[NSData alloc] initWithBase64EncodedString:string options:NSDataBase64DecodingIgnoreUnknownCharacters];
if (imageData) {
// TODO:Bacon: Should we surface `attributes`?
if ([[NSFileManager defaultManager] createFileAtPath:uri.path contents:imageData attributes:nil]) {
resolve([NSNull null]);
} else {
return reject(@"E_FILE_UNKNOWN",
[NSString stringWithFormat:@"No such file or directory '%@'", uri.path],
nil);
}
} else {
reject(@"E_INVALID_FORMAT",
@"Failed to parse base64 string.",
nil);
}
});
} else {
NSUInteger encoding = NSUTF8StringEncoding;
id possibleEncoding = [[self encodingMap] valueForKey:encodingType];
if (possibleEncoding != nil) {
encoding = [possibleEncoding integerValue];
}
NSError *error;
if ([string writeToFile:uri.path atomically:YES encoding:encoding error:&error]) {
resolve([NSNull null]);
} else {
reject(@"E_FILE_NOT_WRITTEN",
[NSString stringWithFormat:@"File '%@' could not be written.", uri],
error);
}
}
} else {
reject(@"E_FILESYSTEM_INVALID_URI",
[NSString stringWithFormat:@"Unsupported URI scheme for '%@'", uri],
nil);
}
}
UM_EXPORT_METHOD_AS(deleteAsync,
deleteAsyncWithURI:(NSString *)uriString
withOptions:(NSDictionary *)options
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
NSURL *uri = [NSURL URLWithString:uriString];
if (!([self permissionsForURI:[uri URLByAppendingPathComponent:@".."]] & UMFileSystemPermissionWrite)) {
reject(@"E_FILESYSTEM_PERMISSIONS",
[NSString stringWithFormat:@"Location '%@' isn't deletable.", uri],
nil);
return;
}
if ([uri.scheme isEqualToString:@"file"]) {
NSString *path = uri.path;
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSError *error;
if ([[NSFileManager defaultManager] removeItemAtPath:path error:&error]) {
resolve([NSNull null]);
} else {
reject(@"E_FILE_NOT_DELETED",
[NSString stringWithFormat:@"File '%@' could not be deleted.", uri],
error);
}
} else {
if (options[@"idempotent"]) {
resolve([NSNull null]);
} else {
reject(@"E_FILE_NOT_FOUND",
[NSString stringWithFormat:@"File '%@' could not be deleted because it could not be found.", uri],
nil);
}
}
} else {
reject(@"E_FILESYSTEM_INVALID_URI",
[NSString stringWithFormat:@"Unsupported URI scheme for '%@'", uri],
nil);
}
}
UM_EXPORT_METHOD_AS(moveAsync,
moveAsyncWithOptions:(NSDictionary *)options
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
NSURL *from = [NSURL URLWithString:options[@"from"]];
if (!from) {
reject(@"E_MISSING_PARAMETER", @"Need a `from` location.", nil);
return;
}
if (!([self permissionsForURI:[from URLByAppendingPathComponent:@".."]] & UMFileSystemPermissionWrite)) {
reject(@"E_FILESYSTEM_PERMISSIONS",
[NSString stringWithFormat:@"Location '%@' isn't movable.", from],
nil);
return;
}
NSURL *to = [NSURL URLWithString:options[@"to"]];
if (!to) {
reject(@"E_MISSING_PARAMETER", @"Need a `to` location.", nil);
return;
}
if (!([self permissionsForURI:to] & UMFileSystemPermissionWrite)) {
reject(@"E_FILESYSTEM_PERMISSIONS",
[NSString stringWithFormat:@"File '%@' isn't writable.", to],
nil);
return;
}
// NOTE: The destination-delete and the move should happen atomically, but we hope for the best for now
if ([from.scheme isEqualToString:@"file"]) {
NSString *fromPath = [from.path stringByStandardizingPath];
NSString *toPath = [to.path stringByStandardizingPath];
NSError *error;
if ([[NSFileManager defaultManager] fileExistsAtPath:toPath]) {
if (![[NSFileManager defaultManager] removeItemAtPath:toPath error:&error]) {
reject(@"E_FILE_NOT_MOVED",
[NSString stringWithFormat:@"File '%@' could not be moved to '%@' because a file already exists at "
"the destination and could not be deleted.", from, to],
error);
return;
}
}
if ([[NSFileManager defaultManager] moveItemAtPath:fromPath toPath:toPath error:&error]) {
resolve([NSNull null]);
} else {
reject(@"E_FILE_NOT_MOVED",
[NSString stringWithFormat:@"File '%@' could not be moved to '%@'.", from, to],
error);
}
} else {
reject(@"E_FILESYSTEM_INVALID_URI",
[NSString stringWithFormat:@"Unsupported URI scheme for '%@'", from],
nil);
}
}
UM_EXPORT_METHOD_AS(copyAsync,
copyAsyncWithOptions:(NSDictionary *)options
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
NSURL *from = [NSURL URLWithString:options[@"from"]];
if (!from) {
reject(@"E_MISSING_PARAMETER", @"Need a `from` location.", nil);
return;
}
if (!([self permissionsForURI:from] & UMFileSystemPermissionRead)) {
reject(@"E_FILESYSTEM_PERMISSIONS",
[NSString stringWithFormat:@"File '%@' isn't readable.", from],
nil);
return;
}
NSURL *to = [NSURL URLWithString:options[@"to"]];
if (!to) {
reject(@"E_MISSING_PARAMETER", @"Need a `to` location.", nil);
return;
}
if (!([self permissionsForURI:to] & UMFileSystemPermissionWrite)) {
reject(@"E_FILESYSTEM_PERMISSIONS",
[NSString stringWithFormat:@"File '%@' isn't writable.", to],
nil);
return;
}
if ([from.scheme isEqualToString:@"file"]) {
[EXFileSystemLocalFileHandler copyFrom:from to:to resolver:resolve rejecter:reject];
} else if ([from.scheme isEqualToString:@"assets-library"] || [from.scheme isEqualToString:@"ph"]) {
[EXFileSystemAssetLibraryHandler copyFrom:from to:to resolver:resolve rejecter:reject];
} else {
reject(@"E_FILESYSTEM_INVALID_URI",
[NSString stringWithFormat:@"Unsupported URI scheme for '%@'", from],
nil);
}
}
UM_EXPORT_METHOD_AS(makeDirectoryAsync,
makeDirectoryAsyncWithURI:(NSString *)uriString
withOptions:(NSDictionary *)options
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
NSURL *uri = [NSURL URLWithString:uriString];
if (!([self permissionsForURI:uri] & UMFileSystemPermissionWrite)) {
reject(@"E_FILESYSTEM_PERMISSIONS",
[NSString stringWithFormat:@"Directory '%@' could not be created because the location isn't writable.", uri],
nil);
return;
}
if ([uri.scheme isEqualToString:@"file"]) {
NSError *error;
if ([[NSFileManager defaultManager] createDirectoryAtPath:uri.path
withIntermediateDirectories:[options[@"intermediates"] boolValue]
attributes:nil
error:&error]) {
resolve([NSNull null]);
} else {
reject(@"E_DIRECTORY_NOT_CREATED",
[NSString stringWithFormat:@"Directory '%@' could not be created.", uri],
error);
}
} else {
reject(@"E_FILESYSTEM_INVALID_URI",
[NSString stringWithFormat:@"Unsupported URI scheme for '%@'", uri],
nil);
}
}
UM_EXPORT_METHOD_AS(readDirectoryAsync,
readDirectoryAsyncWithURI:(NSString *)uriString
withOptions:(NSDictionary *)options
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
NSURL *uri = [NSURL URLWithString:uriString];
if (!([self permissionsForURI:uri] & UMFileSystemPermissionRead)) {
reject(@"E_FILESYSTEM_PERMISSIONS",
[NSString stringWithFormat:@"Location '%@' isn't readable.", uri],
nil);
return;
}
if ([uri.scheme isEqualToString:@"file"]) {
NSError *error;
NSArray<NSString *> *children = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:uri.path error:&error];
if (children) {
resolve(children);
} else {
reject(@"E_DIRECTORY_NOT_READ",
[NSString stringWithFormat:@"Directory '%@' could not be read.", uri],
error);
}
} else {
reject(@"E_FILESYSTEM_INVALID_URI",
[NSString stringWithFormat:@"Unsupported URI scheme for '%@'", uri],
nil);
}
}
UM_EXPORT_METHOD_AS(downloadAsync,
downloadAsyncWithUrl:(NSString *)uriString
withLocalURI:(NSString *)localUriString
withOptions:(NSDictionary *)options
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
NSURL *url = [NSURL URLWithString:uriString];
NSURL *localUri = [NSURL URLWithString:localUriString];
if (!([self checkIfFileDirExists:localUri.path])) {
reject(@"E_FILESYSTEM_WRONG_DESTINATION",
[NSString stringWithFormat:@"Directory for %@ doesn't exist.", localUriString],
nil);
return;
}
if (!([self permissionsForURI:localUri] & UMFileSystemPermissionWrite)) {
reject(@"E_FILESYSTEM_PERMISSIONS",
[NSString stringWithFormat:@"File '%@' isn't writable.", localUri],
nil);
return;
}
NSString *path = localUri.path;
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
NSDictionary *headerDict = (NSDictionary *) [options objectForKey:@"headers"];
if (headerDict != nil) {
sessionConfiguration.HTTPAdditionalHeaders = headerDict;
}
sessionConfiguration.URLCache = nil;
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
reject(@"E_DOWNLOAD_FAILED",
[NSString stringWithFormat:@"Could not download from '%@'", url],
error);
return;
}
[data writeToFile:path atomically:YES];
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSMutableDictionary *result = [NSMutableDictionary dictionary];
result[@"uri"] = [NSURL fileURLWithPath:path].absoluteString;
if (options[@"md5"]) {
result[@"md5"] = [data md5String];
}
result[@"status"] = @([httpResponse statusCode]);
result[@"headers"] = [httpResponse allHeaderFields];
resolve(result);
}];
[task resume];
}
UM_EXPORT_METHOD_AS(downloadResumableStartAsync,
downloadResumableStartAsyncWithUrl:(NSString *)urlString
withFileURI:(NSString *)fileUri
withUUID:(NSString *)uuid
withOptions:(NSDictionary *)options
withResumeData:(NSString *)data
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
NSURL *url = [NSURL URLWithString:urlString];
NSURL *localUrl = [NSURL URLWithString:fileUri];
if (!([self checkIfFileDirExists:localUrl.path])) {
reject(@"E_FILESYSTEM_WRONG_DESTINATION",
[NSString stringWithFormat:@"Directory for %@ doesn't exist.", fileUri],
nil);
return;
}
if (![localUrl.scheme isEqualToString:@"file"]) {
reject(@"E_FILESYSTEM_PERMISSIONS",
[NSString stringWithFormat:@"Cannot download to '%@': only 'file://' URI destinations are supported.", fileUri],
nil);
return;
}
NSString *path = localUrl.path;
if (!([self _permissionsForPath:path] & UMFileSystemPermissionWrite)) {
reject(@"E_FILESYSTEM_PERMISSIONS",
[NSString stringWithFormat:@"File '%@' isn't writable.", fileUri],
nil);
return;
}
NSData *resumeData = data ? [[NSData alloc] initWithBase64EncodedString:data options:0] : nil;
[self _downloadResumableCreateSessionWithUrl:url
withScopedPath:path
withUUID:uuid
withOptions:options
withResumeData:resumeData
withResolver:resolve
withRejecter:reject];
}
UM_EXPORT_METHOD_AS(downloadResumablePauseAsync,
downloadResumablePauseAsyncWithUUID:(NSString *)uuid
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
EXDownloadResumable *downloadResumable = (EXDownloadResumable *)self.downloadObjects[uuid];
if (downloadResumable == nil) {
reject(@"E_UNABLE_TO_PAUSE",
[NSString stringWithFormat:@"There is no download object with UUID: %@", uuid],
nil);
} else {
[downloadResumable.session getTasksWithCompletionHandler:^(NSArray<NSURLSessionDataTask *> * _Nonnull dataTasks, NSArray<NSURLSessionUploadTask *> * _Nonnull uploadTasks, NSArray<NSURLSessionDownloadTask *> * _Nonnull downloadTasks) {
NSURLSessionDownloadTask *downloadTask = [downloadTasks firstObject];
if (downloadTask) {
[downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
resolve(@{ @"resumeData": UMNullIfNil([resumeData base64EncodedStringWithOptions:0]) });
}];
} else {
reject(@"E_UNABLE_TO_PAUSE",
@"There was an error producing resume data",
nil);
}
}];
}
}
UM_EXPORT_METHOD_AS(getFreeDiskStorageAsync, getFreeDiskStorageAsyncWithResolver:(UMPromiseResolveBlock)resolve rejecter:(UMPromiseRejectBlock)reject)
{
if(![self freeDiskStorage]) {
reject(@"ERR_FILESYSTEM", @"Unable to determine free disk storage capacity", nil);
} else {
resolve([self freeDiskStorage]);
}
}
UM_EXPORT_METHOD_AS(getTotalDiskCapacityAsync, getTotalDiskCapacityAsyncWithResolver:(UMPromiseResolveBlock)resolve rejecter:(UMPromiseRejectBlock)reject)
{
if(![self totalDiskCapacity]) {
reject(@"ERR_FILESYSTEM", @"Unable to determine total disk capacity", nil);
} else {
resolve([self totalDiskCapacity]);
}
}
#pragma mark - Internal methods
- (void)_downloadResumableCreateSessionWithUrl:(NSURL *)url withScopedPath:(NSString *)scopedPath withUUID:(NSString *)uuid withOptions:(NSDictionary *)options withResumeData:(NSData * _Nullable)resumeData withResolver:(UMPromiseResolveBlock)resolve withRejecter:(UMPromiseRejectBlock)reject
{
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
sessionConfiguration.URLCache = nil;
__weak typeof(self) weakSelf = self;
EXDownloadDelegateOnWriteCallback onWrite = ^(NSURLSessionDownloadTask *task, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) {
__strong EXFileSystem *strongSelf = weakSelf;
if (strongSelf && bytesWritten > 0) {
[strongSelf sendEventWithName:EXDownloadProgressEventName
body:@{@"uuid":uuid,
@"data":@{
@"totalBytesWritten": @(totalBytesWritten),
@"totalBytesExpectedToWrite": @(totalBytesExpectedToWrite),
},
}];
}
};
EXDownloadDelegateOnDownloadCallback onDownload = ^(NSURLSessionDownloadTask *task, NSURL *location) {
NSURL *scopedLocation = [NSURL fileURLWithPath:scopedPath];
NSData *locationData = [NSData dataWithContentsOfURL:location];
[locationData writeToFile:scopedPath atomically:YES];
NSData *data = [NSData dataWithContentsOfURL:scopedLocation];
if (!data) {
reject(@"E_UNABLE_TO_SAVE",
@"Unable to save file to local URI",
nil);
return;
}
NSMutableDictionary *result = [NSMutableDictionary dictionary];
result[@"uri"] = scopedLocation.absoluteString;
result[@"complete"] = @(YES);
if (options[@"md5"]) {
result[@"md5"] = [data md5String];
}
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response;
result[@"status"] = @([httpResponse statusCode]);
result[@"headers"] = [httpResponse allHeaderFields];
__strong EXFileSystem *strongSelf = weakSelf;
if (strongSelf) {
[strongSelf.downloadObjects removeObjectForKey:uuid];
}
resolve(result);
};
EXDownloadDelegateOnErrorCallback onError = ^(NSError *error) {
//"cancelled" description when paused. Don't throw.
if ([error.localizedDescription isEqualToString:@"cancelled"]) {
__strong EXFileSystem *strongSelf = weakSelf;
if (strongSelf) {
[strongSelf.downloadObjects removeObjectForKey:uuid];
}
resolve([NSNull null]);
} else {
reject(@"E_UNABLE_TO_DOWNLOAD",
[NSString stringWithFormat:@"Unable to download from: %@", url.absoluteString],
error);
}
};
EXDownloadDelegate *downloadDelegate = [[EXDownloadDelegate alloc] initWithId:uuid
onWrite:onWrite
onDownload:onDownload
onError:onError];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:downloadDelegate
delegateQueue:[NSOperationQueue mainQueue]];
EXDownloadResumable *downloadResumable = [[EXDownloadResumable alloc] initWithId:uuid
withSession:session
withDelegate:downloadDelegate];
self.downloadObjects[downloadResumable.uuid] = downloadResumable;
NSURLSessionDownloadTask *downloadTask;
if (resumeData) {
downloadTask = [session downloadTaskWithResumeData:resumeData];
} else {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
if (options[@"headers"]) {
NSDictionary *headerDict = (NSDictionary *) [options objectForKey:@"headers"];
for (NSString *key in headerDict) {
NSString *value = (NSString *) [headerDict objectForKey:key];
[request addValue:value forHTTPHeaderField:key];
}
}
downloadTask = [session downloadTaskWithRequest:request];
}
[downloadTask resume];
}
- (UMFileSystemPermissionFlags)_permissionsForPath:(NSString *)path
{
return [[_moduleRegistry getModuleImplementingProtocol:@protocol(UMFilePermissionModuleInterface)] getPathPermissions:(NSString *)path];
}
- (void)sendEventWithName:(NSString *)eventName body:(id)body
{
if (_eventEmitter != nil) {
[_eventEmitter sendEventWithName:eventName body:body];
}
}
- (NSDictionary *)documentFileSystemAttributes {
return [[NSFileManager defaultManager] attributesOfFileSystemForPath:_documentDirectory error:nil];
}
#pragma mark - Public utils
- (UMFileSystemPermissionFlags)permissionsForURI:(NSURL *)uri
{
NSArray *validSchemas = @[
@"assets-library",
@"http",
@"https",
@"ph",
];
if ([validSchemas containsObject:uri.scheme]) {
return UMFileSystemPermissionRead;
}
if ([uri.scheme isEqualToString:@"file"]) {
return [self _permissionsForPath:uri.path];
}
return UMFileSystemPermissionNone;
}
- (BOOL)checkIfFileDirExists:(NSString *)path
{
NSString *dir = [path stringByDeletingLastPathComponent];
return [[NSFileManager defaultManager] fileExistsAtPath:dir];
}
#pragma mark - Class methods
- (BOOL)ensureDirExistsWithPath:(NSString *)path
{
return [EXFileSystem ensureDirExistsWithPath:path];
}
+ (BOOL)ensureDirExistsWithPath:(NSString *)path
{
BOOL isDir = NO;
NSError *error;
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir];
if (!(exists && isDir)) {
[[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error];
if (error) {
return NO;
}
}
return YES;
}
- (NSString *)generatePathInDirectory:(NSString *)directory withExtension:(NSString *)extension
{
return [EXFileSystem generatePathInDirectory:directory withExtension:extension];
}
+ (NSString *)generatePathInDirectory:(NSString *)directory withExtension:(NSString *)extension
{
NSString *fileName = [[[NSUUID UUID] UUIDString] stringByAppendingString:extension];
[EXFileSystem ensureDirExistsWithPath:directory];
return [directory stringByAppendingPathComponent:fileName];
}
- (NSNumber *)totalDiskCapacity {
NSDictionary *storage = [self documentFileSystemAttributes];
if (storage) {
NSNumber *fileSystemSizeInBytes = storage[NSFileSystemSize];
return fileSystemSizeInBytes;
}
return nil;
}
- (NSNumber *)freeDiskStorage {
NSDictionary *storage = [self documentFileSystemAttributes];
if (storage) {
NSNumber *freeFileSystemSizeInBytes = storage[NSFileSystemFreeSize];
return freeFileSystemSizeInBytes;
}
return nil;
}
@end