From 03b19fdae9ce28e7cc2eedc9aadef28b1877eae0 Mon Sep 17 00:00:00 2001 From: Alan Hoffmeister Date: Mon, 10 Oct 2016 15:04:02 -0700 Subject: [PATCH] Implement downloads for iOS --- ios/Firestack/FirestackEvents.h | 5 +- ios/Firestack/FirestackStorage.m | 127 +++++++++++++++++++++++++++---- lib/modules/storage.js | 32 +++++++- 3 files changed, 143 insertions(+), 21 deletions(-) diff --git a/ios/Firestack/FirestackEvents.h b/ios/Firestack/FirestackEvents.h index 7cbe8e8..ff6ea79 100644 --- a/ios/Firestack/FirestackEvents.h +++ b/ios/Firestack/FirestackEvents.h @@ -38,6 +38,9 @@ static NSString *const DATABASE_CHILD_MOVED_EVENT = @"child_moved"; static NSString *const STORAGE_UPLOAD_PROGRESS = @"upload_progress"; static NSString *const STORAGE_UPLOAD_PAUSED = @"upload_paused"; static NSString *const STORAGE_UPLOAD_RESUMED = @"upload_resumed"; +static NSString *const STORAGE_DOWNLOAD_PROGRESS = @"download_progress"; +static NSString *const STORAGE_DOWNLOAD_PAUSED = @"download_paused"; +static NSString *const STORAGE_DOWNLOAD_RESUMED = @"download_resumed"; // Messaging static NSString *const MESSAGING_SUBSYSTEM_EVENT = @"messaging_event"; @@ -47,4 +50,4 @@ static NSString *const MESSAGING_TOKEN_REFRESH = @"messaging_token_refresh"; static NSString *const MESSAGING_MESSAGE_RECEIVED_REMOTE = @"messaging_remote_event_received"; static NSString *const MESSAGING_MESSAGE_RECEIVED_LOCAL = @"messaging_local_event_received"; -#endif \ No newline at end of file +#endif diff --git a/ios/Firestack/FirestackStorage.m b/ios/Firestack/FirestackStorage.m index 2d0a603..762020c 100644 --- a/ios/Firestack/FirestackStorage.m +++ b/ios/Firestack/FirestackStorage.m @@ -52,7 +52,7 @@ @implementation FirestackStorage [err setValue:@"Call setStorageUrl() first" forKey:@"description"]; return callback(@[err]); } - + if ([path hasPrefix:@"assets-library://"]) { NSURL *localFile = [[NSURL alloc] initWithString:path]; PHFetchResult* assets = [PHAsset fetchAssetsWithALAssetURLs:@[localFile] options:nil]; @@ -60,7 +60,7 @@ @implementation FirestackStorage [asset requestContentEditingInputWithOptions:nil completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) { NSURL *imageFile = contentEditingInput.fullSizeImageURL; - + [self performUpload:urlStr name:name file:imageFile @@ -70,14 +70,14 @@ @implementation FirestackStorage } else { NSURL *localFile = [NSURL fileURLWithPath:path]; FIRStorageMetadata *firmetadata = [[FIRStorageMetadata alloc] initWithDictionary:metadata]; - + [self performUpload:urlStr name:name file:localFile metadata:firmetadata callback:callback]; } - + } - (void) performUpload:(NSString *) urlStr @@ -88,10 +88,10 @@ - (void) performUpload:(NSString *) urlStr { FIRStorageReference *storageRef = [[FIRStorage storage] referenceForURL:urlStr]; FIRStorageReference *uploadRef = [storageRef child:name]; - + FIRStorageUploadTask *uploadTask = [uploadRef putFile:imageFile metadata:firmetadata]; - + // Listen for state changes, errors, and completion of the upload. [uploadTask observeStatus:FIRStorageTaskStatusResume handler:^(FIRStorageTaskSnapshot *snapshot) { // Upload resumed, also fires when the upload starts @@ -100,7 +100,7 @@ - (void) performUpload:(NSString *) urlStr @"ref": snapshot.reference.bucket }]; }]; - + [uploadTask observeStatus:FIRStorageTaskStatusPause handler:^(FIRStorageTaskSnapshot *snapshot) { // Upload paused [self sendJSEvent:STORAGE_UPLOAD_PAUSED props:@{ @@ -116,17 +116,17 @@ - (void) performUpload:(NSString *) urlStr } else { percentComplete = 100.0 * (snapshot.progress.completedUnitCount) / (snapshot.progress.totalUnitCount); } - + [self sendJSEvent:STORAGE_UPLOAD_PROGRESS props:@{ @"eventName": STORAGE_UPLOAD_PROGRESS, @"progress": @(percentComplete) }]; - + }]; - + [uploadTask observeStatus:FIRStorageTaskStatusSuccess handler:^(FIRStorageTaskSnapshot *snapshot) { [uploadTask removeAllObservers]; - + // Upload completed successfully FIRStorageReference *ref = snapshot.reference; NSDictionary *props = @{ @@ -135,14 +135,14 @@ - (void) performUpload:(NSString *) urlStr @"name": ref.name, @"metadata": [snapshot.metadata dictionaryRepresentation] }; - + callback(@[[NSNull null], props]); }]; - + [uploadTask observeStatus:FIRStorageTaskStatusFailure handler:^(FIRStorageTaskSnapshot *snapshot) { if (snapshot.error != nil) { NSDictionary *errProps = [[NSMutableDictionary alloc] init]; - + switch (snapshot.error.code) { case FIRStorageErrorCodeObjectNotFound: // File doesn't exist @@ -161,17 +161,112 @@ - (void) performUpload:(NSString *) urlStr [errProps setValue:@"Unknown error" forKey:@"description"]; break; } - + + callback(@[errProps]); + }}]; +} + +RCT_EXPORT_METHOD(downloadFile: (NSString *) urlStr + path:(NSString *) path + localFile:(NSString *) file + callback:(RCTResponseSenderBlock) callback) +{ + if (urlStr == nil) { + NSError *err = [[NSError alloc] init]; + [err setValue:@"Storage configuration error" forKey:@"name"]; + [err setValue:@"Call setStorageUrl() first" forKey:@"description"]; + return callback(@[err]); + } + + FIRStorageReference *storageRef = [[FIRStorage storage] referenceForURL:urlStr]; + FIRStorageReference *fileRef = [storageRef child:path]; + + NSURL *localFile = [NSURL fileURLWithPath:file]; + + FIRStorageDownloadTask *downloadTask = [fileRef writeToFile:localFile]; + // Listen for state changes, errors, and completion of the download. + [downloadTask observeStatus:FIRStorageTaskStatusResume handler:^(FIRStorageTaskSnapshot *snapshot) { + // Upload resumed, also fires when the upload starts + [self sendJSEvent:STORAGE_DOWNLOAD_RESUMED props:@{ + @"eventName": STORAGE_DOWNLOAD_RESUMED, + @"ref": snapshot.reference.bucket + }]; + }]; + + [downloadTask observeStatus:FIRStorageTaskStatusPause handler:^(FIRStorageTaskSnapshot *snapshot) { + // Upload paused + [self sendJSEvent:STORAGE_DOWNLOAD_PAUSED props:@{ + @"eventName": STORAGE_DOWNLOAD_PAUSED, + @"ref": snapshot.reference.bucket + }]; + }]; + [downloadTask observeStatus:FIRStorageTaskStatusProgress handler:^(FIRStorageTaskSnapshot *snapshot) { + // Upload reported progress + float percentComplete; + if (snapshot.progress.totalUnitCount == 0) { + percentComplete = 0.0; + } else { + percentComplete = 100.0 * (snapshot.progress.completedUnitCount) / (snapshot.progress.totalUnitCount); + } + + [self sendJSEvent:STORAGE_DOWNLOAD_PROGRESS props:@{ + @"eventName": STORAGE_DOWNLOAD_PROGRESS, + @"progress": @(percentComplete) + }]; + + }]; + + [downloadTask observeStatus:FIRStorageTaskStatusSuccess handler:^(FIRStorageTaskSnapshot *snapshot) { + [downloadTask removeAllObservers]; + + // Upload completed successfully + FIRStorageReference *ref = snapshot.reference; + NSDictionary *props = @{ + @"fullPath": ref.fullPath, + @"bucket": ref.bucket, + @"name": ref.name + }; + + callback(@[[NSNull null], props]); + }]; + + [downloadTask observeStatus:FIRStorageTaskStatusFailure handler:^(FIRStorageTaskSnapshot *snapshot) { + if (snapshot.error != nil) { + NSDictionary *errProps = [[NSMutableDictionary alloc] init]; + + switch (snapshot.error.code) { + case FIRStorageErrorCodeObjectNotFound: + // File doesn't exist + [errProps setValue:@"File does not exist" forKey:@"description"]; + break; + case FIRStorageErrorCodeUnauthorized: + // User doesn't have permission to access file + [errProps setValue:@"You do not have permissions" forKey:@"description"]; + break; + case FIRStorageErrorCodeCancelled: + // User canceled the upload + [errProps setValue:@"Download canceled" forKey:@"description"]; + break; + case FIRStorageErrorCodeUnknown: + // Unknown error occurred, inspect the server response + [errProps setValue:@"Unknown error" forKey:@"description"]; + break; + } + callback(@[errProps]); }}]; } + // Not sure how to get away from this... yet - (NSArray *)supportedEvents { return @[ STORAGE_UPLOAD_PAUSED, STORAGE_UPLOAD_RESUMED, - STORAGE_UPLOAD_PROGRESS + STORAGE_UPLOAD_PROGRESS, + STORAGE_DOWNLOAD_PAUSED, + STORAGE_DOWNLOAD_RESUMED, + STORAGE_DOWNLOAD_PROGRESS ]; } diff --git a/lib/modules/storage.js b/lib/modules/storage.js index 5388400..a7bd78a 100644 --- a/lib/modules/storage.js +++ b/lib/modules/storage.js @@ -10,12 +10,36 @@ class StorageRef extends ReferenceBase { constructor(storage, path) { super(storage.firestack, path); - this.storageUrl = storage.storageUrl; + this.storage = storage; } downloadUrl() { const path = this.pathToString(); - return promisify('downloadUrl', FirestackStorage)(this.storageUrl, path); + return promisify('downloadUrl', FirestackStorage)(this.storage.storageUrl, path); + } + + /** + * Downloads a reference to the device + * @param {String} downloadPath Where to store the file + * @return {Promise} + */ + download (downloadPath, cb) { + let callback = cb; + if (!callback || typeof callback !== 'function') { + callback = (evt) => {}; + } + + const listeners = []; + listeners.push(this.storage._addListener('download_progress', callback)); + listeners.push(this.storage._addListener('download_paused', callback)); + listeners.push(this.storage._addListener('download_resumed', callback)); + + const path = this.pathToString(); + return promisify('downloadFile', FirestackStorage)(this.storage.storageUrl, path, downloadPath) + .then((res) => { + listeners.forEach(this.storage._removeListener); + return res; + }); } } @@ -42,7 +66,7 @@ export class Storage extends Base { } return this.refs[key]; } - + /** * Upload a filepath * @param {string} name The destination for the file @@ -91,4 +115,4 @@ export class Storage extends Base { } } -export default Storage \ No newline at end of file +export default Storage