FIRStorageUploadTask.m 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. // Copyright 2017 Google
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "FirebaseStorage/Sources/Public/FirebaseStorage/FIRStorageUploadTask.h"
  15. #import "FirebaseStorage/Sources/FIRStorageConstants_Private.h"
  16. #import "FirebaseStorage/Sources/FIRStorageMetadata_Private.h"
  17. #import "FirebaseStorage/Sources/FIRStorageObservableTask_Private.h"
  18. #import "FirebaseStorage/Sources/FIRStorageTask_Private.h"
  19. #import "FirebaseStorage/Sources/FIRStorageUploadTask_Private.h"
  20. #if SWIFT_PACKAGE
  21. @import GTMSessionFetcherCore;
  22. #else
  23. #import <GTMSessionFetcher/GTMSessionUploadFetcher.h>
  24. #endif
  25. @implementation FIRStorageUploadTask
  26. @synthesize progress = _progress;
  27. @synthesize fetcherCompletion = _fetcherCompletion;
  28. - (instancetype)initWithReference:(FIRStorageReference *)reference
  29. fetcherService:(GTMSessionFetcherService *)service
  30. dispatchQueue:(dispatch_queue_t)queue
  31. data:(NSData *)uploadData
  32. metadata:(FIRStorageMetadata *)metadata {
  33. self = [super initWithReference:reference fetcherService:service dispatchQueue:queue];
  34. if (self) {
  35. _uploadMetadata = [metadata copy];
  36. _uploadData = [uploadData copy];
  37. _progress = [NSProgress progressWithTotalUnitCount:[_uploadData length]];
  38. if (!_uploadMetadata.contentType) {
  39. _uploadMetadata.contentType = @"application/octet-stream";
  40. }
  41. }
  42. return self;
  43. }
  44. - (instancetype)initWithReference:(FIRStorageReference *)reference
  45. fetcherService:(GTMSessionFetcherService *)service
  46. dispatchQueue:(dispatch_queue_t)queue
  47. file:(NSURL *)fileURL
  48. metadata:(FIRStorageMetadata *)metadata {
  49. self = [super initWithReference:reference fetcherService:service dispatchQueue:queue];
  50. if (self) {
  51. _uploadMetadata = [metadata copy];
  52. _fileURL = [fileURL copy];
  53. _progress = [NSProgress progressWithTotalUnitCount:0];
  54. NSString *mimeType = [FIRStorageUtils MIMETypeForExtension:[_fileURL pathExtension]];
  55. if (!_uploadMetadata.contentType) {
  56. _uploadMetadata.contentType = mimeType ?: @"application/octet-stream";
  57. }
  58. }
  59. return self;
  60. }
  61. - (void)dealloc {
  62. [_uploadFetcher stopFetching];
  63. }
  64. - (void)enqueue {
  65. __weak FIRStorageUploadTask *weakSelf = self;
  66. [self dispatchAsync:^() {
  67. FIRStorageUploadTask *strongSelf = weakSelf;
  68. if (!strongSelf) {
  69. return;
  70. }
  71. NSError *contentValidationError;
  72. if (![strongSelf isContentToUploadValid:&contentValidationError]) {
  73. strongSelf.error = contentValidationError;
  74. [strongSelf finishTaskWithStatus:FIRStorageTaskStatusFailure snapshot:strongSelf.snapshot];
  75. return;
  76. }
  77. strongSelf.state = FIRStorageTaskStateQueueing;
  78. NSMutableURLRequest *request = [strongSelf.baseRequest mutableCopy];
  79. request.HTTPMethod = @"POST";
  80. request.timeoutInterval = strongSelf.reference.storage.maxUploadRetryTime;
  81. NSData *bodyData =
  82. [NSData frs_dataFromJSONDictionary:[strongSelf->_uploadMetadata dictionaryRepresentation]];
  83. request.HTTPBody = bodyData;
  84. [request setValue:@"application/json; charset=UTF-8" forHTTPHeaderField:@"Content-Type"];
  85. NSString *contentLengthString =
  86. [NSString stringWithFormat:@"%zu", (unsigned long)[bodyData length]];
  87. [request setValue:contentLengthString forHTTPHeaderField:@"Content-Length"];
  88. NSURLComponents *components = [NSURLComponents componentsWithURL:request.URL
  89. resolvingAgainstBaseURL:NO];
  90. if ([components.host isEqual:kGCSHost]) {
  91. [components setPercentEncodedPath:[@"/upload" stringByAppendingString:components.path]];
  92. }
  93. NSDictionary *queryParams = @{@"uploadType" : @"resumable", @"name" : self.uploadMetadata.path};
  94. [components setPercentEncodedQuery:[FIRStorageUtils queryStringForDictionary:queryParams]];
  95. request.URL = components.URL;
  96. GTMSessionUploadFetcher *uploadFetcher =
  97. [GTMSessionUploadFetcher uploadFetcherWithRequest:request
  98. uploadMIMEType:strongSelf->_uploadMetadata.contentType
  99. chunkSize:kGTMSessionUploadFetcherStandardChunkSize
  100. fetcherService:self.fetcherService];
  101. if (strongSelf->_uploadData) {
  102. [uploadFetcher setUploadData:strongSelf->_uploadData];
  103. uploadFetcher.comment = @"Data UploadTask";
  104. } else if (strongSelf->_fileURL) {
  105. [uploadFetcher setUploadFileURL:strongSelf->_fileURL];
  106. uploadFetcher.comment = @"File UploadTask";
  107. }
  108. uploadFetcher.maxRetryInterval = self.reference.storage.maxUploadRetryTime;
  109. [uploadFetcher setSendProgressBlock:^(int64_t bytesSent, int64_t totalBytesSent,
  110. int64_t totalBytesExpectedToSend) {
  111. weakSelf.state = FIRStorageTaskStateProgress;
  112. weakSelf.progress.completedUnitCount = totalBytesSent;
  113. weakSelf.progress.totalUnitCount = totalBytesExpectedToSend;
  114. weakSelf.metadata = self->_uploadMetadata;
  115. [weakSelf fireHandlersForStatus:FIRStorageTaskStatusProgress snapshot:weakSelf.snapshot];
  116. weakSelf.state = FIRStorageTaskStateRunning;
  117. }];
  118. strongSelf->_uploadFetcher = uploadFetcher;
  119. // Process fetches
  120. strongSelf.state = FIRStorageTaskStateRunning;
  121. strongSelf->_fetcherCompletion = ^(NSData *_Nullable data, NSError *_Nullable error) {
  122. // Fire last progress updates
  123. [self fireHandlersForStatus:FIRStorageTaskStatusProgress snapshot:self.snapshot];
  124. // Handle potential issues with upload
  125. if (error) {
  126. self.state = FIRStorageTaskStateFailed;
  127. self.error = [FIRStorageErrors errorWithServerError:error reference:self.reference];
  128. self.metadata = self->_uploadMetadata;
  129. [self finishTaskWithStatus:FIRStorageTaskStatusFailure snapshot:self.snapshot];
  130. return;
  131. }
  132. // Upload completed successfully, fire completion callbacks
  133. self.state = FIRStorageTaskStateSuccess;
  134. NSDictionary *responseDictionary = [NSDictionary frs_dictionaryFromJSONData:data];
  135. if (responseDictionary) {
  136. FIRStorageMetadata *metadata =
  137. [[FIRStorageMetadata alloc] initWithDictionary:responseDictionary];
  138. [metadata setType:FIRStorageMetadataTypeFile];
  139. self.metadata = metadata;
  140. } else {
  141. self.error = [FIRStorageErrors errorWithInvalidRequest:data];
  142. }
  143. [self finishTaskWithStatus:FIRStorageTaskStatusSuccess snapshot:self.snapshot];
  144. };
  145. [strongSelf->_uploadFetcher
  146. beginFetchWithCompletionHandler:^(NSData *_Nullable data, NSError *_Nullable error) {
  147. if (weakSelf.fetcherCompletion != nil) {
  148. weakSelf.fetcherCompletion(data, error);
  149. }
  150. }];
  151. }];
  152. }
  153. - (void)finishTaskWithStatus:(FIRStorageTaskStatus)status
  154. snapshot:(FIRStorageTaskSnapshot *)snapshot {
  155. [self fireHandlersForStatus:status snapshot:self.snapshot];
  156. [self removeAllObservers];
  157. self->_fetcherCompletion = nil;
  158. }
  159. - (BOOL)isContentToUploadValid:(NSError **)outError {
  160. if (_uploadData != nil) {
  161. return YES;
  162. }
  163. NSError *fileReachabilityError;
  164. if (![_fileURL checkResourceIsReachableAndReturnError:&fileReachabilityError] ||
  165. ![self fileURLisFile:_fileURL]) {
  166. if (outError != NULL) {
  167. NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithCapacity:2];
  168. userInfo[NSLocalizedDescriptionKey] = [NSString
  169. stringWithFormat:@"File at URL: %@ is not reachable. "
  170. @"Ensure file URL is not a directory, symbolic link, or invalid url.",
  171. _fileURL.absoluteString];
  172. if (fileReachabilityError) {
  173. userInfo[NSUnderlyingErrorKey] = fileReachabilityError;
  174. }
  175. *outError = [NSError errorWithDomain:FIRStorageErrorDomain
  176. code:FIRStorageErrorCodeUnknown
  177. userInfo:userInfo];
  178. }
  179. return NO;
  180. }
  181. return YES;
  182. }
  183. #pragma mark - Upload Management
  184. - (void)cancel {
  185. __weak FIRStorageUploadTask *weakSelf = self;
  186. [self dispatchAsync:^() {
  187. weakSelf.state = FIRStorageTaskStateCancelled;
  188. [weakSelf.uploadFetcher stopFetching];
  189. if (weakSelf.state != FIRStorageTaskStateSuccess) {
  190. weakSelf.metadata = weakSelf.uploadMetadata;
  191. }
  192. weakSelf.error = [FIRStorageErrors errorWithCode:FIRStorageErrorCodeCancelled];
  193. [weakSelf fireHandlersForStatus:FIRStorageTaskStatusFailure snapshot:weakSelf.snapshot];
  194. }];
  195. }
  196. - (void)pause {
  197. __weak FIRStorageUploadTask *weakSelf = self;
  198. [self dispatchAsync:^() {
  199. weakSelf.state = FIRStorageTaskStatePaused;
  200. [weakSelf.uploadFetcher pauseFetching];
  201. if (weakSelf.state != FIRStorageTaskStateSuccess) {
  202. weakSelf.metadata = weakSelf.uploadMetadata;
  203. }
  204. [weakSelf fireHandlersForStatus:FIRStorageTaskStatusPause snapshot:weakSelf.snapshot];
  205. }];
  206. }
  207. - (void)resume {
  208. __weak FIRStorageUploadTask *weakSelf = self;
  209. [self dispatchAsync:^() {
  210. weakSelf.state = FIRStorageTaskStateResuming;
  211. [weakSelf.uploadFetcher resumeFetching];
  212. if (weakSelf.state != FIRStorageTaskStateSuccess) {
  213. weakSelf.metadata = weakSelf.uploadMetadata;
  214. }
  215. [weakSelf fireHandlersForStatus:FIRStorageTaskStatusResume snapshot:weakSelf.snapshot];
  216. weakSelf.state = FIRStorageTaskStateRunning;
  217. }];
  218. }
  219. #pragma mark - Private Helpers
  220. - (BOOL)fileURLisFile:(NSURL *)fileURL {
  221. NSNumber *isFile = [NSNumber numberWithBool:NO];
  222. [fileURL getResourceValue:&isFile forKey:NSURLIsRegularFileKey error:nil];
  223. return [isFile boolValue];
  224. }
  225. @end