/core/externals/update-engine/externals/gdata-objectivec-client/Source/HTTPFetcher/GTMHTTPUploadFetcher.m

http://macfuse.googlecode.com/ · Objective C · 945 lines · 608 code · 162 blank · 175 comment · 99 complexity · 268ca25f43bbe3fdc608ae4b0ab29b73 MD5 · raw file

  1. /* Copyright (c) 2010 Google Inc.
  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. //
  16. // GTMHTTPUploadFetcher.m
  17. //
  18. #if (!GDATA_REQUIRE_SERVICE_INCLUDES) || GDATA_INCLUDE_DOCS_SERVICE || \
  19. GDATA_INCLUDE_YOUTUBE_SERVICE || GDATA_INCLUDE_PHOTOS_SERVICE
  20. #import "GTMHTTPUploadFetcher.h"
  21. static NSUInteger const kQueryServerForOffset = NSUIntegerMax;
  22. @interface GTMHTTPFetcher (ProtectedMethods)
  23. @property (readwrite, retain) NSData *downloadedData;
  24. - (void)releaseCallbacks;
  25. - (void)stopFetchReleasingCallbacks:(BOOL)shouldReleaseCallbacks;
  26. - (void)connectionDidFinishLoading:(NSURLConnection *)connection;
  27. - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
  28. @end
  29. @interface GTMHTTPUploadFetcher ()
  30. + (GTMHTTPUploadFetcher *)uploadFetcherWithRequest:(NSURLRequest *)request
  31. fetcherService:(GTMHTTPFetcherService *)fetcherService;
  32. - (void)setLocationURL:(NSURL *)location
  33. uploadData:(NSData *)data
  34. uploadFileHandle:(NSFileHandle *)fileHandle
  35. uploadMIMEType:(NSString *)uploadMIMEType
  36. chunkSize:(NSUInteger)chunkSize;
  37. - (void)uploadNextChunkWithOffset:(NSUInteger)offset;
  38. - (void)uploadNextChunkWithOffset:(NSUInteger)offset
  39. fetcherProperties:(NSMutableDictionary *)props;
  40. - (void)destroyChunkFetcher;
  41. - (void)handleResumeIncompleteStatusForChunkFetcher:(GTMHTTPFetcher *)chunkFetcher;
  42. - (void)uploadFetcher:(GTMHTTPFetcher *)fetcher
  43. didSendBytes:(NSInteger)bytesSent
  44. totalBytesSent:(NSInteger)totalBytesSent
  45. totalBytesExpectedToSend:(NSInteger)totalBytesExpected;
  46. - (void)reportProgressManually;
  47. - (NSUInteger)fullUploadLength;
  48. -(BOOL)chunkFetcher:(GTMHTTPFetcher *)chunkFetcher
  49. willRetry:(BOOL)willRetry
  50. forError:(NSError *)error;
  51. - (void)chunkFetcher:(GTMHTTPFetcher *)chunkFetcher
  52. finishedWithData:(NSData *)data
  53. error:(NSError *)error;
  54. @end
  55. @interface GTMHTTPUploadFetcher (PrivateMethods)
  56. // private methods of the superclass
  57. - (void)invokeSentDataCallback:(SEL)sel
  58. target:(id)target
  59. didSendBodyData:(NSInteger)bytesWritten
  60. totalBytesWritten:(NSInteger)totalBytesWritten
  61. totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite;
  62. - (void)invokeFetchCallback:(SEL)sel
  63. target:(id)target
  64. data:(NSData *)data
  65. error:(NSError *)error;
  66. - (BOOL)invokeRetryCallback:(SEL)sel
  67. target:(id)target
  68. willRetry:(BOOL)willRetry
  69. error:(NSError *)error;
  70. @end
  71. @implementation GTMHTTPUploadFetcher
  72. + (GTMHTTPUploadFetcher *)uploadFetcherWithRequest:(NSURLRequest *)request
  73. uploadData:(NSData *)data
  74. uploadMIMEType:(NSString *)uploadMIMEType
  75. chunkSize:(NSUInteger)chunkSize
  76. fetcherService:(GTMHTTPFetcherService *)fetcherService {
  77. GTMHTTPUploadFetcher *fetcher = [self uploadFetcherWithRequest:request
  78. fetcherService:fetcherService];
  79. [fetcher setLocationURL:nil
  80. uploadData:data
  81. uploadFileHandle:nil
  82. uploadMIMEType:uploadMIMEType
  83. chunkSize:chunkSize];
  84. return fetcher;
  85. }
  86. + (GTMHTTPUploadFetcher *)uploadFetcherWithRequest:(NSURLRequest *)request
  87. uploadFileHandle:(NSFileHandle *)fileHandle
  88. uploadMIMEType:(NSString *)uploadMIMEType
  89. chunkSize:(NSUInteger)chunkSize
  90. fetcherService:(GTMHTTPFetcherService *)fetcherService {
  91. GTMHTTPUploadFetcher *fetcher = [self uploadFetcherWithRequest:request
  92. fetcherService:fetcherService];
  93. [fetcher setLocationURL:nil
  94. uploadData:nil
  95. uploadFileHandle:fileHandle
  96. uploadMIMEType:uploadMIMEType
  97. chunkSize:chunkSize];
  98. return fetcher;
  99. }
  100. + (GTMHTTPUploadFetcher *)uploadFetcherWithLocation:(NSURL *)locationURL
  101. uploadFileHandle:(NSFileHandle *)fileHandle
  102. uploadMIMEType:(NSString *)uploadMIMEType
  103. chunkSize:(NSUInteger)chunkSize
  104. fetcherService:(GTMHTTPFetcherService *)fetcherService {
  105. GTMHTTPUploadFetcher *fetcher = [self uploadFetcherWithRequest:nil
  106. fetcherService:fetcherService];
  107. [fetcher setLocationURL:locationURL
  108. uploadData:nil
  109. uploadFileHandle:fileHandle
  110. uploadMIMEType:uploadMIMEType
  111. chunkSize:chunkSize];
  112. return fetcher;
  113. }
  114. + (GTMHTTPUploadFetcher *)uploadFetcherWithRequest:(NSURLRequest *)request
  115. fetcherService:(GTMHTTPFetcherService *)fetcherService {
  116. // Internal utility method for instantiating fetchers
  117. GTMHTTPUploadFetcher *fetcher;
  118. if (fetcherService) {
  119. fetcher = [fetcherService fetcherWithRequest:request
  120. fetcherClass:self];
  121. } else {
  122. fetcher = (GTMHTTPUploadFetcher *) [self fetcherWithRequest:request];
  123. }
  124. return fetcher;
  125. }
  126. - (void)setLocationURL:(NSURL *)location
  127. uploadData:(NSData *)data
  128. uploadFileHandle:(NSFileHandle *)fileHandle
  129. uploadMIMEType:(NSString *)uploadMIMEType
  130. chunkSize:(NSUInteger)chunkSize {
  131. #if DEBUG
  132. NSAssert((data == nil) != (fileHandle == nil),
  133. @"upload data and fileHandle are mutually exclusive");
  134. NSAssert((self.mutableRequest == nil) != (location == nil),
  135. @"request and location are mutually exclusive");
  136. NSAssert(chunkSize > 0,@"chunk size is zero");
  137. NSAssert(chunkSize != NSUIntegerMax, @"chunk size is sentinel value");
  138. #endif
  139. [self setLocationURL:location];
  140. [self setUploadData:data];
  141. [self setUploadFileHandle:fileHandle];
  142. [self setUploadMIMEType:uploadMIMEType];
  143. [self setChunkSize:chunkSize];
  144. // indicate that we've not yet determined the file handle's length
  145. uploadFileHandleLength_ = -1;
  146. // indicate that we've not yet determined the upload fetcher status
  147. statusCode_ = -1;
  148. // if this is restarting an upload begun by another fetcher,
  149. // the location is specified but the request is nil
  150. isRestartedUpload_ = (location != nil);
  151. // add our custom headers to the initial request indicating the data
  152. // type and total size to be delivered later in the chunk requests
  153. NSMutableURLRequest *mutableReq = [self mutableRequest];
  154. NSNumber *lengthNum = [NSNumber numberWithUnsignedInteger:[self fullUploadLength]];
  155. [mutableReq setValue:[lengthNum stringValue]
  156. forHTTPHeaderField:@"X-Upload-Content-Length"];
  157. [mutableReq setValue:uploadMIMEType
  158. forHTTPHeaderField:@"X-Upload-Content-Type"];
  159. }
  160. - (void)dealloc {
  161. [self releaseCallbacks];
  162. [chunkFetcher_ release];
  163. [locationURL_ release];
  164. #if NS_BLOCKS_AVAILABLE
  165. [locationChangeBlock_ release];
  166. #endif
  167. [uploadData_ release];
  168. [uploadFileHandle_ release];
  169. [uploadMIMEType_ release];
  170. [responseHeaders_ release];
  171. [super dealloc];
  172. }
  173. #pragma mark -
  174. - (NSUInteger)fullUploadLength {
  175. if (uploadData_) {
  176. return [uploadData_ length];
  177. } else {
  178. if (uploadFileHandleLength_ == -1) {
  179. // first time through, seek to end to determine file length
  180. uploadFileHandleLength_ = (NSInteger) [uploadFileHandle_ seekToEndOfFile];
  181. }
  182. return (NSUInteger)uploadFileHandleLength_;
  183. }
  184. }
  185. - (NSData *)uploadSubdataWithOffset:(NSUInteger)offset
  186. length:(NSUInteger)length {
  187. NSData *resultData = nil;
  188. if (uploadData_) {
  189. NSRange range = NSMakeRange(offset, length);
  190. resultData = [uploadData_ subdataWithRange:range];
  191. } else {
  192. @try {
  193. [uploadFileHandle_ seekToFileOffset:offset];
  194. resultData = [uploadFileHandle_ readDataOfLength:length];
  195. }
  196. @catch (NSException *exception) {
  197. NSLog(@"uploadFileHandle exception: %@", exception);
  198. }
  199. }
  200. return resultData;
  201. }
  202. #pragma mark Method overrides affecting the initial fetch only
  203. - (BOOL)beginFetchWithDelegate:(id)delegate
  204. didFinishSelector:(SEL)finishedSEL {
  205. GTMAssertSelectorNilOrImplementedWithArgs(delegate, finishedSEL,
  206. @encode(GTMHTTPFetcher *), @encode(NSData *), @encode(NSError *), 0);
  207. // replace the finishedSEL with our own, since the initial finish callback
  208. // is just the beginning of the upload experience
  209. delegateFinishedSEL_ = finishedSEL;
  210. // if the client is running early 10.5 or iPhone 2, we may need to manually
  211. // send progress indication since NSURLConnection won't be calling back
  212. // to us during uploads
  213. needsManualProgress_ = ![GTMHTTPFetcher doesSupportSentDataCallback];
  214. initialBodyLength_ = [[self postData] length];
  215. if (isRestartedUpload_) {
  216. if (![self isPaused]) {
  217. if (delegate) {
  218. [self setDelegate:delegate];
  219. finishedSel_ = finishedSEL;
  220. }
  221. [self uploadNextChunkWithOffset:kQueryServerForOffset];
  222. }
  223. return YES;
  224. }
  225. // we don't need a finish selector since we're overriding
  226. // -connectionDidFinishLoading
  227. return [super beginFetchWithDelegate:delegate
  228. didFinishSelector:NULL];
  229. }
  230. #if NS_BLOCKS_AVAILABLE
  231. - (BOOL)beginFetchWithCompletionHandler:(void (^)(NSData *data, NSError *error))handler {
  232. // we don't want to call into the delegate's completion block immediately
  233. // after the finish of the initial connection (the delegate is called only
  234. // when uploading finishes), so we substitute our own completion block to be
  235. // called when the initial connection finishes
  236. void (^holdBlock)(NSData *data, NSError *error) = [[handler copy] autorelease];
  237. BOOL flag = [super beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
  238. // callback
  239. if (!isRestartedUpload_) {
  240. if (error == nil) {
  241. // swap in the actual completion block now, as it will be called later
  242. // when the upload chunks have completed
  243. [completionBlock_ autorelease];
  244. completionBlock_ = [holdBlock copy];
  245. } else {
  246. // pass the error on to the actual completion block
  247. holdBlock(nil, error);
  248. }
  249. } else {
  250. // If there was no initial request, then this fetch is resuming some
  251. // other uploadFetcher's initial request, and the superclass's connection
  252. // is never used, so at this point we call the user's actual completion
  253. // block.
  254. holdBlock(data, error);
  255. }
  256. }];
  257. return flag;
  258. }
  259. #endif
  260. - (void)connection:(NSURLConnection *)connection
  261. didSendBodyData:(NSInteger)bytesWritten
  262. totalBytesWritten:(NSInteger)totalBytesWritten
  263. totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite {
  264. // ignore this callback if we're doing manual progress, mainly so that
  265. // we won't see duplicate progress callbacks when testing with
  266. // doesSupportSentDataCallback turned off
  267. if (needsManualProgress_) return;
  268. [self uploadFetcher:self
  269. didSendBytes:bytesWritten
  270. totalBytesSent:totalBytesWritten
  271. totalBytesExpectedToSend:totalBytesExpectedToWrite];
  272. }
  273. - (BOOL)shouldReleaseCallbacksUponCompletion {
  274. // we don't want the superclass to release the delegate and callback
  275. // blocks once the initial fetch has finished
  276. //
  277. // this is invoked for only successful completion of the connection;
  278. // an error always will invoke and release the callbacks
  279. return NO;
  280. }
  281. - (void)invokeFinalCallbacksWithData:(NSData *)data
  282. error:(NSError *)error
  283. shouldInvalidateLocation:(BOOL)shouldInvalidateLocation {
  284. // avoid issues due to being released indirectly by a callback
  285. [[self retain] autorelease];
  286. if (shouldInvalidateLocation) {
  287. [self setLocationURL:nil];
  288. }
  289. if (delegate_ && delegateFinishedSEL_) {
  290. [self invokeFetchCallback:delegateFinishedSEL_
  291. target:delegate_
  292. data:data
  293. error:error];
  294. }
  295. #if NS_BLOCKS_AVAILABLE
  296. if (completionBlock_) {
  297. completionBlock_(data, error);
  298. }
  299. [self setLocationChangeBlock:nil];
  300. #endif
  301. [self releaseCallbacks];
  302. }
  303. - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
  304. // handle failure of the initial fetch as a simple fetcher failure, including
  305. // calling the delegate, and allowing retry to happen if appropriate
  306. SEL prevSel = finishedSel_; // should be null
  307. finishedSel_ = delegateFinishedSEL_;
  308. [super connection:connection didFailWithError:error];
  309. // If retry later happens and succeeds, it shouldn't message the delegate
  310. // since we'll continue to chunk uploads.
  311. finishedSel_ = prevSel;
  312. }
  313. - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
  314. // we land here once the initial fetch sending the initial POST body
  315. // has completed
  316. // let the superclass end its connection
  317. [super connectionDidFinishLoading:connection];
  318. NSInteger statusCode = [super statusCode];
  319. [self setStatusCode:statusCode];
  320. NSData *downloadedData = [self downloadedData];
  321. // we need to get the upload URL from the location header to continue
  322. NSDictionary *responseHeaders = [self responseHeaders];
  323. NSString *locationURLStr = [responseHeaders objectForKey:@"Location"];
  324. NSError *error = nil;
  325. if (statusCode >= 300) {
  326. if (retryTimer_) return;
  327. error = [NSError errorWithDomain:kGTMHTTPFetcherStatusDomain
  328. code:statusCode
  329. userInfo:nil];
  330. } else if ([downloadedData length] > 0) {
  331. // The initial response of the resumable upload protocol should have an
  332. // empty body
  333. //
  334. // This problem typically happens because the upload create/edit link URL was
  335. // not supplied with the request, and the server is thus expecting a non-
  336. // resumable request/response. It may also happen if an error JSON error body
  337. // is returned.
  338. //
  339. // We'll consider this status 501 Not Implemented rather than try to parse
  340. // the body to determine the actual error, but will provide the data
  341. // as userInfo for clients to inspect.
  342. NSDictionary *userInfo = [NSDictionary dictionaryWithObject:downloadedData
  343. forKey:kGTMHTTPFetcherStatusDataKey];
  344. error = [NSError errorWithDomain:kGTMHTTPFetcherStatusDomain
  345. code:501
  346. userInfo:userInfo];
  347. } else {
  348. #if DEBUG
  349. NSAssert([locationURLStr length] > 0, @"need upload location hdr");
  350. #endif
  351. if ([locationURLStr length] == 0) {
  352. // we cannot continue since we do not know the location to use
  353. // as our upload destination
  354. //
  355. // we'll consider this status 501 Not Implemented
  356. error = [NSError errorWithDomain:kGTMHTTPFetcherStatusDomain
  357. code:501
  358. userInfo:nil];
  359. }
  360. }
  361. if (error) {
  362. [self invokeFinalCallbacksWithData:downloadedData
  363. error:error
  364. shouldInvalidateLocation:YES];
  365. return;
  366. }
  367. [self setLocationURL:[NSURL URLWithString:locationURLStr]];
  368. // we've now sent all of the initial post body data, so we need to include
  369. // its size in future progress indicator callbacks
  370. initialBodySent_ = initialBodyLength_;
  371. if (needsManualProgress_) {
  372. [self reportProgressManually];
  373. }
  374. // just in case the user paused us during the initial fetch...
  375. if (![self isPaused]) {
  376. [self uploadNextChunkWithOffset:0];
  377. }
  378. }
  379. - (void)retryFetch {
  380. // Override the fetcher's retryFetch to retry with the saved delegateFinishedSEL_.
  381. [self stopFetchReleasingCallbacks:NO];
  382. [self beginFetchWithDelegate:delegate_
  383. didFinishSelector:delegateFinishedSEL_];
  384. }
  385. #pragma mark Chunk fetching methods
  386. - (void)uploadNextChunkWithOffset:(NSUInteger)offset {
  387. // use the properties in each chunk fetcher
  388. NSMutableDictionary *props = [self properties];
  389. [self uploadNextChunkWithOffset:offset
  390. fetcherProperties:props];
  391. }
  392. - (void)uploadNextChunkWithOffset:(NSUInteger)offset
  393. fetcherProperties:(NSMutableDictionary *)props {
  394. // upload another chunk
  395. NSUInteger chunkSize = [self chunkSize];
  396. NSString *rangeStr, *lengthStr;
  397. NSData *chunkData;
  398. NSUInteger dataLen = [self fullUploadLength];
  399. if (offset == kQueryServerForOffset) {
  400. // resuming, so we'll initially send an empty data block and wait for the
  401. // server to tell us where the current offset really is
  402. chunkData = [NSData data];
  403. rangeStr = [NSString stringWithFormat:@"bytes */%llu",
  404. (unsigned long long)dataLen];
  405. lengthStr = @"0";
  406. offset = 0;
  407. } else {
  408. // uploading the next data chunk
  409. if (dataLen == 0) {
  410. #if DEBUG
  411. NSAssert(offset == 0, @"offset %llu for empty data length", (unsigned long long)offset);
  412. #endif
  413. chunkData = [NSData data];
  414. rangeStr = @"bytes */0";
  415. lengthStr = @"0";
  416. } else {
  417. #if DEBUG
  418. NSAssert(offset < dataLen , @"offset %llu exceeds data length %llu",
  419. (unsigned long long)offset, (unsigned long long)dataLen);
  420. #endif
  421. NSUInteger thisChunkSize = chunkSize;
  422. // if the chunk size is bigger than the remaining data, or else
  423. // it's close enough in size to the remaining data that we'd rather
  424. // avoid having a whole extra http fetch for the leftover bit, then make
  425. // this chunk size exactly match the remaining data size
  426. BOOL isChunkTooBig = (thisChunkSize + offset > dataLen);
  427. BOOL isChunkAlmostBigEnough = (dataLen - offset < thisChunkSize + 2500);
  428. if (isChunkTooBig || isChunkAlmostBigEnough) {
  429. thisChunkSize = dataLen - offset;
  430. }
  431. chunkData = [self uploadSubdataWithOffset:offset
  432. length:thisChunkSize];
  433. rangeStr = [NSString stringWithFormat:@"bytes %llu-%llu/%llu",
  434. (unsigned long long)offset,
  435. (unsigned long long)(offset + thisChunkSize - 1),
  436. (unsigned long long)dataLen];
  437. lengthStr = [NSString stringWithFormat:@"%llu",
  438. (unsigned long long)thisChunkSize];
  439. }
  440. }
  441. // track the current offset for progress reporting
  442. [self setCurrentOffset:offset];
  443. //
  444. // make the request for fetching
  445. //
  446. // the chunk upload URL requires no authentication header
  447. NSURL *locURL = [self locationURL];
  448. NSMutableURLRequest *chunkRequest = [NSMutableURLRequest requestWithURL:locURL];
  449. [chunkRequest setHTTPMethod:@"PUT"];
  450. // copy the user-agent from the original connection
  451. NSURLRequest *origRequest = [self mutableRequest];
  452. NSString *userAgent = [origRequest valueForHTTPHeaderField:@"User-Agent"];
  453. if ([userAgent length] > 0) {
  454. [chunkRequest setValue:userAgent forHTTPHeaderField:@"User-Agent"];
  455. }
  456. [chunkRequest setValue:rangeStr forHTTPHeaderField:@"Content-Range"];
  457. [chunkRequest setValue:lengthStr forHTTPHeaderField:@"Content-Length"];
  458. NSString *uploadMIMEType = [self uploadMIMEType];
  459. [chunkRequest setValue:uploadMIMEType forHTTPHeaderField:@"Content-Type"];
  460. //
  461. // make a new fetcher
  462. //
  463. GTMHTTPFetcher *chunkFetcher;
  464. chunkFetcher = [GTMHTTPFetcher fetcherWithRequest:chunkRequest];
  465. [chunkFetcher setDelegateQueue:[self delegateQueue]];
  466. [chunkFetcher setRunLoopModes:[self runLoopModes]];
  467. // if the upload fetcher has a comment, use the same comment for chunks
  468. NSString *baseComment = [self comment];
  469. if (baseComment) {
  470. [chunkFetcher setCommentWithFormat:@"%@ (%@)", baseComment, rangeStr];
  471. }
  472. // give the chunk fetcher the same properties as the previous chunk fetcher
  473. [chunkFetcher setProperties:props];
  474. // post the appropriate subset of the full data
  475. [chunkFetcher setPostData:chunkData];
  476. // copy other fetcher settings to the new fetcher
  477. [chunkFetcher setRetryEnabled:[self isRetryEnabled]];
  478. [chunkFetcher setMaxRetryInterval:[self maxRetryInterval]];
  479. [chunkFetcher setSentDataSelector:[self sentDataSelector]];
  480. [chunkFetcher setCookieStorageMethod:[self cookieStorageMethod]];
  481. if ([self isRetryEnabled]) {
  482. // we interpose our own retry method both so the sender is the upload
  483. // fetcher, and so we can change the request to ask the server to
  484. // tell us where to resume the chunk
  485. [chunkFetcher setRetrySelector:@selector(chunkFetcher:willRetry:forError:)];
  486. }
  487. [self setMutableRequest:chunkRequest];
  488. // when fetching chunks, a 308 status means "upload more chunks", but
  489. // success (200 or 201 status) and other failures are no different than
  490. // for the regular object fetchers
  491. BOOL didFetch = [chunkFetcher beginFetchWithDelegate:self
  492. didFinishSelector:@selector(chunkFetcher:finishedWithData:error:)];
  493. if (!didFetch) {
  494. // something went horribly wrong, like the chunk upload URL is invalid
  495. NSError *error = [NSError errorWithDomain:kGTMHTTPFetcherErrorDomain
  496. code:kGTMHTTPFetcherErrorChunkUploadFailed
  497. userInfo:nil];
  498. [self invokeFinalCallbacksWithData:nil
  499. error:error
  500. shouldInvalidateLocation:YES];
  501. [self destroyChunkFetcher];
  502. } else {
  503. // hang on to the fetcher in case we need to cancel it
  504. [self setChunkFetcher:chunkFetcher];
  505. }
  506. }
  507. - (void)reportProgressManually {
  508. // reportProgressManually should be called only when there's no
  509. // NSURLConnection support for sent data callbacks
  510. // the user wants upload progress, and there's no support in NSURLConnection
  511. // for it, so we'll provide it here after each chunk
  512. //
  513. // the progress will be based on the uploadData and currentOffset,
  514. // so we can pass zeros
  515. [self uploadFetcher:self
  516. didSendBytes:0
  517. totalBytesSent:0
  518. totalBytesExpectedToSend:0];
  519. }
  520. - (void)chunkFetcher:(GTMHTTPFetcher *)chunkFetcher finishedWithData:(NSData *)data error:(NSError *)error {
  521. [self setStatusCode:[chunkFetcher statusCode]];
  522. [self setResponseHeaders:[chunkFetcher responseHeaders]];
  523. if (error) {
  524. int status = (int)[error code];
  525. // status 308 is "resume incomplete", meaning we should get the offset
  526. // from the Range header and upload the next chunk
  527. //
  528. // any other status really is an error
  529. if (status == 308) {
  530. [self handleResumeIncompleteStatusForChunkFetcher:chunkFetcher];
  531. return;
  532. } else {
  533. // some unexpected status has occurred; handle it as we would a regular
  534. // object fetcher failure
  535. error = [NSError errorWithDomain:kGTMHTTPFetcherStatusDomain
  536. code:status
  537. userInfo:nil];
  538. [self invokeFinalCallbacksWithData:data
  539. error:error
  540. shouldInvalidateLocation:NO];
  541. [self destroyChunkFetcher];
  542. return;
  543. }
  544. } else {
  545. // the final chunk has uploaded successfully
  546. #if DEBUG
  547. NSInteger status = [chunkFetcher statusCode];
  548. NSAssert1(status == 200 || status == 201,
  549. @"unexpected chunks status %d", (int)status);
  550. #endif
  551. // take the chunk fetcher's data as our own
  552. self.downloadedData = data;
  553. if (needsManualProgress_) {
  554. // do a final upload progress report, indicating all of the chunk data
  555. // has been sent
  556. NSUInteger fullDataLength = [self fullUploadLength] + initialBodyLength_;
  557. [self setCurrentOffset:fullDataLength];
  558. [self reportProgressManually];
  559. }
  560. // we're done
  561. [self invokeFinalCallbacksWithData:data
  562. error:error
  563. shouldInvalidateLocation:YES];
  564. [self destroyChunkFetcher];
  565. }
  566. }
  567. - (void)handleResumeIncompleteStatusForChunkFetcher:(GTMHTTPFetcher *)chunkFetcher {
  568. NSDictionary *responseHeaders = [chunkFetcher responseHeaders];
  569. // parse the Range header from the server, since that tells us where we really
  570. // want the next chunk to begin.
  571. //
  572. // lack of a range header means the server has no bytes stored for this upload
  573. NSString *rangeStr = [responseHeaders objectForKey:@"Range"];
  574. NSUInteger newOffset = 0;
  575. if (rangeStr != nil) {
  576. // parse a content-range, like "bytes=0-999", to find where our new
  577. // offset for uploading from the data really is (at the end of the
  578. // range)
  579. NSScanner *scanner = [NSScanner scannerWithString:rangeStr];
  580. long long rangeStart = 0, rangeEnd = 0;
  581. if ([scanner scanString:@"bytes=" intoString:nil]
  582. && [scanner scanLongLong:&rangeStart]
  583. && [scanner scanString:@"-" intoString:nil]
  584. && [scanner scanLongLong:&rangeEnd]) {
  585. newOffset = (NSUInteger)rangeEnd + 1;
  586. }
  587. }
  588. [self setCurrentOffset:newOffset];
  589. if (needsManualProgress_) {
  590. [self reportProgressManually];
  591. }
  592. // if the response specifies a location, use that for future chunks
  593. NSString *locationURLStr = [responseHeaders objectForKey:@"Location"];
  594. if ([locationURLStr length] > 0) {
  595. [self setLocationURL:[NSURL URLWithString:locationURLStr]];
  596. }
  597. // we want to destroy this chunk fetcher before creating the next one, but
  598. // we want to pass on its properties
  599. NSMutableDictionary *props = [[[chunkFetcher properties] retain] autorelease];
  600. // we no longer need to be able to cancel this chunkFetcher
  601. [self destroyChunkFetcher];
  602. // We may in the future handle Retry-After and ETag headers per
  603. // http://code.google.com/p/gears/wiki/ResumableHttpRequestsProposal
  604. // but they are not currently sent by the upload server
  605. [self uploadNextChunkWithOffset:newOffset
  606. fetcherProperties:props];
  607. }
  608. -(BOOL)chunkFetcher:(GTMHTTPFetcher *)chunkFetcher willRetry:(BOOL)willRetry forError:(NSError *)error {
  609. if ([error code] == 308
  610. && [[error domain] isEqual:kGTMHTTPFetcherStatusDomain]) {
  611. // 308 is a normal chunk fethcher response, not an error
  612. // that needs to be retried
  613. return NO;
  614. }
  615. if (delegate_ && retrySel_) {
  616. // call the client with the upload fetcher as the sender (not the chunk
  617. // fetcher) to find out if it wants to retry
  618. willRetry = [self invokeRetryCallback:retrySel_
  619. target:delegate_
  620. willRetry:willRetry
  621. error:error];
  622. }
  623. #if NS_BLOCKS_AVAILABLE
  624. if (retryBlock_) {
  625. willRetry = retryBlock_(willRetry, error);
  626. }
  627. #endif
  628. if (willRetry) {
  629. // change the request being retried into a query to the server to
  630. // tell us where to resume
  631. NSMutableURLRequest *chunkRequest = [chunkFetcher mutableRequest];
  632. NSUInteger dataLen = [self fullUploadLength];
  633. NSString *rangeStr = [NSString stringWithFormat:@"bytes */%llu",
  634. (unsigned long long)dataLen];
  635. [chunkRequest setValue:rangeStr forHTTPHeaderField:@"Content-Range"];
  636. [chunkRequest setValue:@"0" forHTTPHeaderField:@"Content-Length"];
  637. [chunkFetcher setPostData:[NSData data]];
  638. // we don't know what our actual offset is anymore, but the server
  639. // will tell us
  640. [self setCurrentOffset:0];
  641. }
  642. return willRetry;
  643. }
  644. - (void)destroyChunkFetcher {
  645. [chunkFetcher_ stopFetching];
  646. [chunkFetcher_ setProperties:nil];
  647. [chunkFetcher_ autorelease];
  648. chunkFetcher_ = nil;
  649. }
  650. // the chunk fetchers use this as their sentData method
  651. - (void)uploadFetcher:(GTMHTTPFetcher *)chunkFetcher
  652. didSendBytes:(NSInteger)bytesSent
  653. totalBytesSent:(NSInteger)totalBytesSent
  654. totalBytesExpectedToSend:(NSInteger)totalBytesExpected {
  655. // the actual total bytes sent include the initial XML sent, plus the
  656. // offset into the batched data prior to this fetcher
  657. totalBytesSent += initialBodySent_ + currentOffset_;
  658. // the total bytes expected include the initial XML and the full chunked
  659. // data, independent of how big this fetcher's chunk is
  660. totalBytesExpected = (NSInteger)(initialBodyLength_ + [self fullUploadLength]);
  661. if (delegate_ && delegateSentDataSEL_) {
  662. // ensure the chunk fetcher survives the callback in case the user pauses
  663. // the upload process
  664. [[chunkFetcher retain] autorelease];
  665. [self invokeSentDataCallback:delegateSentDataSEL_
  666. target:delegate_
  667. didSendBodyData:bytesSent
  668. totalBytesWritten:totalBytesSent
  669. totalBytesExpectedToWrite:totalBytesExpected];
  670. }
  671. #if NS_BLOCKS_AVAILABLE
  672. if (sentDataBlock_) {
  673. sentDataBlock_(bytesSent, totalBytesSent, totalBytesExpected);
  674. }
  675. #endif
  676. }
  677. #pragma mark -
  678. - (BOOL)isPaused {
  679. return isPaused_;
  680. }
  681. - (void)pauseFetching {
  682. isPaused_ = YES;
  683. // pausing just means stopping the current chunk from uploading;
  684. // when we resume, the magic offset value will force us to send
  685. // a request to the server to figure out what bytes to start sending
  686. //
  687. // we won't try to cancel the initial data upload, but rather will look for
  688. // the magic offset value in -connectionDidFinishLoading before
  689. // creating first initial chunk fetcher, just in case the user
  690. // paused during the initial data upload
  691. [self destroyChunkFetcher];
  692. }
  693. - (void)resumeFetching {
  694. if (isPaused_) {
  695. isPaused_ = NO;
  696. [self uploadNextChunkWithOffset:kQueryServerForOffset];
  697. }
  698. }
  699. - (void)stopFetching {
  700. // overrides the superclass
  701. [self destroyChunkFetcher];
  702. [super stopFetching];
  703. }
  704. #pragma mark -
  705. @synthesize uploadData = uploadData_,
  706. uploadFileHandle = uploadFileHandle_,
  707. uploadMIMEType = uploadMIMEType_,
  708. chunkSize = chunkSize_,
  709. currentOffset = currentOffset_,
  710. chunkFetcher = chunkFetcher_;
  711. #if NS_BLOCKS_AVAILABLE
  712. @synthesize locationChangeBlock = locationChangeBlock_;
  713. #endif
  714. @dynamic activeFetcher;
  715. @dynamic responseHeaders;
  716. @dynamic statusCode;
  717. - (NSDictionary *)responseHeaders {
  718. // overrides the superclass
  719. // if asked for the fetcher's response, use the most recent fetcher
  720. if (responseHeaders_) {
  721. return responseHeaders_;
  722. } else {
  723. // no chunk fetcher yet completed, so return whatever we have from the
  724. // initial fetch
  725. return [super responseHeaders];
  726. }
  727. }
  728. - (void)setResponseHeaders:(NSDictionary *)dict {
  729. [responseHeaders_ autorelease];
  730. responseHeaders_ = [dict retain];
  731. }
  732. - (NSInteger)statusCode {
  733. if (statusCode_ != -1) {
  734. // overrides the superclass to indicate status appropriate to the initial
  735. // or latest chunk fetch
  736. return statusCode_;
  737. } else {
  738. return [super statusCode];
  739. }
  740. }
  741. - (void)setStatusCode:(NSInteger)val {
  742. statusCode_ = val;
  743. }
  744. - (SEL)sentDataSelector {
  745. // overrides the superclass
  746. #if NS_BLOCKS_AVAILABLE
  747. BOOL hasSentDataBlock = (sentDataBlock_ != NULL);
  748. #else
  749. BOOL hasSentDataBlock = NO;
  750. #endif
  751. if ((delegateSentDataSEL_ || hasSentDataBlock) && !needsManualProgress_) {
  752. return @selector(uploadFetcher:didSendBytes:totalBytesSent:totalBytesExpectedToSend:);
  753. } else {
  754. return NULL;
  755. }
  756. }
  757. - (void)setSentDataSelector:(SEL)theSelector {
  758. // overrides the superclass
  759. delegateSentDataSEL_ = theSelector;
  760. }
  761. - (GTMHTTPFetcher *)activeFetcher {
  762. if (chunkFetcher_) {
  763. return chunkFetcher_;
  764. } else {
  765. return self;
  766. }
  767. }
  768. - (NSURL *)locationURL {
  769. return locationURL_;
  770. }
  771. - (void)setLocationURL:(NSURL *)url {
  772. if (url != locationURL_) {
  773. [locationURL_ release];
  774. locationURL_ = [url retain];
  775. #if NS_BLOCKS_AVAILABLE
  776. if (locationChangeBlock_) {
  777. locationChangeBlock_(url);
  778. }
  779. #endif
  780. }
  781. }
  782. @end
  783. #endif // #if !GDATA_REQUIRE_SERVICE_INCLUDES