This commit is contained in:
Yamozha
2021-04-02 02:24:13 +03:00
parent c23950b545
commit 7256d79e2c
31493 changed files with 3036630 additions and 0 deletions

24
node_modules/expo-file-system/ios/EXFileSystem.podspec generated vendored Normal file
View File

@ -0,0 +1,24 @@
require 'json'
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
Pod::Spec.new do |s|
s.name = 'EXFileSystem'
s.version = package['version']
s.summary = package['description']
s.description = package['description']
s.license = package['license']
s.author = package['author']
s.homepage = package['homepage']
s.platform = :ios, '10.0'
s.source = { :git => 'https://github.com/expo/expo.git' }
s.source_files = 'EXFileSystem/**/*.{h,m}'
s.preserve_paths = 'EXFileSystem/**/*.{h,m}'
s.requires_arc = true
s.dependency 'UMCore'
s.dependency 'UMFileSystemInterface'
end

View File

@ -0,0 +1,17 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <UMFileSystemInterface/UMFileSystemInterface.h>
#import <UMFileSystemInterface/UMFilePermissionModuleInterface.h>
#import <UMCore/UMInternalModule.h>
#import <UMCore/UMModuleRegistryConsumer.h>
NS_ASSUME_NONNULL_BEGIN
@interface EXFilePermissionModule : NSObject <UMInternalModule, UMFilePermissionModuleInterface, UMModuleRegistryConsumer>
- (UMFileSystemPermissionFlags)getPathPermissions:(NSString *)path;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,68 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <EXFileSystem/EXFilePermissionModule.h>
#import <UMFileSystemInterface/UMFileSystemInterface.h>
@interface EXFilePermissionModule ()
@property (nonatomic, weak) UMModuleRegistry *moduleRegistry;
@end
@implementation EXFilePermissionModule
UM_REGISTER_MODULE();
+ (const NSArray<Protocol *> *)exportedInterfaces
{
return @[@protocol(UMFilePermissionModuleInterface)];
}
- (UMFileSystemPermissionFlags)getPathPermissions:(NSString *)path
{
UMFileSystemPermissionFlags permissionsForInternalDirectories = [self getInternalPathPermissions:path];
if (permissionsForInternalDirectories != UMFileSystemPermissionNone) {
return permissionsForInternalDirectories;
} else {
return [self getExternalPathPermissions:path];
}
}
- (UMFileSystemPermissionFlags)getInternalPathPermissions:(NSString *)path
{
id<UMFileSystemInterface> fileSystem = [_moduleRegistry getModuleImplementingProtocol:@protocol(UMFileSystemInterface)];
NSArray<NSString *> *scopedDirs = @[fileSystem.cachesDirectory, fileSystem.documentDirectory];
NSString *standardizedPath = [path stringByStandardizingPath];
for (NSString *scopedDirectory in scopedDirs) {
if ([standardizedPath hasPrefix:[scopedDirectory stringByAppendingString:@"/"]] ||
[standardizedPath isEqualToString:scopedDirectory]) {
return UMFileSystemPermissionRead | UMFileSystemPermissionWrite;
}
}
NSString *bundleDirectory = fileSystem.bundleDirectory;
if (bundleDirectory != nil && [path hasPrefix:[bundleDirectory stringByAppendingString:@"/"]]) {
return UMFileSystemPermissionRead;
}
return UMFileSystemPermissionNone;
}
- (UMFileSystemPermissionFlags)getExternalPathPermissions:(NSString *)path
{
UMFileSystemPermissionFlags filePermissions = UMFileSystemPermissionNone;
if ([[NSFileManager defaultManager] isReadableFileAtPath:path]) {
filePermissions |= UMFileSystemPermissionRead;
}
if ([[NSFileManager defaultManager] isWritableFileAtPath:path]) {
filePermissions |= UMFileSystemPermissionWrite;
}
return filePermissions;
}
- (void)setModuleRegistry:(UMModuleRegistry *)moduleRegistry {
_moduleRegistry = moduleRegistry;
}
@end

View File

@ -0,0 +1,43 @@
// Copyright 2016-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <UMCore/UMExportedModule.h>
#import <UMCore/UMModuleRegistryConsumer.h>
#import <UMCore/UMEventEmitter.h>
#import <UMFileSystemInterface/UMFileSystemInterface.h>
@interface EXFileSystem : UMExportedModule <UMEventEmitter, UMModuleRegistryConsumer, UMFileSystemInterface>
@property (nonatomic, readonly) NSString *documentDirectory;
@property (nonatomic, readonly) NSString *cachesDirectory;
@property (nonatomic, readonly) NSString *bundleDirectory;
- (instancetype)initWithDocumentDirectory:(NSString *)documentDirectory cachesDirectory:(NSString *)cachesDirectory bundleDirectory:(NSString *)bundleDirectory;
- (UMFileSystemPermissionFlags)permissionsForURI:(NSURL *)uri;
- (BOOL)ensureDirExistsWithPath:(NSString *)path;
- (NSString *)generatePathInDirectory:(NSString *)directory withExtension:(NSString *)extension;
@end
@protocol EXFileSystemHandler
+ (void)getInfoForFile:(NSURL *)fileUri
withOptions:(NSDictionary *)optionxs
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject;
+ (void)copyFrom:(NSURL *)from
to:(NSURL *)to
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject;
@end
@interface NSData (EXFileSystem)
- (NSString *)md5String;
@end

View File

@ -0,0 +1,984 @@
// Copyright 2016-present 650 Industries. All rights reserved.
#import <UMCore/UMModuleRegistry.h>
#import <EXFileSystem/EXFileSystem.h>
#import <CommonCrypto/CommonDigest.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import <EXFileSystem/EXFileSystemLocalFileHandler.h>
#import <EXFileSystem/EXFileSystemAssetLibraryHandler.h>
#import <UMFileSystemInterface/UMFileSystemInterface.h>
#import <UMFileSystemInterface/UMFilePermissionModuleInterface.h>
#import <UMCore/UMEventEmitterService.h>
#import <EXFileSystem/EXResumablesManager.h>
#import <EXFileSystem/EXSessionTaskDispatcher.h>
#import <EXFileSystem/EXSessionDownloadTaskDelegate.h>
#import <EXFileSystem/EXSessionResumableDownloadTaskDelegate.h>
#import <EXFileSystem/EXSessionUploadTaskDelegate.h>
NSString * const EXDownloadProgressEventName = @"expo-file-system.downloadProgress";
typedef NS_ENUM(NSInteger, EXFileSystemSessionType) {
EXFileSystemBackgroundSession = 0,
EXFileSystemForegroundSession = 1,
};
typedef NS_ENUM(NSInteger, EXFileSystemUploadType) {
EXFileSystemInvalidType = -1,
EXFileSystemBinaryContent = 0,
EXFileSystemMultipart = 1,
};
@interface EXFileSystem ()
@property (nonatomic, strong) NSURLSession *backgroundSession;
@property (nonatomic, strong) NSURLSession *foregroundSession;
@property (nonatomic, strong) EXSessionTaskDispatcher *sessionTaskDispatcher;
@property (nonatomic, strong) EXResumablesManager *resumableManager;
@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 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;
_resumableManager = [EXResumablesManager new];
[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)];
_sessionTaskDispatcher = [[EXSessionTaskDispatcher alloc] initWithSessionHandler:[moduleRegistry getSingletonModuleForName:@"SessionHandler"]];
_backgroundSession = [self _createSession:EXFileSystemBackgroundSession delegate:_sessionTaskDispatcher];
_foregroundSession = [self _createSession:EXFileSystemForegroundSession delegate:_sessionTaskDispatcher];
}
- (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 {
}
- (void)dealloc
{
[_sessionTaskDispatcher deactivate];
[_backgroundSession invalidateAndCancel];
[_foregroundSession invalidateAndCancel];
}
- (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];
// no scheme provided in uri, handle as a local path and add 'file://' scheme
if (!uri.scheme) {
uri = [NSURL fileURLWithPath:uriString isDirectory:false];
}
if (!([self permissionsForURI:uri] & UMFileSystemPermissionRead)) {
reject(@"ERR_FILESYSTEM_NO_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(@"ERR_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];
// no scheme provided in uri, handle as a local path and add 'file://' scheme
if (!uri.scheme) {
uri = [NSURL fileURLWithPath:uriString isDirectory:false];
}
if (!([self permissionsForURI:uri] & UMFileSystemPermissionRead)) {
reject(@"ERR_FILESYSTEM_NO_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(@"ERR_FILESYSTEM_CANNOT_READ_FILE",
[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(@"ERR_FILESYSTEM_CANNOT_READ_FILE",
[NSString stringWithFormat:@"File '%@' could not be read.", uri],
error);
}
}
} else {
reject(@"ERR_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(@"ERR_FILESYSTEM_NO_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(@"ERR_FILESYSTEM_UNKNOWN_FILE",
[NSString stringWithFormat:@"No such file or directory '%@'", uri.path],
nil);
}
} else {
reject(@"ERR_FILESYSTEM_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(@"ERR_FILESYSTEM_CANNOT_WRITE_TO_FILE",
[NSString stringWithFormat:@"File '%@' could not be written.", uri],
error);
}
}
} else {
reject(@"ERR_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(@"ERR_FILESYSTEM_NO_PERMISSIONS",
[NSString stringWithFormat:@"Location '%@' isn't deletable.", uri],
nil);
return;
}
if ([uri.scheme isEqualToString:@"file"]) {
NSString *path = uri.path;
if ([self _checkIfFileExists:path]) {
NSError *error;
if ([[NSFileManager defaultManager] removeItemAtPath:path error:&error]) {
resolve([NSNull null]);
} else {
reject(@"ERR_FILESYSTEM_CANNOT_DELETE_FILE",
[NSString stringWithFormat:@"File '%@' could not be deleted.", uri],
error);
}
} else {
if (options[@"idempotent"]) {
resolve([NSNull null]);
} else {
reject(@"ERR_FILESYSTEM_CANNOT_FIND_FILE",
[NSString stringWithFormat:@"File '%@' could not be deleted because it could not be found.", uri],
nil);
}
}
} else {
reject(@"ERR_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(@"ERR_FILESYSTEM_MISSING_PARAMETER", @"Need a `from` location.", nil);
return;
}
if (!([self permissionsForURI:[from URLByAppendingPathComponent:@".."]] & UMFileSystemPermissionWrite)) {
reject(@"ERR_FILESYSTEM_NO_PERMISSIONS",
[NSString stringWithFormat:@"Location '%@' isn't movable.", from],
nil);
return;
}
NSURL *to = [NSURL URLWithString:options[@"to"]];
if (!to) {
reject(@"ERR_FILESYSTEM_MISSING_PARAMETER", @"Need a `to` location.", nil);
return;
}
if (!([self permissionsForURI:to] & UMFileSystemPermissionWrite)) {
reject(@"ERR_FILESYSTEM_NO_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 ([self _checkIfFileExists:toPath]) {
if (![[NSFileManager defaultManager] removeItemAtPath:toPath error:&error]) {
reject(@"ERR_FILESYSTEM_CANNOT_MOVE_FILE",
[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(@"ERR_FILESYSTEM_CANNOT_MOVE_FILE",
[NSString stringWithFormat:@"File '%@' could not be moved to '%@'.", from, to],
error);
}
} else {
reject(@"ERR_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(@"ERR_FILESYSTEM_MISSING_PARAMETER", @"Need a `from` location.", nil);
return;
}
if (!([self permissionsForURI:from] & UMFileSystemPermissionRead)) {
reject(@"ERR_FILESYSTEM_NO_PERMISSIONS",
[NSString stringWithFormat:@"File '%@' isn't readable.", from],
nil);
return;
}
NSURL *to = [NSURL URLWithString:options[@"to"]];
if (!to) {
reject(@"ERR_FILESYSTEM_MISSING_PARAMETER", @"Need a `to` location.", nil);
return;
}
if (!([self permissionsForURI:to] & UMFileSystemPermissionWrite)) {
reject(@"ERR_FILESYSTEM_NO_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(@"ERR_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(@"ERR_FILESYSTEM_NO_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(@"ERR_FILESYSTEM_CANNOT_CREATE_DIRECTORY",
[NSString stringWithFormat:@"Directory '%@' could not be created.", uri],
error);
}
} else {
reject(@"ERR_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(@"ERR_FILESYSTEM_NO_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(@"ERR_FILESYSTEM_CANNOT_READ_DIRECTORY",
[NSString stringWithFormat:@"Directory '%@' could not be read.", uri],
error);
}
} else {
reject(@"ERR_FILESYSTEM_INVALID_URI",
[NSString stringWithFormat:@"Unsupported URI scheme for '%@'", uri],
nil);
}
}
UM_EXPORT_METHOD_AS(downloadAsync,
downloadAsyncWithUrl:(NSString *)urlString
localURI:(NSString *)localUriString
options:(NSDictionary *)options
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
NSURL *url = [NSURL URLWithString:urlString];
NSURL *localUri = [NSURL URLWithString:localUriString];
if (!([self checkIfFileDirExists:localUri.path])) {
reject(@"ERR_FILESYSTEM_WRONG_DESTINATION",
[NSString stringWithFormat:@"Directory for '%@' doesn't exist. Please make sure directory '%@' exists before calling downloadAsync.", localUriString, [localUri.path stringByDeletingLastPathComponent]],
nil);
return;
}
if (!([self permissionsForURI:localUri] & UMFileSystemPermissionWrite)) {
reject(@"ERR_FILESYSTEM_NO_PERMISSIONS",
[NSString stringWithFormat:@"File '%@' isn't writable.", localUri],
nil);
return;
}
if (![self _checkHeadersDictionary:options[@"headers"]]) {
reject(@"ERR_FILESYSTEM_INVALID_HEADERS",
@"Invalid headers dictionary. Keys and values should be strings.",
nil);
return;
}
NSURLSession *session = [self _sessionForType:[options[@"sessionType"] intValue]];
if (!session) {
reject(@"ERR_FILESYSTEM_INVALID_SESSION_TYPE",
[NSString stringWithFormat:@"Invalid session type: '%@'", options[@"sessionType"]],
nil);
return;
}
NSURLRequest *request = [self _createRequest:url headers:options[@"headers"]];
NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request];
EXSessionTaskDelegate *taskDelegate = [[EXSessionDownloadTaskDelegate alloc] initWithResolve:resolve
reject:reject
localUrl:localUri
shouldCalculateMd5:[options[@"md5"] boolValue]];
[_sessionTaskDispatcher registerTaskDelegate:taskDelegate forTask:task];
[task resume];
}
UM_EXPORT_METHOD_AS(uploadAsync,
uploadAsync:(NSString *)urlString
localURI:(NSString *)fileUriString
options:(NSDictionary *)options
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
NSURL *fileUri = [NSURL URLWithString:fileUriString];
NSString *httpMethod = options[@"httpMethod"];
EXFileSystemUploadType type = [self _getUploadTypeFrom:options[@"uploadType"]];
if (![fileUri.scheme isEqualToString:@"file"]) {
reject(@"ERR_FILESYSTEM_NO_PERMISSIONS",
[NSString stringWithFormat:@"Cannot upload file '%@'. Only 'file://' URI are supported.", fileUri],
nil);
return;
}
if (!([self _checkIfFileExists:fileUri.path])) {
reject(@"ERR_FILE_NOT_EXISTS",
[NSString stringWithFormat:@"File '%@' does not exist.", fileUri],
nil);
return;
}
if (![self _checkHeadersDictionary:options[@"headers"]]) {
reject(@"ERR_FILESYSTEM_INVALID_HEADERS_DICTIONARY",
@"Invalid headers dictionary. Keys and values should be strings.",
nil);
return;
}
if (!httpMethod) {
reject(@"ERR_FILESYSTEM_MISSING_HTTP_METHOD", @"Missing HTTP method.", nil);
return;
}
NSMutableURLRequest *request = [self _createRequest:[NSURL URLWithString:urlString] headers:options[@"headers"]];
[request setHTTPMethod:httpMethod];
NSURLSession *session = [self _sessionForType:[options[@"sessionType"] intValue]];
if (!session) {
reject(@"ERR_FILESYSTEM_INVALID_SESSION_TYPE",
[NSString stringWithFormat:@"Invalid session type: '%@'", options[@"sessionType"]],
nil);
return;
}
NSURLSessionUploadTask *task;
if (type == EXFileSystemBinaryContent) {
task = [session uploadTaskWithRequest:request fromFile:fileUri];
} else if (type == EXFileSystemMultipart) {
NSString *boundaryString = [[NSUUID UUID] UUIDString];
[request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundaryString] forHTTPHeaderField:@"Content-Type"];
NSData *httpBody = [self _createBodyWithBoundary:boundaryString
fileUri:fileUri
parameters:options[@"parameters"]
fieldName:options[@"fieldName"]
mimeType:options[@"mimeType"]];
[request setHTTPBody:httpBody];
task = [session uploadTaskWithStreamedRequest:request];
} else {
reject(@"ERR_FILESYSTEM_INVALID_UPLOAD_TYPE",
[NSString stringWithFormat:@"Invalid upload type: '%@'.", options[@"uploadType"]],
nil);
return;
}
EXSessionTaskDelegate *taskDelegate = [[EXSessionUploadTaskDelegate alloc] initWithResolve:resolve reject:reject];
[_sessionTaskDispatcher registerTaskDelegate:taskDelegate forTask:task];
[task resume];
}
UM_EXPORT_METHOD_AS(downloadResumableStartAsync,
downloadResumableStartAsyncWithUrl:(NSString *)urlString
fileURI:(NSString *)fileUri
uuid:(NSString *)uuid
options:(NSDictionary *)options
resumeData:(NSString *)data
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
NSURL *url = [NSURL URLWithString:urlString];
NSURL *localUrl = [NSURL URLWithString:fileUri];
if (!([self checkIfFileDirExists:localUrl.path])) {
reject(@"ERR_FILESYSTEM_WRONG_DESTINATION",
[NSString stringWithFormat:@"Directory for '%@' doesn't exist. Please make sure directory '%@' exists before calling downloadAsync.", fileUri, [localUrl.path stringByDeletingLastPathComponent]],
nil);
return;
}
if (![localUrl.scheme isEqualToString:@"file"]) {
reject(@"ERR_FILESYSTEM_NO_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(@"ERR_FILESYSTEM_NO_PERMISSIONS",
[NSString stringWithFormat:@"File '%@' isn't writable.", fileUri],
nil);
return;
}
if (![self _checkHeadersDictionary:options[@"headers"]]) {
reject(@"ERR_FILESYSTEM_INVALID_HEADERS_DICTIONARY",
@"Invalid headers dictionary. Keys and values should be strings.",
nil);
return;
}
NSData *resumeData = data ? [[NSData alloc] initWithBase64EncodedString:data options:0] : nil;
[self _downloadResumableCreateSessionWithUrl:url
fileUrl:localUrl
uuid:uuid
optins:options
resumeData:resumeData
resolve:resolve
reject:reject];
}
UM_EXPORT_METHOD_AS(downloadResumablePauseAsync,
downloadResumablePauseAsyncWithUUID:(NSString *)uuid
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
NSURLSessionDownloadTask *task = [_resumableManager taskForId:uuid];
if (!task) {
reject(@"ERR_FILESYSTEM_CANNOT_FIND_TASK",
[NSString stringWithFormat:@"There is no download object with UUID: %@", uuid],
nil);
return;
}
UM_WEAKIFY(self);
[task cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
UM_ENSURE_STRONGIFY(self);
resolve(@{ @"resumeData": UMNullIfNil([resumeData base64EncodedStringWithOptions:0]) });
}];
}
UM_EXPORT_METHOD_AS(getFreeDiskStorageAsync, getFreeDiskStorageAsyncWithResolver:(UMPromiseResolveBlock)resolve rejecter:(UMPromiseRejectBlock)reject)
{
if(![self freeDiskStorage]) {
reject(@"ERR_FILESYSTEM_CANNOT_DETERMINE_DISK_CAPACITY", @"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_CANNOT_DETERMINE_DISK_CAPACITY", @"Unable to determine total disk capacity", nil);
} else {
resolve([self totalDiskCapacity]);
}
}
#pragma mark - Internal methods
- (EXFileSystemUploadType)_getUploadTypeFrom:(NSNumber * _Nullable)type
{
switch ([type intValue]) {
case EXFileSystemBinaryContent:
case EXFileSystemMultipart:
return [type intValue];
}
return EXFileSystemInvalidType;
}
// Borrowed from http://stackoverflow.com/questions/2439020/wheres-the-iphone-mime-type-database
- (NSString *)_guessMIMETypeFromPath:(NSString *)path
{
CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[path pathExtension], NULL);
CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType);
CFRelease(UTI);
if (!MIMEType) {
return @"application/octet-stream";
}
return (__bridge NSString *)(MIMEType);
}
- (NSData *)_createBodyWithBoundary:(NSString *)boundary
fileUri:(NSURL *)fileUri
parameters:(NSDictionary * _Nullable)parameters
fieldName:(NSString * _Nullable)fieldName
mimeType:(NSString * _Nullable)mimetype
{
NSMutableData *body = [NSMutableData data];
NSData *data = [NSData dataWithContentsOfURL:fileUri];
NSString *filename = [[fileUri path] lastPathComponent];
if (!mimetype) {
mimetype = [self _guessMIMETypeFromPath:[fileUri path]];
}
[parameters enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) {
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", parameterKey] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"%@\r\n", parameterValue] dataUsingEncoding:NSUTF8StringEncoding]];
}];
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", fieldName ?: filename, filename] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", mimetype] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:data];
[body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
return body;
}
- (NSMutableURLRequest *)_createRequest:(NSURL *)url headers:(NSDictionary * _Nullable)headers
{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
if (headers != nil) {
for (NSString *headerKey in headers) {
[request setValue:[headers valueForKey:headerKey] forHTTPHeaderField:headerKey];
}
}
return request;
}
- (NSURLSession *)_sessionForType:(EXFileSystemSessionType)type
{
switch (type) {
case EXFileSystemBackgroundSession:
return _backgroundSession;
case EXFileSystemForegroundSession:
return _foregroundSession;
}
return nil;
}
- (BOOL)_checkHeadersDictionary:(NSDictionary * _Nullable)headers
{
for (id key in [headers allKeys]) {
if (![key isKindOfClass:[NSString class]] || ![headers[key] isKindOfClass:[NSString class]]) {
return false;
}
}
return true;
}
- (NSURLSession *)_createSession:(EXFileSystemSessionType)type delegate:(id<NSURLSessionDelegate>)delegate
{
NSURLSessionConfiguration *sessionConfiguration;
if (type == EXFileSystemForegroundSession) {
sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
} else {
sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[[NSUUID UUID] UUIDString]];
}
sessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
sessionConfiguration.URLCache = nil;
return [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:delegate
delegateQueue:nil];
}
- (BOOL)_checkIfFileExists:(NSString *)path
{
return [[NSFileManager defaultManager] fileExistsAtPath:path];
}
- (void)_downloadResumableCreateSessionWithUrl:(NSURL *)url
fileUrl:(NSURL *)fileUrl
uuid:(NSString *)uuid
optins:(NSDictionary *)options
resumeData:(NSData * _Nullable)resumeData
resolve:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject
{
UM_WEAKIFY(self);
EXDownloadDelegateOnWriteCallback onWrite = ^(NSURLSessionDownloadTask *task, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) {
UM_ENSURE_STRONGIFY(self);
[self sendEventWithName:EXDownloadProgressEventName
body:@{
@"uuid": uuid,
@"data": @{
@"totalBytesWritten": @(totalBytesWritten),
@"totalBytesExpectedToWrite": @(totalBytesExpectedToWrite),
},
}];
};
NSURLSessionDownloadTask *downloadTask;
NSURLSession *session = [self _sessionForType:[options[@"sessionType"] intValue]];
if (!session) {
reject(@"ERR_FILESYSTEM_INVALID_SESSION_TYPE",
[NSString stringWithFormat:@"Invalid session type: '%@'", options[@"sessionType"]],
nil);
return;
}
if (resumeData) {
downloadTask = [session downloadTaskWithResumeData:resumeData];
} else {
NSURLRequest *request = [self _createRequest:url headers:options[@"headers"]];
downloadTask = [session downloadTaskWithRequest:request];
}
EXSessionTaskDelegate *taskDelegate = [[EXSessionResumableDownloadTaskDelegate alloc] initWithResolve:resolve
reject:reject
localUrl:fileUrl
shouldCalculateMd5:[options[@"md5"] boolValue]
onWriteCallback:onWrite
resumableManager:_resumableManager
uuid:uuid];
[_sessionTaskDispatcher registerTaskDelegate:taskDelegate forTask:downloadTask];
[_resumableManager registerTask:downloadTask uuid:uuid];
[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 [self _checkIfFileExists: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

View File

@ -0,0 +1,6 @@
#import <EXFileSystem/EXFileSystem.h>
@interface EXFileSystemAssetLibraryHandler : NSObject <EXFileSystemHandler>
@end

View File

@ -0,0 +1,114 @@
#import <EXFileSystem/EXFileSystemAssetLibraryHandler.h>
#import <Photos/Photos.h>
@implementation EXFileSystemAssetLibraryHandler
+ (void)getInfoForFile:(NSURL *)fileUri
withOptions:(NSDictionary *)options
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject
{
NSError *error;
PHFetchResult<PHAsset *> *fetchResult = [self fetchResultForUri:fileUri error:&error];
if (error) {
reject(@"E_UNSUPPORTED_ARG", error.description, error);
return;
}
if (fetchResult.count > 0) {
PHAsset *asset = fetchResult[0];
NSMutableDictionary *result = [NSMutableDictionary dictionary];
result[@"exists"] = @(YES);
result[@"isDirectory"] = @(NO);
result[@"uri"] = fileUri;
result[@"modificationTime"] = @(asset.modificationDate.timeIntervalSince1970);
if (options[@"md5"] || options[@"size"]) {
[[PHImageManager defaultManager] requestImageDataForAsset:asset options:nil resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
result[@"size"] = @(imageData.length);
if (options[@"md5"]) {
result[@"md5"] = [imageData md5String];
}
resolve(result);
}];
} else {
resolve(result);
}
} else {
resolve(@{@"exists": @(NO), @"isDirectory": @(NO)});
}
}
+ (void)copyFrom:(NSURL *)from
to:(NSURL *)to
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject
{
NSString *toPath = [to.path stringByStandardizingPath];
// NOTE: The destination-delete and the copy should happen atomically, but we hope for the best for now
NSError *error;
if ([[NSFileManager defaultManager] fileExistsAtPath:toPath]) {
if (![[NSFileManager defaultManager] removeItemAtPath:toPath error:&error]) {
reject(@"E_FILE_NOT_COPIED",
[NSString stringWithFormat:@"File '%@' could not be copied to '%@' because a file already exists at "
"the destination and could not be deleted.", from, to],
error);
return;
}
}
PHFetchResult<PHAsset *> *fetchResult = [self fetchResultForUri:from error:&error];
if (error) {
reject(@"E_UNSUPPORTED_ARG", error.description, error);
return;
}
if (fetchResult.count > 0) {
PHAsset *asset = fetchResult[0];
[[PHImageManager defaultManager] requestImageDataForAsset:asset options:nil resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
if ([imageData writeToFile:toPath atomically:YES]) {
resolve(nil);
} else {
reject(@"E_FILE_NOT_COPIED",
[NSString stringWithFormat:@"File '%@' could not be copied to '%@'.", from, to],
error);
}
}];
} else {
reject(@"E_FILE_NOT_COPIED",
[NSString stringWithFormat:@"File '%@' could not be found.", from],
error);
}
}
// adapted from RCTImageLoader.m
+ (PHFetchResult<PHAsset *> *)fetchResultForUri:(NSURL *)url error:(NSError **)error
{
if ([url.scheme caseInsensitiveCompare:@"ph"] == NSOrderedSame) {
// Fetch assets using PHAsset localIdentifier (recommended)
NSString *const localIdentifier = [url.absoluteString substringFromIndex:@"ph://".length];
return [PHAsset fetchAssetsWithLocalIdentifiers:@[localIdentifier] options:nil];
} else if ([url.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame) {
#if TARGET_OS_MACCATALYST
static BOOL hasWarned = NO;
if (!hasWarned) {
NSLog(@"assets-library:// URLs have been deprecated and cannot be accessed in macOS Catalyst. Returning nil (future warnings will be suppressed).");
hasWarned = YES;
}
return nil;
#else
// This is the older, deprecated way of fetching assets from assets-library
// using the "assets-library://" protocol
return [PHAsset fetchAssetsWithALAssetURLs:@[url] options:nil];
#endif
}
NSString *description = [NSString stringWithFormat:@"Invalid URL provided, expected scheme to be either 'ph' or 'assets-library', was '%@'.", url.scheme];
*error = [[NSError alloc] initWithDomain:NSURLErrorDomain
code:NSURLErrorUnsupportedURL
userInfo:@{NSLocalizedDescriptionKey: description}];
return nil;
}
@end

View File

@ -0,0 +1,6 @@
#import <EXFileSystem/EXFileSystem.h>
@interface EXFileSystemLocalFileHandler : NSObject <EXFileSystemHandler>
@end

View File

@ -0,0 +1,78 @@
#import <EXFileSystem/EXFileSystemLocalFileHandler.h>
@implementation EXFileSystemLocalFileHandler
+ (void)getInfoForFile:(NSURL *)fileUri
withOptions:(NSDictionary *)options
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject
{
NSString *path = fileUri.path;
BOOL isDirectory;
if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory]) {
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
NSMutableDictionary *result = [NSMutableDictionary dictionary];
result[@"exists"] = @(YES);
result[@"isDirectory"] = @(isDirectory);
result[@"uri"] = [NSURL fileURLWithPath:path].absoluteString;
if (options[@"md5"]) {
result[@"md5"] = [[NSData dataWithContentsOfFile:path] md5String];
}
result[@"size"] = @([EXFileSystemLocalFileHandler getFileSize:path attributes:attributes]);
result[@"modificationTime"] = @(attributes.fileModificationDate.timeIntervalSince1970);
resolve(result);
} else {
resolve(@{@"exists": @(NO), @"isDirectory": @(NO)});
}
}
+ (unsigned long long)getFileSize:(NSString *)path attributes:(NSDictionary<NSFileAttributeKey, id> *)attributes
{
if (attributes.fileType != NSFileTypeDirectory) {
return attributes.fileSize;
}
// The path is pointing to the folder
NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
NSEnumerator *contentsEnumurator = [contents objectEnumerator];
NSString *file;
unsigned long long folderSize = 0;
while (file = [contentsEnumurator nextObject]) {
NSString *filePath = [path stringByAppendingPathComponent:file];
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
folderSize += [EXFileSystemLocalFileHandler getFileSize:filePath attributes:fileAttributes];
}
return folderSize;
}
+ (void)copyFrom:(NSURL *)from
to:(NSURL *)to
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject
{
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_COPIED",
[NSString stringWithFormat:@"File '%@' could not be copied to '%@' because a file already exists at "
"the destination and could not be deleted.", from, to],
error);
return;
}
}
if ([[NSFileManager defaultManager] copyItemAtPath:fromPath toPath:toPath error:&error]) {
resolve(nil);
} else {
reject(@"E_FILE_NOT_COPIED",
[NSString stringWithFormat:@"File '%@' could not be copied to '%@'.", from, to],
error);
}
}
@end

View File

@ -0,0 +1,17 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface EXResumablesManager : NSObject
- (NSURLSessionDownloadTask * _Nullable)taskForId:(NSString *)uuid;
- (void)registerTask:(NSURLSessionDownloadTask *)task uuid:(NSString *)uuid;
- (void)unregisterTask:(NSString *)uuid;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,36 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <EXFileSystem/EXResumablesManager.h>
@interface EXResumablesManager ()
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSURLSessionDownloadTask *> *resumableDownloads;
@end
@implementation EXResumablesManager
- (instancetype)init
{
if (self = [super init]) {
_resumableDownloads = [NSMutableDictionary dictionary];
}
return self;
}
- (void)registerTask:(NSURLSessionDownloadTask *)task uuid:(NSString *)uuid
{
_resumableDownloads[uuid] = task;
}
- (NSURLSessionDownloadTask * _Nullable)taskForId:(NSString *)uuid
{
return _resumableDownloads[uuid];
}
- (void)unregisterTask:(NSString *)uuid
{
[_resumableDownloads removeObjectForKey:uuid];
}
@end

View File

@ -0,0 +1,13 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <EXFileSystem/EXSessionTaskDelegate.h>
@interface EXSessionDownloadTaskDelegate : EXSessionTaskDelegate
- (instancetype)initWithResolve:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject
localUrl:(NSURL *)localUrl
shouldCalculateMd5:(BOOL)shouldCalculateMd5;
@end

View File

@ -0,0 +1,64 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <EXFileSystem/EXSessionDownloadTaskDelegate.h>
#import <EXFileSystem/NSData+EXFileSystem.h>
@interface EXSessionDownloadTaskDelegate ()
@property (strong, nonatomic) NSURL *localUrl;
@property (nonatomic) BOOL shouldCalculateMd5;
@end
@implementation EXSessionDownloadTaskDelegate
- (instancetype)initWithResolve:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject
localUrl:(NSURL *)localUrl
shouldCalculateMd5:(BOOL)shouldCalculateMd5
{
if (self = [super initWithResolve:resolve reject:reject])
{
_localUrl = localUrl;
_shouldCalculateMd5 = shouldCalculateMd5;
}
return self;
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:_localUrl.path]) {
[fileManager removeItemAtURL:_localUrl error:&error];
if (error) {
self.reject(@"ERR_FILESYSTEM_CANNOT_REMOVE",
[NSString stringWithFormat:@"Unable to remove file from local URI: '%@'", error.description],
error);
return;
}
}
[fileManager moveItemAtURL:location toURL:_localUrl error:&error];
if (error) {
self.reject(@"ERR_FILESYSTEM_CANNOT_SAVE",
[NSString stringWithFormat:@"Unable to save file to local URI: '%@'", error.description],
error);
return;
}
self.resolve([self parseServerResponse:downloadTask.response]);
}
- (NSDictionary *)parseServerResponse:(NSURLResponse *)response
{
NSMutableDictionary *result = [[super parseServerResponse:response] mutableCopy];
result[@"uri"] = _localUrl.absoluteString;
if (_shouldCalculateMd5) {
NSData *data = [NSData dataWithContentsOfURL:_localUrl];
result[@"md5"] = [data md5String];
}
return result;
}
@end

View File

@ -0,0 +1,19 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <UMCore/UMSingletonModule.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@protocol EXSessionHandler
- (void)invokeCompletionHandlerForSessionIdentifier:(NSString *)identifier;
@end
@interface EXSessionHandler : UMSingletonModule <UIApplicationDelegate, EXSessionHandler>
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,49 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <EXFileSystem/EXSessionHandler.h>
#import <UMCore/UMDefines.h>
@interface EXSessionHandler ()
@property (nonatomic, strong) NSMutableDictionary<NSString *, void (^)(void)> *completionHandlers;
@end
@implementation EXSessionHandler
UM_REGISTER_SINGLETON_MODULE(SessionHandler);
- (instancetype)init
{
if (self = [super init]) {
_completionHandlers = [NSMutableDictionary dictionary];
}
return self;
}
- (void)invokeCompletionHandlerForSessionIdentifier:(NSString *)identifier
{
if (!identifier) {
return;
}
void (^completionHandler)(void) = _completionHandlers[identifier];
if (completionHandler) {
// We need to run completionHandler explicite on the main thread because is's part of UIKit
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler();
});
[_completionHandlers removeObjectForKey:identifier];
}
}
#pragma mark - AppDelegate
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler
{
_completionHandlers[identifier] = completionHandler;
}
@end

View File

@ -0,0 +1,18 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <EXFileSystem/EXSessionDownloadTaskDelegate.h>
#import <EXFileSystem/EXResumablesManager.h>
typedef void (^EXDownloadDelegateOnWriteCallback)(NSURLSessionDownloadTask *task, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite);
@interface EXSessionResumableDownloadTaskDelegate : EXSessionDownloadTaskDelegate
- (instancetype)initWithResolve:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject
localUrl:(NSURL *)localUrl
shouldCalculateMd5:(BOOL)shouldCalculateMd5
onWriteCallback:(EXDownloadDelegateOnWriteCallback)onWriteCallback
resumableManager:(EXResumablesManager *)manager
uuid:(NSString *)uuid;
@end

View File

@ -0,0 +1,60 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <EXFileSystem/EXSessionResumableDownloadTaskDelegate.h>
@interface EXSessionResumableDownloadTaskDelegate ()
@property (strong, nonatomic, readonly) EXDownloadDelegateOnWriteCallback onWriteCallback;
@property (weak, nonatomic) EXResumablesManager *manager;
@property (strong, nonatomic) NSString *uuid;
@end
@implementation EXSessionResumableDownloadTaskDelegate
- (instancetype)initWithResolve:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject
localUrl:(NSURL *)localUrl
shouldCalculateMd5:(BOOL)shouldCalculateMd5
onWriteCallback:(EXDownloadDelegateOnWriteCallback)onWriteCallback
resumableManager:(EXResumablesManager *)manager
uuid:(NSString *)uuid;
{
if (self = [super initWithResolve:resolve
reject:reject
localUrl:localUrl
shouldCalculateMd5:shouldCalculateMd5]) {
_onWriteCallback = onWriteCallback;
_manager = manager;
_uuid = uuid;
}
return self;
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error) {
// The task was paused by us. So, we shouldn't throw.
if (error.code == NSURLErrorCancelled) {
self.resolve([NSNull null]);
} else {
self.reject(@"ERR_FILESYSTEM_CANNOT_DOWNLOAD",
[NSString stringWithFormat:@"Unable to download file: %@", error.description],
error);
}
}
[_manager unregisterTask:_uuid];
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
if (_onWriteCallback && bytesWritten > 0) {
_onWriteCallback(downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}
}
@end

View File

@ -0,0 +1,27 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <UMCore/UMDefines.h>
@interface EXSessionTaskDelegate : NSObject
@property (nonatomic, strong, readonly) UMPromiseResolveBlock resolve;
@property (nonatomic, strong, readonly) UMPromiseRejectBlock reject;
- (instancetype)initWithResolve:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject;
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data;
- (NSDictionary *)parseServerResponse:(NSURLResponse *)response;
@end

View File

@ -0,0 +1,51 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <EXFileSystem/EXSessionTaskDelegate.h>
@implementation EXSessionTaskDelegate
- (instancetype)initWithResolve:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject
{
if (self = [super init]) {
_resolve = resolve;
_reject = reject;
}
return self;
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error) {
self.reject(@"ERR_FILESYSTEM_CANNOT_DOWNLOAD",
[NSString stringWithFormat:@"Unable to download file: %@", error.description],
error);
}
}
- (NSDictionary *)parseServerResponse:(NSURLResponse *)response
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
return @{
@"status": @([httpResponse statusCode]),
@"headers": [httpResponse allHeaderFields],
@"mimeType": UMNullIfNil([httpResponse MIMEType])
};
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
}
@end

View File

@ -0,0 +1,19 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <EXFileSystem/EXSessionTaskDelegate.h>
#import <EXFileSystem/EXSessionHandler.h>
NS_ASSUME_NONNULL_BEGIN
@interface EXSessionTaskDispatcher : NSObject <NSURLSessionDelegate>
- (instancetype)initWithSessionHandler:(id<EXSessionHandler>)sessionHandler;
- (void)registerTaskDelegate:(EXSessionTaskDelegate *)delegate forTask:(NSURLSessionTask *)task;
- (void)deactivate;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,85 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <EXFileSystem/EXSessionTaskDispatcher.h>
#import <EXFileSystem/EXSessionResumableDownloadTaskDelegate.h>
@interface EXSessionTaskDispatcher ()
@property (nonatomic, strong) NSMutableDictionary<NSURLSessionTask *, EXSessionTaskDelegate *> *tasks;
@property (nonatomic) BOOL isActive;
@property (nonatomic, weak) id<EXSessionHandler> sessionHandler;
@end
@implementation EXSessionTaskDispatcher
- (instancetype)initWithSessionHandler:(id<EXSessionHandler>)sessionHandler;
{
if (self = [super init]) {
_tasks = [NSMutableDictionary dictionary];
_isActive = true;
_sessionHandler = sessionHandler;
}
return self;
}
#pragma mark - public methods
- (void)registerTaskDelegate:(EXSessionTaskDelegate *)delegate forTask:(NSURLSessionTask *)task
{
_tasks[task] = delegate;
}
- (void)deactivate
{
_isActive = false;
}
#pragma mark - dispatcher
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
if (_isActive) {
EXSessionTaskDelegate *exTask = _tasks[downloadTask];
if (exTask) {
[exTask URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
[_tasks removeObjectForKey:downloadTask];
}
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (_isActive) {
EXSessionTaskDelegate *exTask = _tasks[task];
if (exTask) {
[exTask URLSession:session task:task didCompleteWithError:error];
[_tasks removeObjectForKey:task];
}
}
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
if (_isActive) {
EXSessionTaskDelegate *exTask = _tasks[downloadTask];
[exTask URLSession:session downloadTask:downloadTask didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite];
}
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
if (_isActive) {
EXSessionTaskDelegate *exTask = _tasks[dataTask];
[exTask URLSession:session dataTask:dataTask didReceiveData:data];
}
}
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
[_sessionHandler invokeCompletionHandlerForSessionIdentifier:session.configuration.identifier];
}
@end

View File

@ -0,0 +1,8 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <EXFileSystem/EXSessionTaskDelegate.h>
@interface EXSessionUploadTaskDelegate : EXSessionTaskDelegate
@end

View File

@ -0,0 +1,52 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <EXFileSystem/EXSessionUploadTaskDelegate.h>
@interface EXSessionUploadTaskDelegate ()
@property (strong, nonatomic) NSMutableData *responseData;
@end
@implementation EXSessionUploadTaskDelegate
- (instancetype)initWithResolve:(UMPromiseResolveBlock)resolve reject:(UMPromiseRejectBlock)reject
{
if (self = [super initWithResolve:resolve reject:reject]) {
_responseData = [NSMutableData new];
}
return self;
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
if (!data.length) {
return;
}
[_responseData appendData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error) {
self.reject(@"ERR_FILESYSTEM_CANNOT_UPLOAD",
[NSString stringWithFormat:@"Unable to upload the file: '%@'", error.description],
error);
return;
}
// We only set EXSessionUploadTaskDelegates as delegates of upload tasks
// so it should be safe to assume that this is what we will receive here.
NSURLSessionUploadTask *uploadTask = (NSURLSessionUploadTask *)task;
self.resolve([self parseServerResponse:uploadTask.response]);
}
- (NSDictionary *)parseServerResponse:(NSURLResponse *)response
{
NSMutableDictionary *result = [[super parseServerResponse:response] mutableCopy];
// TODO: add support for others response types (different encodings, files)
result[@"body"] = UMNullIfNil([[NSString alloc] initWithData:_responseData encoding:NSUTF8StringEncoding]);
return result;
}
@end

View File

@ -0,0 +1,7 @@
// Copyright 2015-present 650 Industries. All rights reserved.
@interface NSData (EXFileSystem)
- (NSString *)md5String;
@end

View File

@ -0,0 +1,19 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <EXFileSystem/NSData+EXFileSystem.h>
#import <CommonCrypto/CommonDigest.h>
@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