react-native-firebase/ios/RNFirebase/RNFirebaseStorage.m

448 lines
19 KiB
Mathematica
Raw Normal View History

2017-03-09 15:26:28 +00:00
#import "RNFirebaseStorage.h"
#import "RNFirebaseEvents.h"
#import <Photos/Photos.h>
@implementation RNFirebaseStorage
RCT_EXPORT_MODULE(RNFirebaseStorage);
// Run on a different thread
- (dispatch_queue_t)methodQueue {
return dispatch_queue_create("com.invertase.firebase.storage", DISPATCH_QUEUE_SERIAL);
}
/**
Reject a promise with a storage exception
@param reject RCTPromiseRejectBlock
@param error NSError
*/
- (void) promiseRejectStorageException:(RCTPromiseRejectBlock) reject error:(NSError *)error {
NSString *code = @"storage/unknown";
NSString *message = [error localizedDescription];
NSDictionary *userInfo = [error userInfo];
NSError *underlyingError = [userInfo objectForKey:NSUnderlyingErrorKey];
NSString *underlyingErrorDescription = [underlyingError localizedDescription];
switch (error.code) {
case FIRStorageErrorCodeUnknown:
if ([underlyingErrorDescription isEqualToString:@"The operation couldnt be completed. Permission denied"]) {
code = @"storage/invalid-device-file-path";
message = @"The specified device file path is invalid or is restricted.";
} else {
code = @"storage/unknown";
message = @"An unknown error has occurred.";
}
break;
case FIRStorageErrorCodeObjectNotFound:
code = @"storage/object-not-found";
message = @"No object exists at the desired reference.";
break;
case FIRStorageErrorCodeBucketNotFound:
code = @"storage/bucket-not-found";
message = @"No bucket is configured for Firebase Storage.";
break;
case FIRStorageErrorCodeProjectNotFound:
code = @"storage/project-not-found";
message = @"No project is configured for Firebase Storage.";
break;
case FIRStorageErrorCodeQuotaExceeded:
code = @"storage/quota-exceeded";
message = @"Quota on your Firebase Storage bucket has been exceeded.";
break;
case FIRStorageErrorCodeUnauthenticated:
code = @"storage/unauthenticated";
message = @"User is unauthenticated. Authenticate and try again.";
break;
case FIRStorageErrorCodeUnauthorized:
code = @"storage/unauthorized";
message = @"User is not authorized to perform the desired action.";
break;
case FIRStorageErrorCodeRetryLimitExceeded:
code = @"storage/retry-limit-exceeded";
message = @"The maximum time limit on an operation (upload, download, delete, etc.) has been exceeded.";
break;
case FIRStorageErrorCodeNonMatchingChecksum:
code = @"storage/non-matching-checksum";
message = @"File on the client does not match the checksum of the file received by the server.";
break;
case FIRStorageErrorCodeDownloadSizeExceeded:
code = @"storage/download-size-exceeded";
message = @"Size of the downloaded file exceeds the amount of memory allocated for the download.";
break;
case FIRStorageErrorCodeCancelled:
code = @"storage/cancelled";
message = @"User cancelled the operation.";
break;
default:
break;
}
if (userInfo != nil && [userInfo objectForKey:@"data"]) {
// errors with 'data' are unserializable - it breaks react so we send nil instead
reject(code, message, nil);
} else {
reject(code, message, error);
}
2017-03-09 15:26:28 +00:00
}
/**
delete
@url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#delete
@param NSString path
*/
RCT_EXPORT_METHOD(delete: (NSString *) path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
2017-03-09 15:26:28 +00:00
FIRStorageReference *fileRef = [self getReference:path];
2017-03-09 15:26:28 +00:00
[fileRef deleteWithCompletion:^(NSError * _Nullable error) {
if (error != nil) {
[self promiseRejectStorageException:reject error: error];
2017-03-09 15:26:28 +00:00
} else {
resolve([NSNull null]);
2017-03-09 15:26:28 +00:00
}
}];
}
/**
getDownloadURL
@url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getDownloadURL
@param NSString path
*/
RCT_EXPORT_METHOD(getDownloadURL: (NSString *) path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
2017-03-09 15:26:28 +00:00
FIRStorageReference *fileRef = [self getReference:path];
2017-03-09 15:26:28 +00:00
[fileRef downloadURLWithCompletion:^(NSURL * _Nullable URL, NSError * _Nullable error) {
if (error != nil) {
[self promiseRejectStorageException:reject error: error];
2017-03-09 15:26:28 +00:00
} else {
resolve([URL absoluteString]);
2017-03-09 15:26:28 +00:00
}
}];
}
/**
getMetadata
@url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getMetadata
@param NSString path
*/
RCT_EXPORT_METHOD(getMetadata: (NSString *) path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
2017-03-09 15:26:28 +00:00
FIRStorageReference *fileRef = [self getReference:path];
2017-03-09 15:26:28 +00:00
[fileRef metadataWithCompletion:^(FIRStorageMetadata * _Nullable metadata, NSError * _Nullable error) {
if (error != nil) {
[self promiseRejectStorageException:reject error: error];
2017-03-09 15:26:28 +00:00
} else {
resolve([metadata dictionaryRepresentation]);
2017-03-09 15:26:28 +00:00
}
}];
}
/**
updateMetadata
@url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#updateMetadata
@param NSString path
@param NSDictionary metadata
*/
RCT_EXPORT_METHOD(updateMetadata: (NSString *) path metadata:(NSDictionary *) metadata resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
2017-03-09 15:26:28 +00:00
FIRStorageReference *fileRef = [self getReference:path];
FIRStorageMetadata *firmetadata = [[FIRStorageMetadata alloc] initWithDictionary:metadata];
2017-03-09 15:26:28 +00:00
[fileRef updateMetadata:firmetadata completion:^(FIRStorageMetadata * _Nullable metadata, NSError * _Nullable error) {
if (error != nil) {
[self promiseRejectStorageException:reject error: error];
2017-03-09 15:26:28 +00:00
} else {
resolve([metadata dictionaryRepresentation]);
2017-03-09 15:26:28 +00:00
}
}];
}
/**
downloadFile
@url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#downloadFile
@param NSString path
@param NSString localPath
*/
RCT_EXPORT_METHOD(downloadFile: (NSString *) path localPath:(NSString *) localPath resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
2017-03-09 15:26:28 +00:00
FIRStorageReference *fileRef = [self getReference:path];
NSURL *localFile = [NSURL fileURLWithPath:localPath];
FIRStorageDownloadTask *downloadTask = [fileRef writeToFile:localFile];
// listen for state changes, errors, and completion of the download.
2017-03-09 15:26:28 +00:00
[downloadTask observeStatus:FIRStorageTaskStatusResume handler:^(FIRStorageTaskSnapshot *snapshot) {
// download resumed, also fires when the upload starts
2017-03-09 15:26:28 +00:00
NSDictionary *event = [self getDownloadTaskAsDictionary:snapshot];
[self sendJSEvent:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event];
}];
2017-03-09 15:26:28 +00:00
[downloadTask observeStatus:FIRStorageTaskStatusPause handler:^(FIRStorageTaskSnapshot *snapshot) {
// download paused
2017-03-09 15:26:28 +00:00
NSDictionary *event = [self getDownloadTaskAsDictionary:snapshot];
[self sendJSEvent:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event];
}];
2017-03-09 15:26:28 +00:00
[downloadTask observeStatus:FIRStorageTaskStatusProgress handler:^(FIRStorageTaskSnapshot *snapshot) {
// download reported progress
2017-03-09 15:26:28 +00:00
NSDictionary *event = [self getDownloadTaskAsDictionary:snapshot];
[self sendJSEvent:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event];
}];
2017-03-09 15:26:28 +00:00
[downloadTask observeStatus:FIRStorageTaskStatusSuccess handler:^(FIRStorageTaskSnapshot *snapshot) {
// download completed successfully
2017-03-09 15:26:28 +00:00
NSDictionary *resp = [self getDownloadTaskAsDictionary:snapshot];
[self sendJSEvent:STORAGE_EVENT path:path title:STORAGE_DOWNLOAD_SUCCESS props:resp];
resolve(resp);
2017-03-09 15:26:28 +00:00
}];
2017-03-09 15:26:28 +00:00
[downloadTask observeStatus:FIRStorageTaskStatusFailure handler:^(FIRStorageTaskSnapshot *snapshot) {
// download task failed
// TODO sendJSError event
2017-03-09 15:26:28 +00:00
if (snapshot.error != nil) {
[self promiseRejectStorageException:reject error:snapshot.error];
}
}];
}
2017-03-09 15:26:28 +00:00
/**
setMaxDownloadRetryTime
@url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxDownloadRetryTime
@param NSNumber milliseconds
*/
RCT_EXPORT_METHOD(setMaxDownloadRetryTime:(NSNumber *) milliseconds) {
[[FIRStorage storage] setMaxDownloadRetryTime:[milliseconds doubleValue]];
}
2017-03-09 15:26:28 +00:00
/**
setMaxOperationRetryTime
@url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxOperationRetryTime
@param NSNumber milliseconds
*/
RCT_EXPORT_METHOD(setMaxOperationRetryTime:(NSNumber *) milliseconds) {
[[FIRStorage storage] setMaxOperationRetryTime:[milliseconds doubleValue]];
2017-03-09 15:26:28 +00:00
}
/**
setMaxUploadRetryTime
@url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxUploadRetryTime
*/
RCT_EXPORT_METHOD(setMaxUploadRetryTime:(NSNumber *) milliseconds) {
[[FIRStorage storage] setMaxUploadRetryTime:[milliseconds doubleValue]];
}
2017-03-09 15:26:28 +00:00
/**
putFile
@url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#putFile
@param NSString path
@param NSString localPath
@param NSDictionary metadata
*/
RCT_EXPORT_METHOD(putFile:(NSString *) path localPath:(NSString *)localPath metadata:(NSDictionary *)metadata resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
2017-03-09 15:26:28 +00:00
if ([localPath hasPrefix:@"assets-library://"] || [localPath hasPrefix:@"ph://"]) {
PHFetchResult* assets;
2017-03-09 15:26:28 +00:00
if ([localPath hasPrefix:@"assets-library://"]) {
NSURL *localFile = [[NSURL alloc] initWithString:localPath];
assets = [PHAsset fetchAssetsWithALAssetURLs:@[localFile] options:nil];
} else {
NSString *assetId = [localPath substringFromIndex:@"ph://".length];
assets = [PHAsset fetchAssetsWithLocalIdentifiers:@[assetId] options:nil];
}
2017-03-09 15:26:28 +00:00
PHAsset *asset = [assets firstObject];
// this is based on http://stackoverflow.com/questions/35241449
if (asset.mediaType == PHAssetMediaTypeImage) {
// images
2017-03-09 15:26:28 +00:00
PHImageRequestOptions *options = [PHImageRequestOptions new];
options.networkAccessAllowed = true;
[[PHImageManager defaultManager] requestImageDataForAsset:asset options:options resultHandler:^(NSData * imageData, NSString * dataUTI, UIImageOrientation orientation, NSDictionary * info) {
if ([info objectForKey:PHImageErrorKey] == nil) {
[self uploadData:imageData metadata:metadata path:path resolver:resolve rejecter:reject];
} else {
reject(@"storage/request-image-data-failed", @"Could not obtain image data for the specified file.", nil);
}
}];
2017-03-09 15:26:28 +00:00
} else if (asset.mediaType == PHAssetMediaTypeVideo) {
// video
2017-03-09 15:26:28 +00:00
PHVideoRequestOptions *options = [PHVideoRequestOptions new];
options.networkAccessAllowed = true;
[[PHImageManager defaultManager] requestExportSessionForVideo:asset options:options exportPreset:AVAssetExportPresetHighestQuality resultHandler:^(AVAssetExportSession * _Nullable exportSession, NSDictionary * _Nullable info) {
if ([info objectForKey:PHImageErrorKey] == nil) {
NSURL *tempUrl = [self temporaryFileUrl];
exportSession.outputURL = tempUrl;
NSArray<PHAssetResource *> *resources = [PHAssetResource assetResourcesForAsset:asset];
for (PHAssetResource *resource in resources) {
exportSession.outputFileType = resource.uniformTypeIdentifier;
if (exportSession.outputFileType != nil) break;
}
[exportSession exportAsynchronouslyWithCompletionHandler:^{
if (exportSession.status == AVAssetExportSessionStatusCompleted) {
[self uploadFile:tempUrl metadata:metadata path:path resolver:resolve rejecter:reject];
// we're not cleaning up the temporary file at the moment, just relying on the OS to do that in it's own time - todo?
} else {
reject(@"storage/temporary-file-failure", @"Unable to create temporary file for upload.", nil);
}
}];
} else {
reject(@"storage/export-session-failure", @"Unable to create export session for asset.", nil);
}
}];
2017-03-09 15:26:28 +00:00
}
} else {
NSURL *fileUrl = [NSURL fileURLWithPath:localPath];
[self uploadFile:fileUrl metadata:metadata path:path resolver:resolve rejecter:reject];
2017-03-09 15:26:28 +00:00
}
2017-03-09 15:26:28 +00:00
}
- (NSURL *) temporaryFileUrl {
2017-03-09 15:26:28 +00:00
NSString *filename = [NSString stringWithFormat:@"%@.tmp", [[NSProcessInfo processInfo] globallyUniqueString]];
return [[NSURL fileURLWithPath:NSTemporaryDirectory()] URLByAppendingPathComponent:filename];
}
- (void) uploadFile:(NSURL *) url metadata:(NSDictionary *) metadata path:(NSString *) path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
2017-03-09 15:26:28 +00:00
FIRStorageReference *fileRef = [self getReference:path];
FIRStorageMetadata *firmetadata = [[FIRStorageMetadata alloc] initWithDictionary:metadata];
FIRStorageUploadTask *uploadTask = [fileRef putFile:url metadata:firmetadata];
[self addUploadObservers:uploadTask path:path resolver:resolve rejecter:reject];
2017-03-09 15:26:28 +00:00
}
- (void) uploadData:(NSData *) data metadata:(NSDictionary *) metadata path:(NSString *) path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject{
2017-03-09 15:26:28 +00:00
FIRStorageReference *fileRef = [self getReference:path];
FIRStorageMetadata *firmetadata = [[FIRStorageMetadata alloc] initWithDictionary:metadata];
FIRStorageUploadTask *uploadTask = [fileRef putData:data metadata:firmetadata];
[self addUploadObservers:uploadTask path:path resolver:resolve rejecter:reject];
2017-03-09 15:26:28 +00:00
}
- (void) addUploadObservers:(FIRStorageUploadTask *) uploadTask path:(NSString *) path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject{
// listen for state changes, errors, and completion of the upload.
2017-03-09 15:26:28 +00:00
[uploadTask observeStatus:FIRStorageTaskStatusResume handler:^(FIRStorageTaskSnapshot *snapshot) {
// upload resumed, also fires when the upload starts
2017-03-09 15:26:28 +00:00
NSDictionary *event = [self getUploadTaskAsDictionary:snapshot];
[self sendJSEvent:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event];
}];
2017-03-09 15:26:28 +00:00
[uploadTask observeStatus:FIRStorageTaskStatusPause handler:^(FIRStorageTaskSnapshot *snapshot) {
// upload paused
2017-03-09 15:26:28 +00:00
NSDictionary *event = [self getUploadTaskAsDictionary:snapshot];
[self sendJSEvent:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event];
}];
[uploadTask observeStatus:FIRStorageTaskStatusProgress handler:^(FIRStorageTaskSnapshot *snapshot) {
// upload reported progress
2017-03-09 15:26:28 +00:00
NSDictionary *event = [self getUploadTaskAsDictionary:snapshot];
[self sendJSEvent:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event];
}];
2017-03-09 15:26:28 +00:00
[uploadTask observeStatus:FIRStorageTaskStatusSuccess handler:^(FIRStorageTaskSnapshot *snapshot) {
// upload completed successfully
2017-03-09 15:26:28 +00:00
NSDictionary *resp = [self getUploadTaskAsDictionary:snapshot];
[self sendJSEvent:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:resp];
2017-03-09 15:26:28 +00:00
[self sendJSEvent:STORAGE_EVENT path:path title:STORAGE_UPLOAD_SUCCESS props:resp];
resolve(resp);
2017-03-09 15:26:28 +00:00
}];
2017-03-09 15:26:28 +00:00
[uploadTask observeStatus:FIRStorageTaskStatusFailure handler:^(FIRStorageTaskSnapshot *snapshot) {
if (snapshot.error != nil) {
[self promiseRejectStorageException:reject error:snapshot.error];
}
}];
2017-03-09 15:26:28 +00:00
}
- (FIRStorageReference *)getReference:(NSString *)path {
2017-03-09 15:26:28 +00:00
if ([path hasPrefix:@"url::"]) {
NSString *url = [path substringFromIndex:5];
return [[FIRStorage storage] referenceForURL:url];
} else {
return [[FIRStorage storage] referenceWithPath:path];
}
}
- (NSDictionary *)getDownloadTaskAsDictionary:(FIRStorageTaskSnapshot *)task {
return @{
@"bytesTransferred": @(task.progress.completedUnitCount),
@"ref": task.reference.fullPath,
@"state": [self getTaskStatus:task.status],
2017-03-09 15:26:28 +00:00
@"totalBytes": @(task.progress.totalUnitCount)
};
}
- (NSDictionary *)getUploadTaskAsDictionary:(FIRStorageTaskSnapshot *)task {
2017-03-09 15:26:28 +00:00
NSString *downloadUrl = [task.metadata.downloadURL absoluteString];
FIRStorageMetadata *metadata = [task.metadata dictionaryRepresentation];
return @{
@"bytesTransferred": @(task.progress.completedUnitCount),
@"downloadUrl": downloadUrl != nil ? downloadUrl : [NSNull null],
@"metadata": metadata != nil ? metadata : [NSNull null],
@"ref": task.reference.fullPath,
@"state": [self getTaskStatus:task.status],
@"totalBytes": @(task.progress.totalUnitCount)
};
}
- (NSString *)getTaskStatus:(FIRStorageTaskStatus)status {
2017-03-09 15:26:28 +00:00
if (status == FIRStorageTaskStatusResume || status == FIRStorageTaskStatusProgress) {
return @"running";
2017-03-09 15:26:28 +00:00
} else if (status == FIRStorageTaskStatusPause) {
return @"paused";
2017-03-09 15:26:28 +00:00
} else if (status == FIRStorageTaskStatusSuccess) {
return @"success";
2017-03-09 15:26:28 +00:00
} else if (status == FIRStorageTaskStatusFailure) {
return @"error";
2017-03-09 15:26:28 +00:00
} else {
return @"unknown";
2017-03-09 15:26:28 +00:00
}
}
- (NSString *)getPathForDirectory:(int)directory {
NSArray *paths = NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, YES);
return [paths firstObject];
2017-03-09 15:26:28 +00:00
}
- (NSDictionary *)constantsToExport {
return @{
@"MAIN_BUNDLE_PATH": [[NSBundle mainBundle] bundlePath],
@"CACHES_DIRECTORY_PATH": [self getPathForDirectory:NSCachesDirectory],
@"DOCUMENT_DIRECTORY_PATH": [self getPathForDirectory:NSDocumentDirectory],
@"EXTERNAL_DIRECTORY_PATH": [NSNull null],
@"EXTERNAL_STORAGE_DIRECTORY_PATH": [NSNull null],
@"TEMP_DIRECTORY_PATH": NSTemporaryDirectory(),
@"LIBRARY_DIRECTORY_PATH": [self getPathForDirectory:NSLibraryDirectory],
@"FILETYPE_REGULAR": NSFileTypeRegular,
@"FILETYPE_DIRECTORY": NSFileTypeDirectory
};
2017-03-09 15:26:28 +00:00
}
- (NSArray<NSString *> *)supportedEvents {
return @[STORAGE_EVENT, STORAGE_ERROR];
}
- (void) sendJSError:(NSError *) error withPath:(NSString *) path {
NSDictionary *evt = @{ @"path": path, @"message": [error debugDescription] };
2017-03-09 15:26:28 +00:00
[self sendJSEvent:STORAGE_ERROR path:path title:STORAGE_ERROR props: evt];
}
- (void) sendJSEvent:(NSString *)type path:(NSString *)path title:(NSString *)title props:(NSDictionary *)props {
2017-03-09 15:26:28 +00:00
@try {
[self sendEventWithName:type body:@{ @"eventName": title, @"path": path, @"body": props }];
} @catch (NSException *err) {
2017-03-09 15:26:28 +00:00
NSLog(@"An error occurred in sendJSEvent: %@", [err debugDescription]);
NSLog(@"Tried to send: %@ with %@", title, props);
}
}
@end