No Description

GTMSessionUploadFetcher.m 74KB

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