You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1959 lines
72 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  1. /* Copyright 2014 Google Inc. All rights reserved.
  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. */
  15. #if !defined(__has_feature) || !__has_feature(objc_arc)
  16. #error "This file requires ARC support."
  17. #endif
  18. #import "GTMSessionUploadFetcher.h"
  19. static NSString *const kGTMSessionIdentifierIsUploadChunkFetcherMetadataKey = @"_upChunk";
  20. static NSString *const kGTMSessionIdentifierUploadFileURLMetadataKey = @"_upFileURL";
  21. static NSString *const kGTMSessionIdentifierUploadFileLengthMetadataKey = @"_upFileLen";
  22. static NSString *const kGTMSessionIdentifierUploadLocationURLMetadataKey = @"_upLocURL";
  23. static NSString *const kGTMSessionIdentifierUploadMIMETypeMetadataKey = @"_uploadMIME";
  24. static NSString *const kGTMSessionIdentifierUploadChunkSizeMetadataKey = @"_upChSize";
  25. static NSString *const kGTMSessionIdentifierUploadCurrentOffsetMetadataKey = @"_upOffset";
  26. static NSString *const kGTMSessionHeaderXGoogUploadChunkGranularity = @"X-Goog-Upload-Chunk-Granularity";
  27. static NSString *const kGTMSessionHeaderXGoogUploadCommand = @"X-Goog-Upload-Command";
  28. static NSString *const kGTMSessionHeaderXGoogUploadContentLength = @"X-Goog-Upload-Content-Length";
  29. static NSString *const kGTMSessionHeaderXGoogUploadContentType = @"X-Goog-Upload-Content-Type";
  30. static NSString *const kGTMSessionHeaderXGoogUploadOffset = @"X-Goog-Upload-Offset";
  31. static NSString *const kGTMSessionHeaderXGoogUploadProtocol = @"X-Goog-Upload-Protocol";
  32. static NSString *const kGTMSessionXGoogUploadProtocolResumable = @"resumable";
  33. static NSString *const kGTMSessionHeaderXGoogUploadSizeReceived = @"X-Goog-Upload-Size-Received";
  34. static NSString *const kGTMSessionHeaderXGoogUploadStatus = @"X-Goog-Upload-Status";
  35. static NSString *const kGTMSessionHeaderXGoogUploadURL = @"X-Goog-Upload-URL";
  36. // Property of chunk fetchers identifying the parent upload fetcher. Non-retained NSValue.
  37. static NSString *const kGTMSessionUploadFetcherChunkParentKey = @"_uploadFetcherChunkParent";
  38. int64_t const kGTMSessionUploadFetcherUnknownFileSize = -1;
  39. int64_t const kGTMSessionUploadFetcherStandardChunkSize = (int64_t)LLONG_MAX;
  40. #if TARGET_OS_IPHONE
  41. int64_t const kGTMSessionUploadFetcherMaximumDemandBufferSize = 10 * 1024 * 1024; // 10 MB for iOS, watchOS, tvOS
  42. #else
  43. int64_t const kGTMSessionUploadFetcherMaximumDemandBufferSize = 100 * 1024 * 1024; // 100 MB for macOS
  44. #endif
  45. typedef NS_ENUM(NSUInteger, GTMSessionUploadFetcherStatus) {
  46. kStatusUnknown,
  47. kStatusActive,
  48. kStatusFinal,
  49. kStatusCancelled,
  50. };
  51. NSString *const kGTMSessionFetcherUploadLocationObtainedNotification =
  52. @"kGTMSessionFetcherUploadLocationObtainedNotification";
  53. #if !GTMSESSION_BUILD_COMBINED_SOURCES
  54. @interface GTMSessionFetcher (ProtectedMethods)
  55. // Access to non-public method on the parent fetcher class.
  56. - (void)stopFetchReleasingCallbacks:(BOOL)shouldReleaseCallbacks;
  57. - (void)createSessionIdentifierWithMetadata:(NSDictionary *)metadata;
  58. - (GTMSessionFetcherCompletionHandler)completionHandlerWithTarget:(id)target
  59. didFinishSelector:(SEL)finishedSelector;
  60. - (void)invokeOnCallbackQueue:(dispatch_queue_t)callbackQueue
  61. afterUserStopped:(BOOL)afterStopped
  62. block:(void (^)(void))block;
  63. - (NSTimer *)retryTimer;
  64. - (void)beginFetchForRetry;
  65. @property(readwrite, strong) NSData *downloadedData;
  66. - (void)releaseCallbacks;
  67. - (NSInteger)statusCodeUnsynchronized;
  68. - (BOOL)userStoppedFetching;
  69. @end
  70. #endif // !GTMSESSION_BUILD_COMBINED_SOURCES
  71. @interface GTMSessionUploadFetcher ()
  72. // Changing readonly to readwrite.
  73. @property(atomic, strong, readwrite) NSURLRequest *lastChunkRequest;
  74. @property(atomic, readwrite, assign) int64_t currentOffset;
  75. // Internal properties.
  76. @property(strong, atomic, GTM_NULLABLE) GTMSessionFetcher *fetcherInFlight; // Synchronized on self.
  77. @property(assign, atomic, getter=isSubdataGenerating) BOOL subdataGenerating;
  78. @property(assign, atomic) BOOL shouldInitiateOffsetQuery;
  79. @property(assign, atomic) int64_t uploadGranularity;
  80. @end
  81. @implementation GTMSessionUploadFetcher {
  82. GTMSessionFetcher *_chunkFetcher;
  83. // We'll call through to the delegate's completion handler.
  84. GTMSessionFetcherCompletionHandler _delegateCompletionHandler;
  85. dispatch_queue_t _delegateCallbackQueue;
  86. // The initial fetch's body length and bytes actually sent are
  87. // needed for calculating progress during subsequent chunk uploads
  88. int64_t _initialBodyLength;
  89. int64_t _initialBodySent;
  90. // The upload server address for the chunks of this upload session.
  91. NSURL *_uploadLocationURL;
  92. // _uploadData, _uploadDataProvider, or _uploadFileHandle may be set, but only one.
  93. NSData *_uploadData;
  94. NSFileHandle *_uploadFileHandle;
  95. GTMSessionUploadFetcherDataProvider _uploadDataProvider;
  96. NSURL *_uploadFileURL;
  97. int64_t _uploadFileLength;
  98. NSString *_uploadMIMEType;
  99. int64_t _chunkSize;
  100. int64_t _uploadGranularity;
  101. BOOL _isPaused;
  102. BOOL _isRestartedUpload;
  103. BOOL _shouldInitiateOffsetQuery;
  104. // Tied to useBackgroundSession property, since this property is applicable to chunk fetchers.
  105. BOOL _useBackgroundSessionOnChunkFetchers;
  106. // We keep the latest offset into the upload data just for progress reporting.
  107. int64_t _currentOffset;
  108. NSDictionary *_recentChunkReponseHeaders;
  109. NSInteger _recentChunkStatusCode;
  110. // For waiting, we need to know the fetcher in flight, if any, and if subdata generation
  111. // is in progress.
  112. GTMSessionFetcher *_fetcherInFlight;
  113. BOOL _isSubdataGenerating;
  114. BOOL _isCancelInFlight;
  115. GTMSessionUploadFetcherCancellationHandler _cancellationHandler;
  116. }
  117. + (void)load {
  118. [self uploadFetchersForBackgroundSessions];
  119. }
  120. + (instancetype)uploadFetcherWithRequest:(NSURLRequest *)request
  121. uploadMIMEType:(NSString *)uploadMIMEType
  122. chunkSize:(int64_t)chunkSize
  123. fetcherService:(GTMSessionFetcherService *)fetcherService {
  124. GTMSessionUploadFetcher *fetcher = [self uploadFetcherWithRequest:request
  125. fetcherService:fetcherService];
  126. [fetcher setLocationURL:nil
  127. uploadMIMEType:uploadMIMEType
  128. chunkSize:chunkSize];
  129. return fetcher;
  130. }
  131. + (instancetype)uploadFetcherWithLocation:(NSURL * GTM_NULLABLE_TYPE)uploadLocationURL
  132. uploadMIMEType:(NSString *)uploadMIMEType
  133. chunkSize:(int64_t)chunkSize
  134. fetcherService:(GTMSessionFetcherService *)fetcherService {
  135. GTMSessionUploadFetcher *fetcher = [self uploadFetcherWithRequest:nil
  136. fetcherService:fetcherService];
  137. [fetcher setLocationURL:uploadLocationURL
  138. uploadMIMEType:uploadMIMEType
  139. chunkSize:chunkSize];
  140. return fetcher;
  141. }
  142. + (instancetype)uploadFetcherForSessionIdentifierMetadata:(NSDictionary *)metadata {
  143. GTMSESSION_ASSERT_DEBUG(
  144. [metadata[kGTMSessionIdentifierIsUploadChunkFetcherMetadataKey] boolValue],
  145. @"Session identifier metadata is not for an upload fetcher: %@", metadata);
  146. NSNumber *uploadFileLengthNum = metadata[kGTMSessionIdentifierUploadFileLengthMetadataKey];
  147. GTMSESSION_ASSERT_DEBUG(uploadFileLengthNum != nil,
  148. @"Session metadata missing an UploadFileSize");
  149. if (uploadFileLengthNum == nil) return nil;
  150. int64_t uploadFileLength = [uploadFileLengthNum longLongValue];
  151. GTMSESSION_ASSERT_DEBUG(uploadFileLength >= 0, @"Session metadata UploadFileSize is unknown");
  152. NSString *uploadFileURLString = metadata[kGTMSessionIdentifierUploadFileURLMetadataKey];
  153. GTMSESSION_ASSERT_DEBUG(uploadFileURLString, @"Session metadata missing an UploadFileURL");
  154. if (uploadFileURLString == nil) return nil;
  155. NSURL *uploadFileURL = [NSURL URLWithString:uploadFileURLString];
  156. // There used to be a call here to NSURL checkResourceIsReachableAndReturnError: to check for the
  157. // existence of the file (also tried NSFileManager fileExistsAtPath:). We've determined
  158. // empirically that the check can fail at startup even when the upload file does in fact exist.
  159. // For now, we'll go ahead and restore the background upload fetcher. If the file doesn't exist,
  160. // it will fail later.
  161. NSString *uploadLocationURLString = metadata[kGTMSessionIdentifierUploadLocationURLMetadataKey];
  162. NSURL *uploadLocationURL =
  163. uploadLocationURLString ? [NSURL URLWithString:uploadLocationURLString] : nil;
  164. NSString *uploadMIMEType =
  165. metadata[kGTMSessionIdentifierUploadMIMETypeMetadataKey];
  166. int64_t uploadChunkSize =
  167. [metadata[kGTMSessionIdentifierUploadChunkSizeMetadataKey] longLongValue];
  168. if (uploadChunkSize <= 0) {
  169. uploadChunkSize = kGTMSessionUploadFetcherStandardChunkSize;
  170. }
  171. int64_t currentOffset =
  172. [metadata[kGTMSessionIdentifierUploadCurrentOffsetMetadataKey] longLongValue];
  173. GTMSESSION_ASSERT_DEBUG(currentOffset <= uploadFileLength,
  174. @"CurrentOffset (%lld) exceeds UploadFileSize (%lld)",
  175. currentOffset, uploadFileLength);
  176. if (currentOffset > uploadFileLength) return nil;
  177. GTMSessionUploadFetcher *uploadFetcher = [self uploadFetcherWithLocation:uploadLocationURL
  178. uploadMIMEType:uploadMIMEType
  179. chunkSize:uploadChunkSize
  180. fetcherService:nil];
  181. // Set the upload file length before setting the upload file URL tries to determine the length.
  182. [uploadFetcher setUploadFileLength:uploadFileLength];
  183. uploadFetcher.uploadFileURL = uploadFileURL;
  184. uploadFetcher.sessionUserInfo = metadata;
  185. uploadFetcher.useBackgroundSession = YES;
  186. uploadFetcher.currentOffset = currentOffset;
  187. uploadFetcher.delegateCallbackQueue = uploadFetcher.callbackQueue;
  188. uploadFetcher.allowedInsecureSchemes = @[ @"http" ]; // Allowed on restored upload fetcher.
  189. return uploadFetcher;
  190. }
  191. + (instancetype)uploadFetcherWithRequest:(NSURLRequest *)request
  192. fetcherService:(GTMSessionFetcherService *)fetcherService {
  193. // Internal utility method for instantiating fetchers
  194. GTMSessionUploadFetcher *fetcher;
  195. if ([fetcherService isKindOfClass:[GTMSessionFetcherService class]]) {
  196. fetcher = [fetcherService fetcherWithRequest:request
  197. fetcherClass:self];
  198. } else {
  199. fetcher = [self fetcherWithRequest:request];
  200. }
  201. fetcher.useBackgroundSession = YES;
  202. return fetcher;
  203. }
  204. + (NSPointerArray *)uploadFetcherPointerArrayForBackgroundSessions {
  205. static NSPointerArray *gUploadFetcherPointerArrayForBackgroundSessions = nil;
  206. static dispatch_once_t onceToken;
  207. dispatch_once(&onceToken, ^{
  208. gUploadFetcherPointerArrayForBackgroundSessions = [NSPointerArray weakObjectsPointerArray];
  209. });
  210. return gUploadFetcherPointerArrayForBackgroundSessions;
  211. }
  212. + (instancetype)uploadFetcherForSessionIdentifier:(NSString *)sessionIdentifier {
  213. GTMSESSION_ASSERT_DEBUG(sessionIdentifier != nil, @"Invalid session identifier");
  214. NSArray *uploadFetchersForBackgroundSessions = [self uploadFetchersForBackgroundSessions];
  215. for (GTMSessionUploadFetcher *uploadFetcher in uploadFetchersForBackgroundSessions) {
  216. if ([uploadFetcher.chunkFetcher.sessionIdentifier isEqual:sessionIdentifier]) {
  217. return uploadFetcher;
  218. }
  219. }
  220. return nil;
  221. }
  222. + (NSArray *)uploadFetchersForBackgroundSessions {
  223. NSMutableSet *restoredSessionIdentifiers = [[NSMutableSet alloc] init];
  224. NSMutableArray *uploadFetchers = [[NSMutableArray alloc] init];
  225. NSPointerArray *uploadFetcherPointerArray = [self uploadFetcherPointerArrayForBackgroundSessions];
  226. // Collect the background session upload fetchers that are still in memory.
  227. @synchronized(uploadFetcherPointerArray) {
  228. [uploadFetcherPointerArray compact];
  229. for (GTMSessionUploadFetcher *uploadFetcher in uploadFetcherPointerArray) {
  230. NSString *sessionIdentifier = uploadFetcher.chunkFetcher.sessionIdentifier;
  231. if (sessionIdentifier) {
  232. [restoredSessionIdentifiers addObject:sessionIdentifier];
  233. [uploadFetchers addObject:uploadFetcher];
  234. }
  235. }
  236. } // @synchronized(uploadFetcherPointerArray)
  237. // The system may have other ongoing background upload sessions. Restore upload fetchers for those
  238. // too.
  239. NSArray *fetchers = [GTMSessionFetcher fetchersForBackgroundSessions];
  240. for (GTMSessionFetcher *fetcher in fetchers) {
  241. NSString *sessionIdentifier = fetcher.sessionIdentifier;
  242. if (!sessionIdentifier || [restoredSessionIdentifiers containsObject:sessionIdentifier]) {
  243. continue;
  244. }
  245. NSDictionary *sessionIdentifierMetadata = [fetcher sessionIdentifierMetadata];
  246. if (sessionIdentifierMetadata == nil) {
  247. continue;
  248. }
  249. if (![sessionIdentifierMetadata[kGTMSessionIdentifierIsUploadChunkFetcherMetadataKey] boolValue]) {
  250. continue;
  251. }
  252. GTMSessionUploadFetcher *uploadFetcher =
  253. [self uploadFetcherForSessionIdentifierMetadata:sessionIdentifierMetadata];
  254. if (uploadFetcher == nil) {
  255. // Something went wrong with this upload fetcher, so kill the restored chunk fetcher.
  256. [fetcher stopFetching];
  257. continue;
  258. }
  259. [uploadFetchers addObject:uploadFetcher];
  260. uploadFetcher->_chunkFetcher = fetcher;
  261. uploadFetcher->_fetcherInFlight = fetcher;
  262. [uploadFetcher attachSendProgressBlockToChunkFetcher:fetcher];
  263. fetcher.completionHandler =
  264. [fetcher completionHandlerWithTarget:uploadFetcher
  265. didFinishSelector:@selector(chunkFetcher:finishedWithData:error:)];
  266. GTMSESSION_LOG_DEBUG(@"%@ restoring upload fetcher %@ for chunk fetcher %@",
  267. [self class], uploadFetcher, fetcher);
  268. }
  269. return uploadFetchers;
  270. }
  271. - (void)setUploadData:(NSData *)data {
  272. BOOL changed = NO;
  273. @synchronized(self) {
  274. GTMSessionMonitorSynchronized(self);
  275. if (_uploadData != data) {
  276. _uploadData = data;
  277. changed = YES;
  278. }
  279. }
  280. if (changed) {
  281. [self setupRequestHeaders];
  282. }
  283. }
  284. - (NSData *)uploadData {
  285. @synchronized(self) {
  286. GTMSessionMonitorSynchronized(self);
  287. return _uploadData;
  288. }
  289. }
  290. - (void)setUploadFileHandle:(NSFileHandle *)fh {
  291. BOOL changed = NO;
  292. @synchronized(self) {
  293. GTMSessionMonitorSynchronized(self);
  294. if (_uploadFileHandle != fh) {
  295. _uploadFileHandle = fh;
  296. changed = YES;
  297. }
  298. }
  299. if (changed) {
  300. [self setupRequestHeaders];
  301. }
  302. }
  303. - (NSFileHandle *)uploadFileHandle {
  304. @synchronized(self) {
  305. GTMSessionMonitorSynchronized(self);
  306. return _uploadFileHandle;
  307. }
  308. }
  309. - (void)setUploadFileURL:(NSURL *)uploadURL {
  310. BOOL changed = NO;
  311. @synchronized(self) {
  312. GTMSessionMonitorSynchronized(self);
  313. if (_uploadFileURL != uploadURL) {
  314. _uploadFileURL = uploadURL;
  315. changed = YES;
  316. }
  317. }
  318. if (changed) {
  319. [self setupRequestHeaders];
  320. }
  321. }
  322. - (NSURL *)uploadFileURL {
  323. @synchronized(self) {
  324. GTMSessionMonitorSynchronized(self);
  325. return _uploadFileURL;
  326. }
  327. }
  328. - (void)setUploadFileLength:(int64_t)fullLength {
  329. @synchronized(self) {
  330. GTMSessionMonitorSynchronized(self);
  331. if (_uploadFileLength == kGTMSessionUploadFetcherUnknownFileSize &&
  332. fullLength != kGTMSessionUploadFetcherUnknownFileSize) {
  333. _uploadFileLength = fullLength;
  334. }
  335. }
  336. }
  337. - (void)setUploadDataLength:(int64_t)fullLength
  338. provider:(GTMSessionUploadFetcherDataProvider)block {
  339. @synchronized(self) {
  340. GTMSessionMonitorSynchronized(self);
  341. _uploadDataProvider = [block copy];
  342. _uploadFileLength = fullLength;
  343. }
  344. [self setupRequestHeaders];
  345. }
  346. - (GTMSessionUploadFetcherDataProvider)uploadDataProvider {
  347. @synchronized(self) {
  348. GTMSessionMonitorSynchronized(self);
  349. return _uploadDataProvider;
  350. }
  351. }
  352. - (void)setUploadMIMEType:(NSString *)uploadMIMEType {
  353. GTMSESSION_ASSERT_DEBUG(0, @"TODO: disallow setUploadMIMEType by making declaration readonly");
  354. // (and uploadMIMEType, chunksize, currentOffset)
  355. @synchronized(self) {
  356. GTMSessionMonitorSynchronized(self);
  357. _uploadMIMEType = uploadMIMEType;
  358. }
  359. }
  360. - (NSString *)uploadMIMEType {
  361. @synchronized(self) {
  362. GTMSessionMonitorSynchronized(self);
  363. return _uploadMIMEType;
  364. }
  365. }
  366. - (void)setChunkSize:(int64_t)chunkSize {
  367. @synchronized(self) {
  368. GTMSessionMonitorSynchronized(self);
  369. _chunkSize = chunkSize;
  370. }
  371. }
  372. - (int64_t)chunkSize {
  373. @synchronized(self) {
  374. GTMSessionMonitorSynchronized(self);
  375. return _chunkSize;
  376. }
  377. }
  378. - (void)setupRequestHeaders {
  379. GTMSessionCheckNotSynchronized(self);
  380. #if DEBUG
  381. @synchronized(self) {
  382. GTMSessionMonitorSynchronized(self);
  383. int hasData = (_uploadData != nil) ? 1 : 0;
  384. int hasFileHandle = (_uploadFileHandle != nil) ? 1 : 0;
  385. int hasFileURL = (_uploadFileURL != nil) ? 1 : 0;
  386. int hasUploadDataProvider = (_uploadDataProvider != nil) ? 1 : 0;
  387. int numberOfSources = hasData + hasFileHandle + hasFileURL + hasUploadDataProvider;
  388. #pragma unused(numberOfSources)
  389. GTMSESSION_ASSERT_DEBUG(numberOfSources == 1,
  390. @"Need just one upload source (%d)", numberOfSources);
  391. } // @synchronized(self)
  392. #endif
  393. // Add our custom headers to the initial request indicating the data
  394. // type and total size to be delivered later in the chunk requests.
  395. NSMutableURLRequest *mutableRequest = [self.request mutableCopy];
  396. GTMSESSION_ASSERT_DEBUG((mutableRequest == nil) != (_uploadLocationURL == nil),
  397. @"Request and location are mutually exclusive");
  398. if (!mutableRequest) return;
  399. [mutableRequest setValue:kGTMSessionXGoogUploadProtocolResumable
  400. forHTTPHeaderField:kGTMSessionHeaderXGoogUploadProtocol];
  401. [mutableRequest setValue:@"start"
  402. forHTTPHeaderField:kGTMSessionHeaderXGoogUploadCommand];
  403. [mutableRequest setValue:_uploadMIMEType
  404. forHTTPHeaderField:kGTMSessionHeaderXGoogUploadContentType];
  405. [mutableRequest setValue:@([self fullUploadLength]).stringValue
  406. forHTTPHeaderField:kGTMSessionHeaderXGoogUploadContentLength];
  407. NSString *method = mutableRequest.HTTPMethod;
  408. if (method == nil || [method caseInsensitiveCompare:@"GET"] == NSOrderedSame) {
  409. [mutableRequest setHTTPMethod:@"POST"];
  410. }
  411. // Ensure the user agent header identifies this to the upload server as a
  412. // GTMSessionUploadFetcher client. The /1 can be incremented in the unlikely circumstance
  413. // we need to make a bug fix in the client that the server can recognize.
  414. NSString *const kUserAgentStub = @"(GTMSUF/1)";
  415. NSString *userAgent = [mutableRequest valueForHTTPHeaderField:@"User-Agent"];
  416. if (userAgent == nil
  417. || [userAgent rangeOfString:kUserAgentStub].location == NSNotFound) {
  418. if (userAgent.length == 0) {
  419. userAgent = GTMFetcherStandardUserAgentString(nil);
  420. }
  421. userAgent = [userAgent stringByAppendingFormat:@" %@", kUserAgentStub];
  422. [mutableRequest setValue:userAgent forHTTPHeaderField:@"User-Agent"];
  423. }
  424. [self setRequest:mutableRequest];
  425. }
  426. - (void)setLocationURL:(NSURL * GTM_NULLABLE_TYPE)location
  427. uploadMIMEType:(NSString *)uploadMIMEType
  428. chunkSize:(int64_t)chunkSize {
  429. @synchronized(self) {
  430. GTMSessionMonitorSynchronized(self);
  431. GTMSESSION_ASSERT_DEBUG(chunkSize > 0, @"chunk size is zero");
  432. // When resuming an upload, set the known upload target URL.
  433. _uploadLocationURL = location;
  434. _uploadMIMEType = uploadMIMEType;
  435. _chunkSize = chunkSize;
  436. // Indicate that we've not yet determined the file handle's length
  437. _uploadFileLength = kGTMSessionUploadFetcherUnknownFileSize;
  438. // Indicate that we've not yet determined the upload fetcher status
  439. _recentChunkStatusCode = -1;
  440. // If this is restarting an upload begun by another fetcher,
  441. // the location is specified but the request is nil
  442. _isRestartedUpload = (location != nil);
  443. } // @synchronized(self)
  444. }
  445. - (int64_t)fullUploadLength {
  446. int64_t result;
  447. @synchronized(self) {
  448. GTMSessionMonitorSynchronized(self);
  449. if (_uploadData) {
  450. result = (int64_t)_uploadData.length;
  451. } else {
  452. if (_uploadFileLength == kGTMSessionUploadFetcherUnknownFileSize) {
  453. if (_uploadFileHandle) {
  454. // First time through, seek to end to determine file length
  455. _uploadFileLength = (int64_t)[_uploadFileHandle seekToEndOfFile];
  456. } else if (_uploadDataProvider) {
  457. // _uploadFileLength is set when the _uploadDataProvider is set.
  458. GTMSESSION_ASSERT_DEBUG(_uploadFileLength >= 0, @"No uploadDataProvider length set");
  459. } else {
  460. NSNumber *filesizeNum;
  461. NSError *valueError;
  462. if ([_uploadFileURL getResourceValue:&filesizeNum
  463. forKey:NSURLFileSizeKey
  464. error:&valueError]) {
  465. _uploadFileLength = filesizeNum.longLongValue;
  466. } else {
  467. GTMSESSION_ASSERT_DEBUG(NO, @"Cannot get file size: %@\n %@",
  468. valueError, _uploadFileURL.path);
  469. _uploadFileLength = 0;
  470. }
  471. }
  472. }
  473. result = _uploadFileLength;
  474. }
  475. } // @synchronized(self)
  476. return result;
  477. }
  478. // Make a subdata of the upload data.
  479. - (void)generateChunkSubdataWithOffset:(int64_t)offset
  480. length:(int64_t)length
  481. response:(GTMSessionUploadFetcherDataProviderResponse)response {
  482. GTMSessionUploadFetcherDataProvider uploadDataProvider = self.uploadDataProvider;
  483. if (uploadDataProvider) {
  484. uploadDataProvider(offset, length, response);
  485. return;
  486. }
  487. NSData *uploadData = self.uploadData;
  488. if (uploadData) {
  489. // NSData provided.
  490. NSData *resultData;
  491. if (offset == 0 && length == (int64_t)uploadData.length) {
  492. resultData = uploadData;
  493. } else {
  494. int64_t dataLength = (int64_t)uploadData.length;
  495. // Ensure our range is valid. b/18007814
  496. if (offset + length > dataLength) {
  497. NSString *errorMessage = [NSString stringWithFormat:
  498. @"Range invalid for upload data. offset: %lld\tlength: %lld\tdataLength: %lld",
  499. offset, length, dataLength];
  500. GTMSESSION_ASSERT_DEBUG(NO, @"%@", errorMessage);
  501. response(nil,
  502. kGTMSessionUploadFetcherUnknownFileSize,
  503. [self uploadChunkUnavailableErrorWithDescription:errorMessage]);
  504. return;
  505. }
  506. NSRange range = NSMakeRange((NSUInteger)offset, (NSUInteger)length);
  507. @try {
  508. resultData = [uploadData subdataWithRange:range];
  509. }
  510. @catch (NSException *exception) {
  511. NSString *errorMessage = exception.description;
  512. GTMSESSION_ASSERT_DEBUG(NO, @"%@", errorMessage);
  513. response(nil,
  514. kGTMSessionUploadFetcherUnknownFileSize,
  515. [self uploadChunkUnavailableErrorWithDescription:errorMessage]);
  516. return;
  517. }
  518. }
  519. response(resultData, kGTMSessionUploadFetcherUnknownFileSize, nil);
  520. return;
  521. }
  522. NSURL *uploadFileURL = self.uploadFileURL;
  523. if (uploadFileURL) {
  524. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  525. [self generateChunkSubdataFromFileURL:uploadFileURL
  526. offset:offset
  527. length:length
  528. response:response];
  529. });
  530. return;
  531. }
  532. GTMSESSION_ASSERT_DEBUG(_uploadFileHandle, @"Unexpectedly missing upload data package");
  533. NSFileHandle *uploadFileHandle = self.uploadFileHandle;
  534. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  535. [self generateChunkSubdataFromFileHandle:uploadFileHandle
  536. offset:offset
  537. length:length
  538. response:response];
  539. });
  540. }
  541. - (void)generateChunkSubdataFromFileHandle:(NSFileHandle *)fileHandle
  542. offset:(int64_t)offset
  543. length:(int64_t)length
  544. response:(GTMSessionUploadFetcherDataProviderResponse)response {
  545. NSData *resultData;
  546. NSError *error;
  547. @try {
  548. [fileHandle seekToFileOffset:(unsigned long long)offset];
  549. resultData = [fileHandle readDataOfLength:(NSUInteger)length];
  550. }
  551. @catch (NSException *exception) {
  552. GTMSESSION_ASSERT_DEBUG(NO, @"uploadFileHandle failed to read, %@", exception);
  553. error = [self uploadChunkUnavailableErrorWithDescription:exception.description];
  554. }
  555. // The response always re-dispatches to the main thread, so we skip doing that here.
  556. response(resultData, kGTMSessionUploadFetcherUnknownFileSize, error);
  557. }
  558. - (void)generateChunkSubdataFromFileURL:(NSURL *)fileURL
  559. offset:(int64_t)offset
  560. length:(int64_t)length
  561. response:(GTMSessionUploadFetcherDataProviderResponse)response {
  562. GTMSessionCheckNotSynchronized(self);
  563. NSData *resultData;
  564. NSError *error;
  565. int64_t fullUploadLength = [self fullUploadLength];
  566. NSData *mappedData =
  567. [NSData dataWithContentsOfURL:fileURL
  568. options:NSDataReadingMappedAlways + NSDataReadingUncached
  569. error:&error];
  570. if (!mappedData) {
  571. // We could not create an NSData by memory-mapping the file.
  572. #if TARGET_IPHONE_SIMULATOR
  573. // NSTemporaryDirectory() can differ in the simulator between app restarts,
  574. // yet the contents for the new path remains unchanged, so try the latest temp path.
  575. if ([error.domain isEqual:NSCocoaErrorDomain] && (error.code == NSFileReadNoSuchFileError)) {
  576. NSString *filename = [fileURL lastPathComponent];
  577. NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
  578. NSURL *newFileURL = [NSURL fileURLWithPath:filePath];
  579. if (![newFileURL isEqual:fileURL]) {
  580. [self generateChunkSubdataFromFileURL:newFileURL
  581. offset:offset
  582. length:length
  583. response:response];
  584. return;
  585. }
  586. }
  587. #endif
  588. // If the file is just too large to create an NSData for, or if for some other reason we can't
  589. // map it, create an NSFileHandle instead to read a subset into an NSData.
  590. #if DEBUG
  591. NSNumber *fileSizeNum;
  592. BOOL hasFileSize = [fileURL getResourceValue:&fileSizeNum forKey:NSURLFileSizeKey error:NULL];
  593. GTMSESSION_LOG_DEBUG(@"Note: uploadFileURL is falling back to creating upload chunks by reading"
  594. @" an NSFileHandle since uploadFileURL failed to map the upload file,"
  595. @" file size %@, %@",
  596. hasFileSize ? fileSizeNum : @"unknown", error);
  597. #endif
  598. NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingFromURL:fileURL
  599. error:&error];
  600. if (fileHandle != nil) {
  601. [self generateChunkSubdataFromFileHandle:fileHandle
  602. offset:offset
  603. length:length
  604. response:response];
  605. return;
  606. }
  607. GTMSESSION_ASSERT_DEBUG(NO, @"uploadFileURL failed to read, %@", error);
  608. // Fall through with the error.
  609. } else {
  610. // Successfully created an NSData by memory-mapping the file.
  611. if ((NSUInteger)(offset + length) > mappedData.length) {
  612. NSString *errorMessage = [NSString stringWithFormat:
  613. @"Range invalid for upload data. offset: %lld\tlength: %lld\tdataLength: %lld\texpected UploadLength: %lld",
  614. offset, length, (long long)mappedData.length, fullUploadLength];
  615. GTMSESSION_ASSERT_DEBUG(NO, @"%@", errorMessage);
  616. response(nil,
  617. kGTMSessionUploadFetcherUnknownFileSize,
  618. [self uploadChunkUnavailableErrorWithDescription:errorMessage]);
  619. return;
  620. }
  621. if (offset > 0 || length < fullUploadLength) {
  622. NSRange range = NSMakeRange((NSUInteger)offset, (NSUInteger)length);
  623. resultData = [mappedData subdataWithRange:range];
  624. } else {
  625. resultData = mappedData;
  626. }
  627. }
  628. // The response always re-dispatches to the main thread, so we skip re-dispatching here.
  629. response(resultData, kGTMSessionUploadFetcherUnknownFileSize, error);
  630. }
  631. - (NSError *)uploadChunkUnavailableErrorWithDescription:(NSString *)description {
  632. // The description in the userInfo is intended as a clue to programmers, not
  633. // for client code to examine or rely on.
  634. NSDictionary *userInfo = @{ @"description" : description };
  635. return [NSError errorWithDomain:kGTMSessionFetcherErrorDomain
  636. code:GTMSessionFetcherErrorUploadChunkUnavailable
  637. userInfo:userInfo];
  638. }
  639. - (NSError *)prematureFailureErrorWithUserInfo:(NSDictionary *)userInfo {
  640. // An error for if we get an unexpected status from the upload server or
  641. // otherwise cannot continue. This is an issue beyond the upload protocol;
  642. // there's no way the client can do anything useful except give up.
  643. NSError *error = [NSError errorWithDomain:kGTMSessionFetcherStatusDomain
  644. code:501 // Not implemented
  645. userInfo:userInfo];
  646. return error;
  647. }
  648. + (GTMSessionUploadFetcherStatus)uploadStatusFromResponseHeaders:(NSDictionary *)responseHeaders {
  649. NSString *statusString = [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadStatus];
  650. if ([statusString isEqual:@"active"]) {
  651. return kStatusActive;
  652. }
  653. if ([statusString isEqual:@"final"]) {
  654. return kStatusFinal;
  655. }
  656. if ([statusString isEqual:@"cancelled"]) {
  657. return kStatusCancelled;
  658. }
  659. return kStatusUnknown;
  660. }
  661. #pragma mark Method overrides affecting the initial fetch only
  662. - (void)setCompletionHandler:(GTMSessionFetcherCompletionHandler)handler {
  663. @synchronized(self) {
  664. GTMSessionMonitorSynchronized(self);
  665. _delegateCompletionHandler = handler;
  666. }
  667. }
  668. - (void)setDelegateCallbackQueue:(dispatch_queue_t GTM_NULLABLE_TYPE)queue {
  669. @synchronized(self) {
  670. GTMSessionMonitorSynchronized(self);
  671. _delegateCallbackQueue = queue;
  672. }
  673. }
  674. - (dispatch_queue_t GTM_NULLABLE_TYPE)delegateCallbackQueue {
  675. @synchronized(self) {
  676. GTMSessionMonitorSynchronized(self);
  677. return _delegateCallbackQueue;
  678. }
  679. }
  680. - (BOOL)isRestartedUpload {
  681. @synchronized(self) {
  682. GTMSessionMonitorSynchronized(self);
  683. return _isRestartedUpload;
  684. }
  685. }
  686. - (GTMSessionFetcher * GTM_NULLABLE_TYPE)chunkFetcher {
  687. @synchronized(self) {
  688. GTMSessionMonitorSynchronized(self);
  689. return _chunkFetcher;
  690. }
  691. }
  692. - (void)setChunkFetcher:(GTMSessionFetcher * GTM_NULLABLE_TYPE)fetcher {
  693. @synchronized(self) {
  694. GTMSessionMonitorSynchronized(self);
  695. _chunkFetcher = fetcher;
  696. }
  697. }
  698. - (void)setFetcherInFlight:(GTMSessionFetcher * GTM_NULLABLE_TYPE)fetcher {
  699. @synchronized(self) {
  700. GTMSessionMonitorSynchronized(self);
  701. _fetcherInFlight = fetcher;
  702. }
  703. }
  704. - (GTMSessionFetcher * GTM_NULLABLE_TYPE)fetcherInFlight {
  705. @synchronized(self) {
  706. GTMSessionMonitorSynchronized(self);
  707. return _fetcherInFlight;
  708. }
  709. }
  710. - (void)setCancellationHandler:(GTMSessionUploadFetcherCancellationHandler GTM_NULLABLE_TYPE)
  711. cancellationHandler {
  712. @synchronized(self) {
  713. GTMSessionMonitorSynchronized(self);
  714. _cancellationHandler = cancellationHandler;
  715. }
  716. }
  717. - (GTMSessionUploadFetcherCancellationHandler GTM_NULLABLE_TYPE)cancellationHandler {
  718. @synchronized(self) {
  719. GTMSessionMonitorSynchronized(self);
  720. return _cancellationHandler;
  721. }
  722. }
  723. - (void)beginFetchForRetry {
  724. GTMSessionCheckNotSynchronized(self);
  725. // Override the superclass to reset the initial body length and fetcher-in-flight,
  726. // then call the superclass implementation.
  727. [self setInitialBodyLength:[self bodyLength]];
  728. GTMSESSION_ASSERT_DEBUG(self.fetcherInFlight == nil, @"unexpected fetcher in flight: %@",
  729. self.fetcherInFlight);
  730. self.fetcherInFlight = self;
  731. [super beginFetchForRetry];
  732. }
  733. - (void)beginFetchWithCompletionHandler:(GTMSessionFetcherCompletionHandler)handler {
  734. GTMSessionCheckNotSynchronized(self);
  735. [self setInitialBodyLength:[self bodyLength]];
  736. // We'll hold onto the superclass's callback queue so we can invoke the handler
  737. // even after the superclass has released the queue and its callback handler, as
  738. // happens during auth failure.
  739. [self setDelegateCallbackQueue:self.callbackQueue];
  740. self.completionHandler = handler;
  741. if ([self isRestartedUpload]) {
  742. // When restarting an upload, we know the destination location for chunk fetches,
  743. // but we need to query to find the initial offset.
  744. if (![self isPaused]) {
  745. [self sendQueryForUploadOffsetWithFetcherProperties:self.properties];
  746. }
  747. return;
  748. }
  749. // We don't want to call into the client's completion block immediately
  750. // after the finish of the initial connection (the delegate is called only
  751. // when uploading finishes), so we substitute our own completion block to be
  752. // called when the initial connection finishes
  753. GTMSESSION_ASSERT_DEBUG(self.fetcherInFlight == nil, @"unexpected fetcher in flight: %@",
  754. self.fetcherInFlight);
  755. self.fetcherInFlight = self;
  756. [super beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
  757. self.fetcherInFlight = nil;
  758. // callback
  759. BOOL hasTestBlock = (self.testBlock != nil);
  760. if (![self isRestartedUpload] && !hasTestBlock) {
  761. if (error == nil) {
  762. [self beginChunkFetches];
  763. } else {
  764. if ([self retryTimer] == nil) {
  765. [self invokeFinalCallbackWithData:nil
  766. error:error
  767. shouldInvalidateLocation:YES];
  768. }
  769. }
  770. } else {
  771. // If there was no initial request, then this fetch is resuming some
  772. // other uploadFetcher's initial request, and the superclass's connection
  773. // is never used, so at this point we call the user's actual completion
  774. // block.
  775. if (!hasTestBlock) {
  776. [self invokeFinalCallbackWithData:data
  777. error:error
  778. shouldInvalidateLocation:YES];
  779. } else {
  780. // There was a test block, so we won't do chunk fetches, but we simulate obtaining
  781. // the data to be uploaded from the upload data provider block or the file handle,
  782. // and then call back.
  783. [self generateChunkSubdataWithOffset:0
  784. length:[self fullUploadLength]
  785. response:^(NSData *generateData, int64_t fullUploadLength, NSError *generateError) {
  786. [self invokeFinalCallbackWithData:data
  787. error:error
  788. shouldInvalidateLocation:YES];
  789. }];
  790. }
  791. }
  792. }];
  793. }
  794. - (void)beginChunkFetches {
  795. GTMSessionCheckNotSynchronized(self);
  796. #if DEBUG
  797. // The initial response of the resumable upload protocol should have an
  798. // empty body
  799. //
  800. // This assert typically happens because the upload create/edit link URL was
  801. // not supplied with the request, and the server is thus expecting a non-
  802. // resumable request/response.
  803. if (self.downloadedData.length > 0) {
  804. NSData *downloadedData = self.downloadedData;
  805. NSString *str = [[NSString alloc] initWithData:downloadedData
  806. encoding:NSUTF8StringEncoding];
  807. #pragma unused(str)
  808. GTMSESSION_ASSERT_DEBUG(NO, @"unexpected response data (uploading to the wrong URL?)\n%@", str);
  809. }
  810. #endif
  811. // We need to get the upload URL from the location header to continue.
  812. NSDictionary *responseHeaders = [self responseHeaders];
  813. [self retrieveUploadChunkGranularityFromResponseHeaders:responseHeaders];
  814. GTMSessionUploadFetcherStatus uploadStatus =
  815. [[self class] uploadStatusFromResponseHeaders:responseHeaders];
  816. GTMSESSION_ASSERT_DEBUG(uploadStatus != kStatusUnknown,
  817. @"beginChunkFetches has unexpected upload status for headers %@", responseHeaders);
  818. BOOL isPrematureStop = (uploadStatus == kStatusFinal) || (uploadStatus == kStatusCancelled);
  819. NSString *uploadLocationURLStr = [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadURL];
  820. BOOL hasUploadLocation = (uploadLocationURLStr.length > 0);
  821. if (isPrematureStop || !hasUploadLocation) {
  822. GTMSESSION_ASSERT_DEBUG(NO, @"Premature failure: upload-status:\"%@\" location:%@",
  823. [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadStatus], uploadLocationURLStr);
  824. // We cannot continue since we do not know the location to use
  825. // as our upload destination.
  826. NSDictionary *userInfo = nil;
  827. NSData *downloadedData = self.downloadedData;
  828. if (downloadedData.length > 0) {
  829. userInfo = @{ kGTMSessionFetcherStatusDataKey : downloadedData };
  830. }
  831. NSError *failureError = [self prematureFailureErrorWithUserInfo:userInfo];
  832. [self invokeFinalCallbackWithData:nil
  833. error:failureError
  834. shouldInvalidateLocation:YES];
  835. return;
  836. }
  837. self.uploadLocationURL = [NSURL URLWithString:uploadLocationURLStr];
  838. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  839. [nc postNotificationName:kGTMSessionFetcherUploadLocationObtainedNotification
  840. object:self];
  841. // we've now sent all of the initial post body data, so we need to include
  842. // its size in future progress indicator callbacks
  843. [self setInitialBodySent:[self initialBodyLength]];
  844. // just in case the user paused us during the initial fetch...
  845. if (![self isPaused]) {
  846. [self uploadNextChunkWithOffset:0];
  847. }
  848. }
  849. - (void)URLSession:(NSURLSession *)session
  850. task:(NSURLSessionTask *)task
  851. didSendBodyData:(int64_t)bytesSent
  852. totalBytesSent:(int64_t)totalBytesSent
  853. totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
  854. // Overrides the superclass.
  855. [self invokeDelegateWithDidSendBytes:bytesSent
  856. totalBytesSent:totalBytesSent
  857. totalBytesExpectedToSend:totalBytesExpectedToSend + [self fullUploadLength]];
  858. }
  859. - (BOOL)shouldReleaseCallbacksUponCompletion {
  860. // Overrides the superclass.
  861. // We don't want the superclass to release the delegate and callback
  862. // blocks once the initial fetch has finished
  863. //
  864. // This is invoked for only successful completion of the connection;
  865. // an error always will invoke and release the callbacks
  866. return NO;
  867. }
  868. - (void)invokeFinalCallbackWithData:(NSData *)data
  869. error:(NSError *)error
  870. shouldInvalidateLocation:(BOOL)shouldInvalidateLocation {
  871. @synchronized(self) {
  872. GTMSessionMonitorSynchronized(self);
  873. if (shouldInvalidateLocation) {
  874. _uploadLocationURL = nil;
  875. }
  876. dispatch_queue_t queue = _delegateCallbackQueue;
  877. GTMSessionFetcherCompletionHandler handler = _delegateCompletionHandler;
  878. if (queue && handler) {
  879. [self invokeOnCallbackQueue:queue
  880. afterUserStopped:NO
  881. block:^{
  882. handler(data, error);
  883. }];
  884. }
  885. } // @synchronized(self)
  886. [self releaseUploadAndBaseCallbacks:!self.userStoppedFetching];
  887. }
  888. - (void)releaseUploadAndBaseCallbacks:(BOOL)shouldReleaseCancellation {
  889. @synchronized(self) {
  890. GTMSessionMonitorSynchronized(self);
  891. _delegateCallbackQueue = nil;
  892. _delegateCompletionHandler = nil;
  893. _uploadDataProvider = nil;
  894. if (shouldReleaseCancellation) {
  895. _cancellationHandler = nil;
  896. }
  897. }
  898. // Release the base class's callbacks, too, if needed.
  899. [self releaseCallbacks];
  900. }
  901. - (void)stopFetchReleasingCallbacks:(BOOL)shouldReleaseCallbacks {
  902. GTMSessionCheckNotSynchronized(self);
  903. // Clear _fetcherInFlight when stopped. Moved from stopFetching, since that's a public method,
  904. // where this method does the work. Fixes issue clearing value when retryBlock included.
  905. GTMSessionFetcher *fetcherInFlight = self.fetcherInFlight;
  906. if (fetcherInFlight == self) {
  907. self.fetcherInFlight = nil;
  908. }
  909. [super stopFetchReleasingCallbacks:shouldReleaseCallbacks];
  910. if (shouldReleaseCallbacks) {
  911. [self releaseUploadAndBaseCallbacks:NO];
  912. }
  913. }
  914. #pragma mark Chunk fetching methods
  915. - (void)uploadNextChunkWithOffset:(int64_t)offset {
  916. // use the properties in each chunk fetcher
  917. NSDictionary *props = [self properties];
  918. [self uploadNextChunkWithOffset:offset
  919. fetcherProperties:props];
  920. }
  921. - (void)sendQueryForUploadOffsetWithFetcherProperties:(NSDictionary *)props {
  922. GTMSessionFetcher *queryFetcher = [self uploadFetcherWithProperties:props
  923. isQueryFetch:YES];
  924. queryFetcher.bodyData = [NSData data];
  925. NSString *originalComment = self.comment;
  926. [queryFetcher setCommentWithFormat:@"%@ (query offset)",
  927. originalComment ? originalComment : @"upload"];
  928. [queryFetcher setRequestValue:@"query" forHTTPHeaderField:kGTMSessionHeaderXGoogUploadCommand];
  929. self.fetcherInFlight = queryFetcher;
  930. [queryFetcher beginFetchWithDelegate:self
  931. didFinishSelector:@selector(queryFetcher:finishedWithData:error:)];
  932. }
  933. - (void)queryFetcher:(GTMSessionFetcher *)queryFetcher
  934. finishedWithData:(NSData *)data
  935. error:(NSError *)error {
  936. self.fetcherInFlight = nil;
  937. NSDictionary *responseHeaders = [queryFetcher responseHeaders];
  938. NSString *sizeReceivedHeader;
  939. GTMSessionUploadFetcherStatus uploadStatus =
  940. [[self class] uploadStatusFromResponseHeaders:responseHeaders];
  941. GTMSESSION_ASSERT_DEBUG(uploadStatus != kStatusUnknown || error != nil,
  942. @"query fetcher completion has unexpected upload status for headers %@", responseHeaders);
  943. if (error == nil) {
  944. sizeReceivedHeader = [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadSizeReceived];
  945. if (uploadStatus == kStatusCancelled ||
  946. (uploadStatus == kStatusActive && sizeReceivedHeader == nil)) {
  947. NSDictionary *userInfo = nil;
  948. if (data.length > 0) {
  949. userInfo = @{ kGTMSessionFetcherStatusDataKey : data };
  950. }
  951. error = [self prematureFailureErrorWithUserInfo:userInfo];
  952. }
  953. }
  954. if (error == nil) {
  955. int64_t offset = [sizeReceivedHeader longLongValue];
  956. int64_t fullUploadLength = [self fullUploadLength];
  957. if (uploadStatus == kStatusFinal ||
  958. (offset >= fullUploadLength &&
  959. fullUploadLength != kGTMSessionUploadFetcherUnknownFileSize)) {
  960. // Handle we're done
  961. [self chunkFetcher:queryFetcher finishedWithData:data error:nil];
  962. } else {
  963. [self retrieveUploadChunkGranularityFromResponseHeaders:responseHeaders];
  964. [self uploadNextChunkWithOffset:offset];
  965. }
  966. } else {
  967. // Handle query error
  968. [self chunkFetcher:queryFetcher finishedWithData:data error:error];
  969. }
  970. }
  971. - (void)sendCancelUploadWithFetcherProperties:(NSDictionary *)props {
  972. @synchronized(self) {
  973. _isCancelInFlight = YES;
  974. }
  975. GTMSessionFetcher *cancelFetcher = [self uploadFetcherWithProperties:props
  976. isQueryFetch:YES];
  977. cancelFetcher.bodyData = [NSData data];
  978. NSString *originalComment = self.comment;
  979. [cancelFetcher setCommentWithFormat:@"%@ (cancel)",
  980. originalComment ? originalComment : @"upload"];
  981. [cancelFetcher setRequestValue:@"cancel" forHTTPHeaderField:kGTMSessionHeaderXGoogUploadCommand];
  982. self.fetcherInFlight = cancelFetcher;
  983. [cancelFetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
  984. self.fetcherInFlight = nil;
  985. if (![self triggerCancellationHandlerForFetch:cancelFetcher data:data error:error]) {
  986. if (error) {
  987. GTMSESSION_LOG_DEBUG(@"cancelFetcher %@", error);
  988. }
  989. }
  990. @synchronized(self) {
  991. self->_isCancelInFlight = NO;
  992. }
  993. }];
  994. }
  995. - (void)uploadNextChunkWithOffset:(int64_t)offset
  996. fetcherProperties:(NSDictionary *)props {
  997. GTMSessionCheckNotSynchronized(self);
  998. // Example chunk headers:
  999. // X-Goog-Upload-Command: upload, finalize
  1000. // X-Goog-Upload-Offset: 0
  1001. // Content-Length: 2000000
  1002. // Content-Type: image/jpeg
  1003. //
  1004. // {bytes 0-1999999}
  1005. // The chunk upload URL requires no authentication header.
  1006. GTMSessionFetcher *chunkFetcher = [self uploadFetcherWithProperties:props
  1007. isQueryFetch:NO];
  1008. [self attachSendProgressBlockToChunkFetcher:chunkFetcher];
  1009. int64_t chunkSize = [self updateChunkFetcher:chunkFetcher
  1010. forChunkAtOffset:offset];
  1011. BOOL isUploadingFileURL = (self.uploadFileURL != nil);
  1012. int64_t fullUploadLength = [self fullUploadLength];
  1013. // The chunk size may have changed, so determine again if we're uploading the full file.
  1014. BOOL isUploadingFullFile = (offset == 0 &&
  1015. fullUploadLength != kGTMSessionUploadFetcherUnknownFileSize &&
  1016. chunkSize >= fullUploadLength);
  1017. if (isUploadingFullFile && isUploadingFileURL) {
  1018. // The data is the full upload file URL.
  1019. chunkFetcher.bodyFileURL = self.uploadFileURL;
  1020. [self beginChunkFetcher:chunkFetcher
  1021. offset:offset];
  1022. } else {
  1023. // Make an NSData for the subset for this upload chunk.
  1024. self.subdataGenerating = YES;
  1025. [self generateChunkSubdataWithOffset:offset
  1026. length:chunkSize
  1027. response:^(NSData *chunkData, int64_t uploadFileLength, NSError *chunkError) {
  1028. // The subdata methods may leave us on a background thread.
  1029. dispatch_async(dispatch_get_main_queue(), ^{
  1030. self.subdataGenerating = NO;
  1031. // dont allow the updating of fileLength for uploads not using a data provider as they
  1032. // should know the file length before the upload starts.
  1033. if (self->_uploadDataProvider != nil && uploadFileLength > 0) {
  1034. [self setUploadFileLength:uploadFileLength];
  1035. // Update the command and content-length headers if this is the last chunk to be sent.
  1036. if (offset + chunkSize >= uploadFileLength) {
  1037. int64_t updatedChunkSize = [self updateChunkFetcher:chunkFetcher
  1038. forChunkAtOffset:offset];
  1039. if (updatedChunkSize == 0) {
  1040. // Calling beginChunkFetcher early when there is no more data to send allows us to
  1041. // properly handle nil chunkData below without having to account for the case where
  1042. // we are just finalizing the file.
  1043. chunkFetcher.bodyData = [[NSData alloc] init];
  1044. [self beginChunkFetcher:chunkFetcher
  1045. offset:offset];
  1046. return;
  1047. }
  1048. }
  1049. }
  1050. if (chunkData == nil) {
  1051. NSError *responseError = chunkError;
  1052. if (!responseError) {
  1053. responseError = [self uploadChunkUnavailableErrorWithDescription:@"chunkData is nil"];
  1054. }
  1055. [self invokeFinalCallbackWithData:nil
  1056. error:responseError
  1057. shouldInvalidateLocation:YES];
  1058. return;
  1059. }
  1060. BOOL didWriteFile = NO;
  1061. if (isUploadingFileURL) {
  1062. // Make a temporary file with the data subset.
  1063. NSString *tempName =
  1064. [NSString stringWithFormat:@"GTMUpload_temp_%@", [[NSUUID UUID] UUIDString]];
  1065. NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:tempName];
  1066. NSError *writeError;
  1067. didWriteFile = [chunkData writeToFile:tempPath
  1068. options:NSDataWritingAtomic
  1069. error:&writeError];
  1070. if (didWriteFile) {
  1071. chunkFetcher.bodyFileURL = [NSURL fileURLWithPath:tempPath];
  1072. } else {
  1073. GTMSESSION_LOG_DEBUG(@"writeToFile failed: %@\n%@", writeError, tempPath);
  1074. }
  1075. }
  1076. if (!didWriteFile) {
  1077. chunkFetcher.bodyData = [chunkData copy];
  1078. }
  1079. [self beginChunkFetcher:chunkFetcher
  1080. offset:offset];
  1081. });
  1082. }];
  1083. }
  1084. }
  1085. - (void)beginChunkFetcher:(GTMSessionFetcher *)chunkFetcher
  1086. offset:(int64_t)offset {
  1087. // Track the current offset for progress reporting
  1088. self.currentOffset = offset;
  1089. // Hang on to the fetcher in case we need to cancel it. We set these before beginning the
  1090. // chunk fetch so the observers notified of chunk fetches can inspect the upload fetcher to
  1091. // match to the chunk.
  1092. self.chunkFetcher = chunkFetcher;
  1093. self.fetcherInFlight = chunkFetcher;
  1094. // Update the last chunk request, including any request headers.
  1095. self.lastChunkRequest = chunkFetcher.request;
  1096. [chunkFetcher beginFetchWithDelegate:self
  1097. didFinishSelector:@selector(chunkFetcher:finishedWithData:error:)];
  1098. }
  1099. - (void)attachSendProgressBlockToChunkFetcher:(GTMSessionFetcher *)chunkFetcher {
  1100. chunkFetcher.sendProgressBlock = ^(int64_t bytesSent, int64_t totalBytesSent,
  1101. int64_t totalBytesExpectedToSend) {
  1102. // The total bytes expected include the initial body and the full chunked
  1103. // data, independent of how big this fetcher's chunk is.
  1104. int64_t initialBodySent = [self bodyLength]; // TODO(grobbins) use [self initialBodySent]
  1105. int64_t totalSent = initialBodySent + self.currentOffset + totalBytesSent;
  1106. int64_t totalExpected = initialBodySent + [self fullUploadLength];
  1107. [self invokeDelegateWithDidSendBytes:bytesSent
  1108. totalBytesSent:totalSent
  1109. totalBytesExpectedToSend:totalExpected];
  1110. };
  1111. }
  1112. - (NSDictionary *)uploadSessionIdentifierMetadata {
  1113. NSMutableDictionary *metadata = [NSMutableDictionary dictionary];
  1114. metadata[kGTMSessionIdentifierIsUploadChunkFetcherMetadataKey] = @YES;
  1115. GTMSESSION_ASSERT_DEBUG(self.uploadFileURL,
  1116. @"Invalid upload fetcher to create session identifier for metadata");
  1117. metadata[kGTMSessionIdentifierUploadFileURLMetadataKey] = [self.uploadFileURL absoluteString];
  1118. metadata[kGTMSessionIdentifierUploadFileLengthMetadataKey] = @([self fullUploadLength]);
  1119. if (self.uploadLocationURL) {
  1120. metadata[kGTMSessionIdentifierUploadLocationURLMetadataKey] =
  1121. [self.uploadLocationURL absoluteString];
  1122. }
  1123. if (self.uploadMIMEType) {
  1124. metadata[kGTMSessionIdentifierUploadMIMETypeMetadataKey] = self.uploadMIMEType;
  1125. }
  1126. metadata[kGTMSessionIdentifierUploadChunkSizeMetadataKey] = @(self.chunkSize);
  1127. metadata[kGTMSessionIdentifierUploadCurrentOffsetMetadataKey] = @(self.currentOffset);
  1128. return metadata;
  1129. }
  1130. - (GTMSessionFetcher *)uploadFetcherWithProperties:(NSDictionary *)properties
  1131. isQueryFetch:(BOOL)isQueryFetch {
  1132. GTMSessionCheckNotSynchronized(self);
  1133. // Common code to make a request for a query command or for a chunk upload.
  1134. NSURL *uploadLocationURL = self.uploadLocationURL;
  1135. NSMutableURLRequest *chunkRequest = [NSMutableURLRequest requestWithURL:uploadLocationURL];
  1136. [chunkRequest setHTTPMethod:@"PUT"];
  1137. // copy the user-agent from the original connection
  1138. // n.b. that self.request is nil for upload fetchers created with an existing upload location
  1139. // URL.
  1140. NSURLRequest *origRequest = self.request;
  1141. NSString *userAgent = [origRequest valueForHTTPHeaderField:@"User-Agent"];
  1142. if (userAgent.length > 0) {
  1143. [chunkRequest setValue:userAgent forHTTPHeaderField:@"User-Agent"];
  1144. }
  1145. [chunkRequest setValue:kGTMSessionXGoogUploadProtocolResumable
  1146. forHTTPHeaderField:kGTMSessionHeaderXGoogUploadProtocol];
  1147. // To avoid timeouts when debugging, copy the timeout of the initial fetcher.
  1148. NSTimeInterval origTimeout = [origRequest timeoutInterval];
  1149. [chunkRequest setTimeoutInterval:origTimeout];
  1150. //
  1151. // Make a new chunk fetcher.
  1152. //
  1153. GTMSessionFetcher *chunkFetcher = [GTMSessionFetcher fetcherWithRequest:chunkRequest];
  1154. chunkFetcher.callbackQueue = self.callbackQueue;
  1155. chunkFetcher.sessionUserInfo = self.sessionUserInfo;
  1156. chunkFetcher.configurationBlock = self.configurationBlock;
  1157. chunkFetcher.allowedInsecureSchemes = self.allowedInsecureSchemes;
  1158. chunkFetcher.allowLocalhostRequest = self.allowLocalhostRequest;
  1159. chunkFetcher.allowInvalidServerCertificates = self.allowInvalidServerCertificates;
  1160. chunkFetcher.useUploadTask = !isQueryFetch;
  1161. if (self.uploadFileURL && !isQueryFetch && self.useBackgroundSession) {
  1162. [chunkFetcher createSessionIdentifierWithMetadata:[self uploadSessionIdentifierMetadata]];
  1163. }
  1164. // Give the chunk fetcher the same properties as the previous chunk fetcher
  1165. chunkFetcher.properties = [properties mutableCopy];
  1166. [chunkFetcher setProperty:[NSValue valueWithNonretainedObject:self]
  1167. forKey:kGTMSessionUploadFetcherChunkParentKey];
  1168. // copy other fetcher settings to the new fetcher
  1169. chunkFetcher.retryEnabled = self.retryEnabled;
  1170. chunkFetcher.maxRetryInterval = self.maxRetryInterval;
  1171. if ([self isRetryEnabled]) {
  1172. // We interpose our own retry method both so we can change the request to ask the server to
  1173. // tell us where to resume the chunk.
  1174. chunkFetcher.retryBlock = ^(BOOL suggestedWillRetry, NSError *chunkError,
  1175. GTMSessionFetcherRetryResponse response) {
  1176. void (^finish)(BOOL) = ^(BOOL shouldRetry){
  1177. // We'll retry by sending an offset query.
  1178. if (shouldRetry) {
  1179. self.shouldInitiateOffsetQuery = !isQueryFetch;
  1180. // We don't know what our actual offset is anymore, but the server will tell us.
  1181. self.currentOffset = 0;
  1182. }
  1183. // We don't actually want to retry this specific fetcher.
  1184. response(NO);
  1185. };
  1186. GTMSessionFetcherRetryBlock retryBlock = self.retryBlock;
  1187. if (retryBlock) {
  1188. // Ask the client, then call the finish block above.
  1189. retryBlock(suggestedWillRetry, chunkError, finish);
  1190. } else {
  1191. finish(suggestedWillRetry);
  1192. }
  1193. };
  1194. }
  1195. return chunkFetcher;
  1196. }
  1197. - (void)chunkFetcher:(GTMSessionFetcher *)chunkFetcher
  1198. finishedWithData:(NSData *)data
  1199. error:(NSError *)error {
  1200. BOOL hasDestroyedOldChunkFetcher = NO;
  1201. self.fetcherInFlight = nil;
  1202. NSDictionary *responseHeaders = [chunkFetcher responseHeaders];
  1203. GTMSessionUploadFetcherStatus uploadStatus =
  1204. [[self class] uploadStatusFromResponseHeaders:responseHeaders];
  1205. GTMSESSION_ASSERT_DEBUG(uploadStatus != kStatusUnknown
  1206. || error != nil
  1207. || self.wasCreatedFromBackgroundSession,
  1208. @"chunk fetcher completion has kStatusUnknown upload status for headers %@ fetcher %@",
  1209. responseHeaders, self);
  1210. BOOL isUploadStatusStopped = (uploadStatus == kStatusFinal || uploadStatus == kStatusCancelled);
  1211. // Check if the fetcher was actually querying. If it failed, do not retry,
  1212. // as it would enter an infinite retry loop.
  1213. NSString *uploadCommand =
  1214. chunkFetcher.request.allHTTPHeaderFields[kGTMSessionHeaderXGoogUploadCommand];
  1215. BOOL isQueryFetch = [uploadCommand isEqual:@"query"];
  1216. // TODO
  1217. // Maybe here we can check to see if the request had x goog content length set. (the file length one).
  1218. int64_t previousContentLength =
  1219. [[chunkFetcher.request valueForHTTPHeaderField:@"Content-Length"] longLongValue];
  1220. // The Content-Length header may not be present if the chunk fetcher was recreated from
  1221. // a background session.
  1222. BOOL hasKnownChunkSize = (previousContentLength > 0);
  1223. BOOL needsQuery = (!hasKnownChunkSize && !isUploadStatusStopped);
  1224. if (error || (needsQuery && !isQueryFetch)) {
  1225. NSInteger status = error.code;
  1226. // Status 4xx indicates a bad offset in the Google upload protocol. However, do not retry status
  1227. // 404 per spec, nor if the upload size appears to have been zero (since the server will just
  1228. // keep asking us to retry.)
  1229. if (self.shouldInitiateOffsetQuery ||
  1230. (needsQuery && !isQueryFetch) ||
  1231. ([error.domain isEqual:kGTMSessionFetcherStatusDomain] &&
  1232. status >= 400 && status <= 499 &&
  1233. status != 404 &&
  1234. uploadStatus == kStatusActive &&
  1235. previousContentLength > 0)) {
  1236. self.shouldInitiateOffsetQuery = NO;
  1237. [self destroyChunkFetcher];
  1238. hasDestroyedOldChunkFetcher = YES;
  1239. [self sendQueryForUploadOffsetWithFetcherProperties:chunkFetcher.properties];
  1240. } else {
  1241. // Some unexpected status has occurred; handle it as we would a regular
  1242. // object fetcher failure.
  1243. [self invokeFinalCallbackWithData:data
  1244. error:error
  1245. shouldInvalidateLocation:NO];
  1246. }
  1247. } else {
  1248. // The chunk has uploaded successfully.
  1249. int64_t newOffset = self.currentOffset + previousContentLength;
  1250. #if DEBUG
  1251. // Verify that if we think all of the uploading data has been sent, the server responded with
  1252. // the "final" upload status.
  1253. BOOL hasUploadAllData = (newOffset == [self fullUploadLength]);
  1254. BOOL isFinalStatus = (uploadStatus == kStatusFinal);
  1255. #pragma unused(hasUploadAllData,isFinalStatus)
  1256. GTMSESSION_ASSERT_DEBUG(hasUploadAllData == isFinalStatus || !hasKnownChunkSize,
  1257. @"uploadStatus:%@ newOffset:%lld (%lld + %lld) fullUploadLength:%lld"
  1258. @" chunkFetcher:%@ requestHeaders:%@ responseHeaders:%@",
  1259. [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadStatus],
  1260. newOffset, self.currentOffset, previousContentLength,
  1261. [self fullUploadLength],
  1262. chunkFetcher, chunkFetcher.request.allHTTPHeaderFields,
  1263. responseHeaders);
  1264. #endif
  1265. if (isUploadStatusStopped || (_currentOffset > _uploadFileLength && _uploadFileLength > 0)) {
  1266. // This was the last chunk.
  1267. if (error == nil && uploadStatus == kStatusCancelled) {
  1268. // Report cancelled status as an error.
  1269. NSDictionary *userInfo = nil;
  1270. if (data.length > 0) {
  1271. userInfo = @{ kGTMSessionFetcherStatusDataKey : data };
  1272. }
  1273. data = nil;
  1274. error = [self prematureFailureErrorWithUserInfo:userInfo];
  1275. } else {
  1276. // The upload is in final status.
  1277. //
  1278. // Take the chunk fetcher's data as the superclass data.
  1279. self.downloadedData = data;
  1280. self.statusCode = chunkFetcher.statusCode;
  1281. }
  1282. // we're done
  1283. [self invokeFinalCallbackWithData:data
  1284. error:error
  1285. shouldInvalidateLocation:YES];
  1286. } else {
  1287. // Start the next chunk.
  1288. self.currentOffset = newOffset;
  1289. // We want to destroy this chunk fetcher before creating the next one, but
  1290. // we want to pass on its properties
  1291. NSDictionary *props = [chunkFetcher properties];
  1292. // We no longer need to be able to cancel this chunkFetcher. Destroy it
  1293. // before we create a new chunk fetcher.
  1294. [self destroyChunkFetcher];
  1295. hasDestroyedOldChunkFetcher = YES;
  1296. [self uploadNextChunkWithOffset:newOffset
  1297. fetcherProperties:props];
  1298. }
  1299. }
  1300. if (!hasDestroyedOldChunkFetcher) {
  1301. [self destroyChunkFetcher];
  1302. }
  1303. }
  1304. - (void)destroyChunkFetcher {
  1305. @synchronized(self) {
  1306. GTMSessionMonitorSynchronized(self);
  1307. if (_fetcherInFlight == _chunkFetcher) {
  1308. _fetcherInFlight = nil;
  1309. }
  1310. [_chunkFetcher stopFetching];
  1311. NSURL *chunkFileURL = _chunkFetcher.bodyFileURL;
  1312. BOOL wasTemporaryUploadFile = ![chunkFileURL isEqual:_uploadFileURL];
  1313. if (wasTemporaryUploadFile) {
  1314. NSError *error;
  1315. [[NSFileManager defaultManager] removeItemAtURL:chunkFileURL
  1316. error:&error];
  1317. if (error) {
  1318. GTMSESSION_LOG_DEBUG(@"removingItemAtURL failed: %@\n%@", error, chunkFileURL);
  1319. }
  1320. }
  1321. _recentChunkReponseHeaders = _chunkFetcher.responseHeaders;
  1322. // To avoid retain cycles, remove all properties except the parent identifier.
  1323. _chunkFetcher.properties =
  1324. @{ kGTMSessionUploadFetcherChunkParentKey : [NSValue valueWithNonretainedObject:self] };
  1325. _chunkFetcher.retryBlock = nil;
  1326. _chunkFetcher.sendProgressBlock = nil;
  1327. _chunkFetcher = nil;
  1328. } // @synchronized(self)
  1329. }
  1330. // This method calculates the proper values to pass to the client's send progress block.
  1331. //
  1332. // The actual total bytes sent include the initial body sent, plus the
  1333. // offset into the batched data prior to the current chunk fetcher
  1334. - (void)invokeDelegateWithDidSendBytes:(int64_t)bytesSent
  1335. totalBytesSent:(int64_t)totalBytesSent
  1336. totalBytesExpectedToSend:(int64_t)totalBytesExpected {
  1337. GTMSessionCheckNotSynchronized(self);
  1338. // Ensure the chunk fetcher survives the callback in case the user pauses the upload process.
  1339. __block GTMSessionFetcher *holdFetcher = self.chunkFetcher;
  1340. [self invokeOnCallbackQueue:self.delegateCallbackQueue
  1341. afterUserStopped:NO
  1342. block:^{
  1343. GTMSessionFetcherSendProgressBlock sendProgressBlock = self.sendProgressBlock;
  1344. if (sendProgressBlock) {
  1345. sendProgressBlock(bytesSent, totalBytesSent, totalBytesExpected);
  1346. }
  1347. holdFetcher = nil;
  1348. }];
  1349. }
  1350. - (void)retrieveUploadChunkGranularityFromResponseHeaders:(NSDictionary *)responseHeaders {
  1351. GTMSessionCheckNotSynchronized(self);
  1352. // Standard granularity for Google uploads is 256K.
  1353. NSString *chunkGranularityHeader =
  1354. [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadChunkGranularity];
  1355. self.uploadGranularity = chunkGranularityHeader.longLongValue;
  1356. }
  1357. #pragma mark -
  1358. - (BOOL)isPaused {
  1359. @synchronized(self) {
  1360. GTMSessionMonitorSynchronized(self);
  1361. return _isPaused;
  1362. } // @synchronized(self)
  1363. }
  1364. - (void)pauseFetching {
  1365. @synchronized(self) {
  1366. GTMSessionMonitorSynchronized(self);
  1367. _isPaused = YES;
  1368. } // @synchronized(self)
  1369. // Pausing just means stopping the current chunk from uploading;
  1370. // when we resume, we will send a query request to the server to
  1371. // figure out what bytes to resume sending.
  1372. //
  1373. // We won't try to cancel the initial data upload, but rather will check
  1374. // for being paused in beginChunkFetches.
  1375. [self destroyChunkFetcher];
  1376. }
  1377. - (void)resumeFetching {
  1378. BOOL wasPaused;
  1379. @synchronized(self) {
  1380. GTMSessionMonitorSynchronized(self);
  1381. wasPaused = _isPaused;
  1382. _isPaused = NO;
  1383. } // @synchronized(self)
  1384. if (wasPaused) {
  1385. [self sendQueryForUploadOffsetWithFetcherProperties:self.properties];
  1386. }
  1387. }
  1388. - (void)stopFetching {
  1389. // Overrides the superclass
  1390. [self destroyChunkFetcher];
  1391. // If we think the server is waiting for more data, then tell it there won't be more.
  1392. if (self.uploadLocationURL) {
  1393. [self sendCancelUploadWithFetcherProperties:[self properties]];
  1394. self.uploadLocationURL = nil;
  1395. } else {
  1396. [self invokeOnCallbackQueue:self.callbackQueue
  1397. afterUserStopped:YES
  1398. block:^{
  1399. // Repeated calls to stopFetching may cause this path to be reached despite having sent a real
  1400. // cancel request, check here to ensure that the cancellation handler invocation which fires
  1401. // will definitely be for the real request sent previously.
  1402. @synchronized(self) {
  1403. if (self->_isCancelInFlight) {
  1404. return;
  1405. }
  1406. }
  1407. [self triggerCancellationHandlerForFetch:nil data:nil error:nil];
  1408. }];
  1409. }
  1410. [super stopFetching];
  1411. }
  1412. // Fires the cancellation handler, returning whether there was a handler to be fired.
  1413. - (BOOL)triggerCancellationHandlerForFetch:(GTMSessionFetcher *)fetcher
  1414. data:(NSData *)data
  1415. error:(NSError *)error {
  1416. GTMSessionUploadFetcherCancellationHandler handler = self.cancellationHandler;
  1417. if (handler) {
  1418. handler(fetcher, data, error);
  1419. self.cancellationHandler = nil;
  1420. return YES;
  1421. }
  1422. return NO;
  1423. }
  1424. #pragma mark -
  1425. - (int64_t)updateChunkFetcher:(GTMSessionFetcher *)chunkFetcher
  1426. forChunkAtOffset:(int64_t)offset {
  1427. BOOL isUploadingFileURL = (self.uploadFileURL != nil);
  1428. // Upload another chunk, meeting server-required granularity.
  1429. int64_t chunkSize = self.chunkSize;
  1430. int64_t fullUploadLength = [self fullUploadLength];
  1431. BOOL isFileLengthKnown = fullUploadLength >= 0;
  1432. BOOL isUploadingFullFile = (offset == 0 && isFileLengthKnown && chunkSize >= fullUploadLength);
  1433. if (!isUploadingFileURL || !isUploadingFullFile) {
  1434. // We're not uploading the entire file and given the file URL. Since we'll be
  1435. // allocating a subdata block for a chunk, we need to bound it to something that
  1436. // won't blow the process's memory.
  1437. if (chunkSize > kGTMSessionUploadFetcherMaximumDemandBufferSize) {
  1438. chunkSize = kGTMSessionUploadFetcherMaximumDemandBufferSize;
  1439. }
  1440. }
  1441. int64_t granularity = self.uploadGranularity;
  1442. if (granularity > 0) {
  1443. if (chunkSize < granularity) {
  1444. chunkSize = granularity;
  1445. } else {
  1446. chunkSize = chunkSize - (chunkSize % granularity);
  1447. }
  1448. }
  1449. GTMSESSION_ASSERT_DEBUG(offset < fullUploadLength || fullUploadLength == 0,
  1450. @"offset %lld exceeds data length %lld", offset, fullUploadLength);
  1451. if (granularity > 0) {
  1452. offset = offset - (offset % granularity);
  1453. }
  1454. // If the chunk size is bigger than the remaining data, or else
  1455. // it's close enough in size to the remaining data that we'd rather
  1456. // avoid having a whole extra http fetch for the leftover bit, then make
  1457. // this chunk size exactly match the remaining data size
  1458. NSString *command;
  1459. int64_t thisChunkSize = chunkSize;
  1460. BOOL isChunkTooBig = (thisChunkSize >= (fullUploadLength - offset));
  1461. BOOL isChunkAlmostBigEnough = (fullUploadLength - offset - 2500 < thisChunkSize);
  1462. BOOL isFinalChunk = (isChunkTooBig || isChunkAlmostBigEnough) && isFileLengthKnown;
  1463. if (isFinalChunk) {
  1464. thisChunkSize = fullUploadLength - offset;
  1465. if (thisChunkSize > 0) {
  1466. command = @"upload, finalize";
  1467. } else {
  1468. command = @"finalize";
  1469. }
  1470. } else {
  1471. command = @"upload";
  1472. }
  1473. NSString *lengthStr = @(thisChunkSize).stringValue;
  1474. NSString *offsetStr = @(offset).stringValue;
  1475. [chunkFetcher setRequestValue:command forHTTPHeaderField:kGTMSessionHeaderXGoogUploadCommand];
  1476. [chunkFetcher setRequestValue:lengthStr forHTTPHeaderField:@"Content-Length"];
  1477. [chunkFetcher setRequestValue:offsetStr forHTTPHeaderField:kGTMSessionHeaderXGoogUploadOffset];
  1478. if (_uploadFileLength != kGTMSessionUploadFetcherUnknownFileSize) {
  1479. [chunkFetcher setRequestValue:@([self fullUploadLength]).stringValue
  1480. forHTTPHeaderField:kGTMSessionHeaderXGoogUploadContentLength];
  1481. }
  1482. // Append the range of bytes in this chunk to the fetcher comment.
  1483. NSString *baseComment = self.comment;
  1484. [chunkFetcher setCommentWithFormat:@"%@ (%lld-%lld)",
  1485. baseComment ? baseComment : @"upload", offset, MAX(0, offset + thisChunkSize - 1)];
  1486. return thisChunkSize;
  1487. }
  1488. // Public properties.
  1489. @synthesize currentOffset = _currentOffset,
  1490. delegateCompletionHandler = _delegateCompletionHandler,
  1491. chunkFetcher = _chunkFetcher,
  1492. lastChunkRequest = _lastChunkRequest,
  1493. subdataGenerating = _subdataGenerating,
  1494. shouldInitiateOffsetQuery = _shouldInitiateOffsetQuery,
  1495. uploadGranularity = _uploadGranularity;
  1496. // Internal properties.
  1497. @dynamic fetcherInFlight;
  1498. @dynamic activeFetcher;
  1499. @dynamic statusCode;
  1500. @dynamic delegateCallbackQueue;
  1501. + (void)removePointer:(void *)pointer fromPointerArray:(NSPointerArray *)pointerArray {
  1502. for (NSUInteger index = 0, count = pointerArray.count; index < count; ++index) {
  1503. void *pointerAtIndex = [pointerArray pointerAtIndex:index];
  1504. if (pointerAtIndex == pointer) {
  1505. [pointerArray removePointerAtIndex:index];
  1506. return;
  1507. }
  1508. }
  1509. }
  1510. - (BOOL)useBackgroundSession {
  1511. @synchronized(self) {
  1512. GTMSessionMonitorSynchronized(self);
  1513. return _useBackgroundSessionOnChunkFetchers;
  1514. } // @synchronized(self
  1515. }
  1516. - (void)setUseBackgroundSession:(BOOL)useBackgroundSession {
  1517. @synchronized(self) {
  1518. GTMSessionMonitorSynchronized(self);
  1519. if (_useBackgroundSessionOnChunkFetchers != useBackgroundSession) {
  1520. _useBackgroundSessionOnChunkFetchers = useBackgroundSession;
  1521. NSPointerArray *uploadFetcherPointerArrayForBackgroundSessions =
  1522. [[self class] uploadFetcherPointerArrayForBackgroundSessions];
  1523. @synchronized(uploadFetcherPointerArrayForBackgroundSessions) {
  1524. if (_useBackgroundSessionOnChunkFetchers) {
  1525. [uploadFetcherPointerArrayForBackgroundSessions addPointer:(__bridge void *)self];
  1526. } else {
  1527. [[self class] removePointer:(__bridge void *)self
  1528. fromPointerArray:uploadFetcherPointerArrayForBackgroundSessions];
  1529. }
  1530. } // @synchronized(uploadFetcherPointerArrayForBackgroundSessions)
  1531. }
  1532. } // @synchronized(self)
  1533. }
  1534. - (BOOL)canFetchWithBackgroundSession {
  1535. // The initial upload fetcher is always a foreground session; the
  1536. // useBackgroundSession property will apply only to chunk fetchers,
  1537. // not to queries.
  1538. return NO;
  1539. }
  1540. - (NSDictionary *)responseHeaders {
  1541. GTMSessionCheckNotSynchronized(self);
  1542. // Overrides the superclass
  1543. // If asked for the fetcher's response, use the most recent chunk fetcher's response,
  1544. // since the original request's response lacks useful information like the actual
  1545. // Content-Type.
  1546. NSDictionary *dict = self.chunkFetcher.responseHeaders;
  1547. if (dict) {
  1548. return dict;
  1549. }
  1550. @synchronized(self) {
  1551. GTMSessionMonitorSynchronized(self);
  1552. if (_recentChunkReponseHeaders) {
  1553. return _recentChunkReponseHeaders;
  1554. }
  1555. } // @synchronized(self
  1556. // No chunk fetcher yet completed, so return whatever we have from the initial fetch.
  1557. return [super responseHeaders];
  1558. }
  1559. - (NSInteger)statusCodeUnsynchronized {
  1560. GTMSessionCheckSynchronized(self);
  1561. if (_recentChunkStatusCode != -1) {
  1562. // Overrides the superclass to indicate status appropriate to the initial
  1563. // or latest chunk fetch
  1564. return _recentChunkStatusCode;
  1565. } else {
  1566. return [super statusCodeUnsynchronized];
  1567. }
  1568. }
  1569. - (void)setStatusCode:(NSInteger)val {
  1570. @synchronized(self) {
  1571. GTMSessionMonitorSynchronized(self);
  1572. _recentChunkStatusCode = val;
  1573. }
  1574. }
  1575. - (int64_t)initialBodyLength {
  1576. @synchronized(self) {
  1577. GTMSessionMonitorSynchronized(self);
  1578. return _initialBodyLength;
  1579. }
  1580. }
  1581. - (void)setInitialBodyLength:(int64_t)length {
  1582. @synchronized(self) {
  1583. GTMSessionMonitorSynchronized(self);
  1584. _initialBodyLength = length;
  1585. }
  1586. }
  1587. - (int64_t)initialBodySent {
  1588. @synchronized(self) {
  1589. GTMSessionMonitorSynchronized(self);
  1590. return _initialBodySent;
  1591. }
  1592. }
  1593. - (void)setInitialBodySent:(int64_t)length {
  1594. @synchronized(self) {
  1595. GTMSessionMonitorSynchronized(self);
  1596. _initialBodySent = length;
  1597. }
  1598. }
  1599. - (NSURL *)uploadLocationURL {
  1600. @synchronized(self) {
  1601. GTMSessionMonitorSynchronized(self);
  1602. return _uploadLocationURL;
  1603. }
  1604. }
  1605. - (void)setUploadLocationURL:(NSURL *)locationURL {
  1606. @synchronized(self) {
  1607. GTMSessionMonitorSynchronized(self);
  1608. _uploadLocationURL = locationURL;
  1609. }
  1610. }
  1611. - (GTMSessionFetcher *)activeFetcher {
  1612. GTMSessionFetcher *result = self.fetcherInFlight;
  1613. if (result) return result;
  1614. return self;
  1615. }
  1616. - (BOOL)isFetching {
  1617. // If there is an active chunk fetcher, then the upload fetcher is considered
  1618. // to still be fetching.
  1619. if (self.fetcherInFlight != nil) return YES;
  1620. return [super isFetching];
  1621. }
  1622. - (BOOL)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds {
  1623. NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds];
  1624. while (self.fetcherInFlight || self.subdataGenerating) {
  1625. if ([timeoutDate timeIntervalSinceNow] < 0) return NO;
  1626. if (self.subdataGenerating) {
  1627. // Allow time for subdata generation.
  1628. NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:0.001];
  1629. [[NSRunLoop currentRunLoop] runUntilDate:stopDate];
  1630. } else {
  1631. // Wait for any chunk or query fetchers that still have pending callbacks or
  1632. // notifications.
  1633. BOOL timedOut;
  1634. if (self.fetcherInFlight == self) {
  1635. timedOut = ![super waitForCompletionWithTimeout:timeoutInSeconds];
  1636. } else {
  1637. timedOut = ![self.fetcherInFlight waitForCompletionWithTimeout:timeoutInSeconds];
  1638. }
  1639. if (timedOut) return NO;
  1640. }
  1641. }
  1642. return YES;
  1643. }
  1644. @end
  1645. @implementation GTMSessionFetcher (GTMSessionUploadFetcherMethods)
  1646. - (GTMSessionUploadFetcher *)parentUploadFetcher {
  1647. NSValue *property = [self propertyForKey:kGTMSessionUploadFetcherChunkParentKey];
  1648. if (!property) return nil;
  1649. GTMSessionUploadFetcher *uploadFetcher = property.nonretainedObjectValue;
  1650. GTMSESSION_ASSERT_DEBUG([uploadFetcher isKindOfClass:[GTMSessionUploadFetcher class]],
  1651. @"Unexpected parent upload fetcher class: %@", [uploadFetcher class]);
  1652. return uploadFetcher;
  1653. }
  1654. @end