FIRStorageUploadTask.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  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. #import "FirebaseStorage/Sources/FIRStorage_Private.h"
  21. #if SWIFT_PACKAGE
  22. @import GTMSessionFetcherCore;
  23. #else
  24. #import <GTMSessionFetcher/GTMSessionUploadFetcher.h>
  25. #endif
  26. @implementation FIRIMPLStorageUploadTask
  27. @synthesize progress = _progress;
  28. @synthesize fetcherCompletion = _fetcherCompletion;
  29. - (instancetype)initWithReference:(FIRIMPLStorageReference *)reference
  30. fetcherService:(GTMSessionFetcherService *)service
  31. dispatchQueue:(dispatch_queue_t)queue
  32. data:(NSData *)uploadData
  33. metadata:(FIRIMPLStorageMetadata *)metadata {
  34. self = [super initWithReference:reference fetcherService:service dispatchQueue:queue];
  35. if (self) {
  36. _uploadMetadata = [metadata copy];
  37. _uploadData = [uploadData copy];
  38. _progress = [NSProgress progressWithTotalUnitCount:[_uploadData length]];
  39. if (!_uploadMetadata.contentType) {
  40. _uploadMetadata.contentType = @"application/octet-stream";
  41. }
  42. }
  43. return self;
  44. }
  45. - (instancetype)initWithReference:(FIRIMPLStorageReference *)reference
  46. fetcherService:(GTMSessionFetcherService *)service
  47. dispatchQueue:(dispatch_queue_t)queue
  48. file:(NSURL *)fileURL
  49. metadata:(FIRIMPLStorageMetadata *)metadata {
  50. self = [super initWithReference:reference fetcherService:service dispatchQueue:queue];
  51. if (self) {
  52. _uploadMetadata = [metadata copy];
  53. _fileURL = [fileURL copy];
  54. _progress = [NSProgress progressWithTotalUnitCount:0];
  55. NSString *mimeType = [FIRStorageUtils MIMETypeForExtension:[_fileURL pathExtension]];
  56. if (!_uploadMetadata.contentType) {
  57. _uploadMetadata.contentType = mimeType ?: @"application/octet-stream";
  58. }
  59. }
  60. return self;
  61. }
  62. - (void)dealloc {
  63. [_uploadFetcher stopFetching];
  64. }
  65. - (void)enqueue {
  66. __weak FIRIMPLStorageUploadTask *weakSelf = self;
  67. [self dispatchAsync:^() {
  68. FIRIMPLStorageUploadTask *strongSelf = weakSelf;
  69. if (!strongSelf) {
  70. return;
  71. }
  72. NSError *contentValidationError;
  73. if (![strongSelf isContentToUploadValid:&contentValidationError]) {
  74. strongSelf.error = contentValidationError;
  75. [strongSelf finishTaskWithStatus:FIRIMPLStorageTaskStatusFailure
  76. snapshot:strongSelf.snapshot];
  77. return;
  78. }
  79. strongSelf.state = FIRIMPLStorageTaskStateQueueing;
  80. NSMutableURLRequest *request = [strongSelf.baseRequest mutableCopy];
  81. request.HTTPMethod = @"POST";
  82. request.timeoutInterval = strongSelf.reference.storage.maxUploadRetryTime;
  83. NSData *bodyData =
  84. [NSData frs_dataFromJSONDictionary:[strongSelf->_uploadMetadata dictionaryRepresentation]];
  85. request.HTTPBody = bodyData;
  86. [request setValue:@"application/json; charset=UTF-8" forHTTPHeaderField:@"Content-Type"];
  87. NSString *contentLengthString =
  88. [NSString stringWithFormat:@"%zu", (unsigned long)[bodyData length]];
  89. [request setValue:contentLengthString forHTTPHeaderField:@"Content-Length"];
  90. NSURLComponents *components = [NSURLComponents componentsWithURL:request.URL
  91. resolvingAgainstBaseURL:NO];
  92. if ([components.host isEqual:kGCSHost]) {
  93. [components setPercentEncodedPath:[@"/upload" stringByAppendingString:components.path]];
  94. }
  95. NSDictionary *queryParams = @{@"uploadType" : @"resumable", @"name" : self.uploadMetadata.path};
  96. [components setPercentEncodedQuery:[FIRStorageUtils queryStringForDictionary:queryParams]];
  97. request.URL = components.URL;
  98. GTMSessionUploadFetcher *uploadFetcher =
  99. [GTMSessionUploadFetcher uploadFetcherWithRequest:request
  100. uploadMIMEType:strongSelf->_uploadMetadata.contentType
  101. chunkSize:kGTMSessionUploadFetcherStandardChunkSize
  102. fetcherService:self.fetcherService];
  103. if (strongSelf->_uploadData) {
  104. [uploadFetcher setUploadData:strongSelf->_uploadData];
  105. uploadFetcher.comment = @"Data UploadTask";
  106. } else if (strongSelf->_fileURL) {
  107. [uploadFetcher setUploadFileURL:strongSelf->_fileURL];
  108. uploadFetcher.comment = @"File UploadTask";
  109. }
  110. uploadFetcher.maxRetryInterval = self.reference.storage.maxUploadRetryInterval;
  111. [uploadFetcher setSendProgressBlock:^(int64_t bytesSent, int64_t totalBytesSent,
  112. int64_t totalBytesExpectedToSend) {
  113. weakSelf.state = FIRIMPLStorageTaskStateProgress;
  114. weakSelf.progress.completedUnitCount = totalBytesSent;
  115. weakSelf.progress.totalUnitCount = totalBytesExpectedToSend;
  116. weakSelf.metadata = self->_uploadMetadata;
  117. [weakSelf fireHandlersForStatus:FIRIMPLStorageTaskStatusProgress snapshot:weakSelf.snapshot];
  118. weakSelf.state = FIRIMPLStorageTaskStateRunning;
  119. }];
  120. strongSelf->_uploadFetcher = uploadFetcher;
  121. // Process fetches
  122. strongSelf.state = FIRIMPLStorageTaskStateRunning;
  123. strongSelf->_fetcherCompletion = ^(NSData *_Nullable data, NSError *_Nullable error) {
  124. // Fire last progress updates
  125. [self fireHandlersForStatus:FIRIMPLStorageTaskStatusProgress snapshot:self.snapshot];
  126. // Handle potential issues with upload
  127. if (error) {
  128. self.state = FIRIMPLStorageTaskStateFailed;
  129. self.error = [FIRStorageErrors errorWithServerError:error reference:self.reference];
  130. self.metadata = self->_uploadMetadata;
  131. [self finishTaskWithStatus:FIRIMPLStorageTaskStatusFailure snapshot:self.snapshot];
  132. return;
  133. }
  134. // Upload completed successfully, fire completion callbacks
  135. self.state = FIRIMPLStorageTaskStateSuccess;
  136. NSDictionary *responseDictionary = [NSDictionary frs_dictionaryFromJSONData:data];
  137. if (responseDictionary) {
  138. FIRIMPLStorageMetadata *metadata =
  139. [[FIRIMPLStorageMetadata alloc] initWithDictionary:responseDictionary];
  140. [metadata setType:FIRStorageMetadataTypeFile];
  141. self.metadata = metadata;
  142. } else {
  143. self.error = [FIRStorageErrors errorWithInvalidRequest:data];
  144. }
  145. [self finishTaskWithStatus:FIRIMPLStorageTaskStatusSuccess snapshot:self.snapshot];
  146. };
  147. [strongSelf->_uploadFetcher
  148. beginFetchWithCompletionHandler:^(NSData *_Nullable data, NSError *_Nullable error) {
  149. FIRIMPLStorageUploadTask *strongSelf = weakSelf;
  150. if (strongSelf.fetcherCompletion) {
  151. strongSelf.fetcherCompletion(data, error);
  152. }
  153. }];
  154. }];
  155. }
  156. - (void)finishTaskWithStatus:(FIRIMPLStorageTaskStatus)status
  157. snapshot:(FIRIMPLStorageTaskSnapshot *)snapshot {
  158. [self fireHandlersForStatus:status snapshot:self.snapshot];
  159. [self removeAllObservers];
  160. self->_fetcherCompletion = nil;
  161. }
  162. - (BOOL)isContentToUploadValid:(NSError **)outError {
  163. if (_uploadData != nil) {
  164. return YES;
  165. }
  166. NSError *fileReachabilityError;
  167. if (![_fileURL checkResourceIsReachableAndReturnError:&fileReachabilityError] ||
  168. ![self fileURLisFile:_fileURL]) {
  169. if (outError != NULL) {
  170. NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithCapacity:2];
  171. userInfo[NSLocalizedDescriptionKey] = [NSString
  172. stringWithFormat:@"File at URL: %@ is not reachable. "
  173. @"Ensure file URL is not a directory, symbolic link, or invalid url.",
  174. _fileURL.absoluteString];
  175. if (fileReachabilityError) {
  176. userInfo[NSUnderlyingErrorKey] = fileReachabilityError;
  177. }
  178. *outError = [NSError errorWithDomain:FIRStorageErrorDomainInternal
  179. code:FIRIMPLStorageErrorCodeUnknown
  180. userInfo:userInfo];
  181. }
  182. return NO;
  183. }
  184. return YES;
  185. }
  186. #pragma mark - Upload Management
  187. - (void)cancel {
  188. __weak FIRIMPLStorageUploadTask *weakSelf = self;
  189. [self dispatchAsync:^() {
  190. weakSelf.state = FIRIMPLStorageTaskStateCancelled;
  191. [weakSelf.uploadFetcher stopFetching];
  192. if (weakSelf.state != FIRIMPLStorageTaskStateSuccess) {
  193. weakSelf.metadata = weakSelf.uploadMetadata;
  194. }
  195. weakSelf.error = [FIRStorageErrors errorWithCode:FIRIMPLStorageErrorCodeCancelled];
  196. [weakSelf fireHandlersForStatus:FIRIMPLStorageTaskStatusFailure snapshot:weakSelf.snapshot];
  197. }];
  198. }
  199. - (void)pause {
  200. __weak FIRIMPLStorageUploadTask *weakSelf = self;
  201. [self dispatchAsync:^() {
  202. weakSelf.state = FIRIMPLStorageTaskStatePaused;
  203. [weakSelf.uploadFetcher pauseFetching];
  204. if (weakSelf.state != FIRIMPLStorageTaskStateSuccess) {
  205. weakSelf.metadata = weakSelf.uploadMetadata;
  206. }
  207. [weakSelf fireHandlersForStatus:FIRIMPLStorageTaskStatusPause snapshot:weakSelf.snapshot];
  208. }];
  209. }
  210. - (void)resume {
  211. __weak FIRIMPLStorageUploadTask *weakSelf = self;
  212. [self dispatchAsync:^() {
  213. weakSelf.state = FIRIMPLStorageTaskStateResuming;
  214. [weakSelf.uploadFetcher resumeFetching];
  215. if (weakSelf.state != FIRIMPLStorageTaskStateSuccess) {
  216. weakSelf.metadata = weakSelf.uploadMetadata;
  217. }
  218. [weakSelf fireHandlersForStatus:FIRIMPLStorageTaskStatusResume snapshot:weakSelf.snapshot];
  219. weakSelf.state = FIRIMPLStorageTaskStateRunning;
  220. }];
  221. }
  222. #pragma mark - Private Helpers
  223. - (BOOL)fileURLisFile:(NSURL *)fileURL {
  224. NSNumber *isFile = [NSNumber numberWithBool:NO];
  225. [fileURL getResourceValue:&isFile forKey:NSURLIsRegularFileKey error:nil];
  226. return [isFile boolValue];
  227. }
  228. @end