PageRenderTime 254ms CodeModel.GetById 13ms app.highlight 227ms RepoModel.GetById 1ms app.codeStats 1ms

/core/externals/update-engine/externals/gdata-objectivec-client/Source/BaseClasses/GDataServiceBase.m

http://macfuse.googlecode.com/
Objective C | 2142 lines | 1417 code | 421 blank | 304 comment | 199 complexity | 97452e9420da4152311b1b41e2324812 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1/* Copyright (c) 2007 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//
  17//  GDataServiceBase.m
  18//
  19
  20#import <TargetConditionals.h>
  21#if TARGET_OS_MAC
  22#include <sys/utsname.h>
  23#endif
  24
  25#if TARGET_OS_IPHONE
  26#import <UIKit/UIKit.h>
  27#endif
  28
  29#define GDATASERVICEBASE_DEFINE_GLOBALS 1
  30#import "GDataServiceBase.h"
  31#import "GDataServerError.h"
  32#import "GDataFramework.h"
  33
  34static NSString *const kXMLErrorContentType = @"application/vnd.google.gdata.error+xml";
  35
  36static NSString* const kFetcherDelegateKey             = @"_delegate";
  37static NSString* const kFetcherObjectClassKey          = @"_objectClass";
  38static NSString* const kFetcherFinishedSelectorKey     = @"_finishedSelector";
  39static NSString* const kFetcherCompletionHandlerKey    = @"_completionHandler";
  40static NSString* const kFetcherTicketKey               = @"_ticket";
  41static NSString* const kFetcherStreamDataKey           = @"_streamData";
  42static NSString* const kFetcherParsedObjectKey         = @"_parsedObject";
  43static NSString* const kFetcherParseErrorKey           = @"_parseError";
  44static NSString* const kFetcherCallbackThreadKey       = @"_callbackThread";
  45static NSString* const kFetcherCallbackRunLoopModesKey = @"_runLoopModes";
  46
  47NSString* const kFetcherRetryInvocationKey = @"_retryInvocation";
  48
  49static const NSUInteger kMaxNumberOfNextLinksFollowed = 25;
  50
  51// we'll enforce 50K chunks minimum just to avoid the server getting hit
  52// with too many small upload chunks
  53static const NSUInteger kMinimumUploadChunkSize = 50000;
  54
  55// XorPlainMutableData is a simple way to keep passwords held in heap objects
  56// from being visible as plain-text
  57static void XorPlainMutableData(NSMutableData *mutableData) {
  58
  59  // this helps avoid storing passwords on the heap in plaintext
  60  const unsigned char theXORValue = 0x95; // 0x95 = 0xb10010101
  61
  62  unsigned char *dataPtr = [mutableData mutableBytes];
  63  NSUInteger length = [mutableData length];
  64
  65  for (NSUInteger idx = 0; idx < length; idx++) {
  66    dataPtr[idx] ^= theXORValue;
  67  }
  68}
  69
  70
  71// category to provide opaque access to tickets stored in fetcher properties
  72@implementation GTMHTTPFetcher (GDataServiceTicketAdditions)
  73- (id)GDataTicket {
  74  return [self propertyForKey:kFetcherTicketKey];
  75}
  76@end
  77
  78// If GTMHTTPUploadFetcher is available, it can be used for chunked uploads
  79//
  80// We locally declare some methods of GTMHTTPUploadFetcher so we
  81// do not need to import the header, as some projects may not have it available
  82@interface GTMHTTPUploadFetcher : GTMHTTPFetcher
  83+ (GTMHTTPUploadFetcher *)uploadFetcherWithRequest:(NSURLRequest *)request
  84                                        uploadData:(NSData *)data
  85                                    uploadMIMEType:(NSString *)uploadMIMEType
  86                                         chunkSize:(NSUInteger)chunkSize
  87                                    fetcherService:(GTMHTTPFetcherService *)fetcherService;
  88+ (GTMHTTPUploadFetcher *)uploadFetcherWithRequest:(NSURLRequest *)request
  89                                  uploadFileHandle:(NSFileHandle *)uploadFileHandle
  90                                    uploadMIMEType:(NSString *)uploadMIMEType
  91                                         chunkSize:(NSUInteger)chunkSize
  92                                    fetcherService:(GTMHTTPFetcherService *)fetcherService;
  93+ (GTMHTTPUploadFetcher *)uploadFetcherWithLocation:(NSURL *)locationURL
  94                                   uploadFileHandle:(NSFileHandle *)uploadFileHandle
  95                                     uploadMIMEType:(NSString *)uploadMIMEType
  96                                          chunkSize:(NSUInteger)chunkSize
  97                                     fetcherService:(GTMHTTPFetcherService *)fetcherService;
  98- (void)pauseFetching;
  99- (void)resumeFetching;
 100- (BOOL)isPaused;
 101@end
 102
 103@interface GDataEntryBase (PrivateMethods)
 104- (NSDictionary *)contentHeaders;
 105@end
 106
 107@interface GDataServiceBase (PrivateMethods)
 108- (BOOL)fetchNextFeedWithURL:(NSURL *)nextFeedURL
 109                    delegate:(id)delegate
 110         didFinishedSelector:(SEL)finishedSelector
 111           completionHandler:(GDataServiceCompletionHandler)completionHandler
 112                      ticket:(GDataServiceTicketBase *)ticket;
 113
 114- (NSDictionary *)userInfoForErrorResponseData:(NSData *)data
 115                                   contentType:(NSString *)contentType
 116                              previousUserInfo:(NSDictionary *)previousUserInfo;
 117
 118- (void)objectFetcher:(GTMHTTPFetcher *)fetcher
 119     finishedWithData:(NSData *)data
 120                error:(NSError *)error;
 121- (void)objectFetcher:(GTMHTTPFetcher *)fetcher
 122       failedWithData:(NSData *)data
 123                error:(NSError *)error;
 124- (BOOL)objectFetcher:(GTMHTTPFetcher *)fetcher
 125            willRetry:(BOOL)willRetry
 126             forError:(NSError *)error;
 127- (void)objectFetcher:(GTMHTTPFetcher *)fetcher
 128         didSendBytes:(NSInteger)bytesSent
 129       totalBytesSent:(NSInteger)totalBytesSent
 130totalBytesExpectedToSend:(NSInteger)totalBytesExpected;
 131
 132- (void)parseObjectFromDataOfFetcher:(GTMHTTPFetcher *)fetcher;
 133- (void)handleParsedObjectForFetcher:(GTMHTTPFetcher *)fetcher;
 134@end
 135
 136@implementation GDataServiceBase
 137
 138+ (Class)ticketClass {
 139  return [GDataServiceTicketBase class];
 140}
 141
 142- (id)init {
 143  self = [super init];
 144  if (self) {
 145
 146#if GDATA_IPHONE || (MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_5)
 147    operationQueue_ = [[NSOperationQueue alloc] init];
 148#elif !GDATA_SKIP_PARSE_THREADING
 149    // Avoid NSOperationQueue prior to 10.5.6, per
 150    // http://www.mikeash.com/?page=pyblog/use-nsoperationqueue.html
 151    SInt32 bcdSystemVersion = 0;
 152    (void) Gestalt(gestaltSystemVersion, &bcdSystemVersion);
 153
 154    if (bcdSystemVersion >= 0x1057) {
 155      operationQueue_ = [[NSOperationQueue alloc] init];
 156    }
 157#else
 158    // operationQueue_ defaults to nil, so parsing will be done immediately
 159    // on the current thread
 160#endif
 161
 162    fetcherService_ = [[GTMHTTPFetcherService alloc] init];
 163    [fetcherService_ setShouldRememberETags:YES];
 164
 165    cookieStorageMethod_ = -1;
 166
 167    NSUInteger chunkSize = [[self class] defaultServiceUploadChunkSize];
 168    [self setServiceUploadChunkSize:chunkSize];
 169  }
 170  return self;
 171}
 172
 173- (void)dealloc {
 174  [operationQueue_ release];
 175
 176  [serviceVersion_ release];
 177  [userAgent_ release];
 178  [fetcherService_ release];
 179
 180  [username_ release];
 181  [password_ release];
 182
 183  [serviceUserData_ release];
 184  [serviceProperties_ release];
 185  [serviceSurrogates_ release];
 186
 187#if NS_BLOCKS_AVAILABLE
 188  [serviceUploadProgressBlock_ release];
 189#endif
 190
 191  [super dealloc];
 192}
 193
 194+ (NSString *)systemVersionString {
 195  NSString *str = GTMSystemVersionString();
 196  return str;
 197}
 198
 199- (NSString *)requestUserAgent {
 200
 201  NSString *userAgent = [self userAgent];
 202
 203  if ([userAgent length] == 0 || [userAgent hasPrefix:@"MyCompany-"]) {
 204
 205    // the service instance is missing an explicit user-agent; use the bundle ID
 206    // or process name
 207    userAgent = [[self class] defaultApplicationIdentifier];
 208  }
 209
 210  NSString *requestUserAgent = userAgent;
 211
 212  // if the user agent already specifies the library version, we'll
 213  // use it verbatim in the request
 214  NSString *libraryString = @"GData-ObjectiveC";
 215  NSRange libRange = [userAgent rangeOfString:libraryString
 216                                      options:NSCaseInsensitiveSearch];
 217  if (libRange.location == NSNotFound) {
 218    // the user agent doesn't specify the client library, so append that
 219    // information, and the system version
 220    NSString *libVersionString = GDataFrameworkVersionString();
 221
 222    NSString *systemString = [[self class] systemVersionString];
 223
 224    // Google servers look for gzip in the user agent before sending gzip-
 225    // encoded responses.  See Service.java
 226    requestUserAgent = [NSString stringWithFormat:@"%@ %@/%@ %@ (gzip)",
 227      userAgent, libraryString, libVersionString, systemString];
 228  }
 229  return requestUserAgent;
 230}
 231
 232- (NSMutableURLRequest *)requestForURL:(NSURL *)url
 233                                  ETag:(NSString *)etag
 234                            httpMethod:(NSString *)httpMethod
 235                                ticket:(GDataServiceTicketBase *)ticket {
 236
 237  // subclasses may add headers to this
 238  NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] initWithURL:url
 239                                                               cachePolicy:NSURLRequestReloadIgnoringCacheData
 240                                                           timeoutInterval:60] autorelease];
 241
 242  NSString *requestUserAgent = [self requestUserAgent];
 243  [request setValue:requestUserAgent forHTTPHeaderField:@"User-Agent"];
 244
 245  NSString *serviceVersion = [self serviceVersion];
 246  if ([serviceVersion length] > 0) {
 247
 248    // only add a version header if the URL lacks a v= version parameter
 249    NSString *queryString = [url query];
 250    if  (queryString == nil
 251         || ([queryString rangeOfString:@"&v="].location == NSNotFound
 252             && ![queryString hasPrefix:@"v="])) {
 253
 254      [request setValue:serviceVersion forHTTPHeaderField:@"GData-Version"];
 255    }
 256  }
 257
 258  if ([httpMethod length] > 0) {
 259    [request setHTTPMethod:httpMethod];
 260  }
 261
 262  if ([etag length] > 0) {
 263
 264    // it's rather unexpected for an etagged object to be provided for a GET,
 265    // but we'll check for an etag anyway, similar to HttpGDataRequest.java,
 266    // and if present use it to request only an unchanged resource
 267
 268    BOOL isDoingHTTPGet = (httpMethod == nil
 269               || [httpMethod caseInsensitiveCompare:@"GET"] == NSOrderedSame);
 270
 271    if (isDoingHTTPGet) {
 272
 273      // set the etag header, even if weak, indicating we don't want
 274      // another copy of the resource if it's the same as the object
 275      [request setValue:etag forHTTPHeaderField:@"If-None-Match"];
 276
 277    } else {
 278
 279      // if we're doing PUT or DELETE, set the etag header indicating
 280      // we only want to update the resource if our copy matches the current
 281      // one (unless the etag is weak and so shouldn't be a constraint at all)
 282      BOOL isWeakETag = [etag hasPrefix:@"W/"];
 283
 284      BOOL isModifying =
 285        [httpMethod caseInsensitiveCompare:@"PUT"] == NSOrderedSame
 286        || [httpMethod caseInsensitiveCompare:@"DELETE"] == NSOrderedSame
 287        || [httpMethod caseInsensitiveCompare:@"PATCH"] == NSOrderedSame;
 288
 289      if (isModifying && !isWeakETag) {
 290        [request setValue:etag forHTTPHeaderField:@"If-Match"];
 291      }
 292    }
 293  }
 294
 295  return request;
 296}
 297
 298- (NSMutableURLRequest *)requestForURL:(NSURL *)url
 299                                  ETag:(NSString *)etag
 300                            httpMethod:(NSString *)httpMethod {
 301  // this public entry point authenticates from the service object but
 302  // not from the auth token in the ticket
 303  return [self requestForURL:url ETag:etag httpMethod:httpMethod ticket:nil];
 304}
 305
 306// objectRequestForURL returns an NSMutableURLRequest for a GData object as XML
 307//
 308// the object is the object being sent to the server, or nil;
 309// the http method may be nil for get, or POST, PUT, DELETE
 310
 311- (NSMutableURLRequest *)objectRequestForURL:(NSURL *)url
 312                                      object:(GDataObject *)object
 313                                        ETag:(NSString *)etag
 314                                  httpMethod:(NSString *)httpMethod
 315                                      ticket:(GDataServiceTicketBase *)ticket {
 316  NSString *contentType = @"application/atom+xml; charset=utf-8";
 317
 318  if (object) {
 319    // if the object being sent has an etag, add it to the request header to
 320    // avoid retrieving a duplicate or to avoid writing over an updated
 321    // version of the resource on the server
 322    //
 323    // Typically, delete requests will provide an explicit ETag parameter, and
 324    // other requests will have the ETag carried inside the object being updated
 325    if (etag == nil) {
 326      SEL selEtag = @selector(ETag);
 327      if ([object respondsToSelector:selEtag]) {
 328        etag = [object performSelector:selEtag];
 329      }
 330    }
 331
 332    if (httpMethod != nil
 333        && [httpMethod caseInsensitiveCompare:@"PATCH"] == NSOrderedSame) {
 334      // PATCH isn't part of Atom
 335      contentType = @"application/xml; charset=utf-8";
 336    }
 337  }
 338
 339  NSMutableURLRequest *request = [self requestForURL:url
 340                                                ETag:etag
 341                                          httpMethod:httpMethod
 342                                              ticket:ticket];
 343
 344  [request setValue:@"application/atom+xml, text/xml" forHTTPHeaderField:@"Accept"];
 345
 346  [request setValue:contentType forHTTPHeaderField:@"Content-Type"];
 347
 348  [request setValue:@"no-cache" forHTTPHeaderField:@"Cache-Control"];
 349
 350  return request;
 351}
 352
 353#pragma mark -
 354
 355- (GDataServiceTicketBase *)fetchObjectWithURL:(NSURL *)feedURL
 356                                   objectClass:(Class)objectClass
 357                                  objectToPost:(GDataObject *)objectToPost
 358                                          ETag:(NSString *)etag
 359                                    httpMethod:(NSString *)httpMethod
 360                                      delegate:(id)delegate
 361                             didFinishSelector:(SEL)finishedSelector
 362                             completionHandler:(id)completionHandler // GDataServiceCompletionHandler
 363                          retryInvocationValue:(NSValue *)retryInvocationValue
 364                                        ticket:(GDataServiceTicketBase *)ticket {
 365
 366  GTMAssertSelectorNilOrImplementedWithArgs(delegate, finishedSelector, @encode(GDataServiceTicketBase *), @encode(GDataObject *), @encode(NSError *), 0);
 367
 368  // The completionHandler argument is declared as an id, not as a block
 369  // pointer, so this can be built with the 10.6 SDK and still run on 10.5.
 370  // If the argument were declared as a block pointer, the invocation for
 371  // fetchObjectWithURL: created in GDataServiceGoogle would cause an exception
 372  // since 10.5's NSInvocation cannot deal with encoding of block pointers.
 373
 374  NSURL *uploadLocationURL = [objectToPost uploadLocationURL];
 375
 376  // if no URL was supplied, and we're not resuming an upload, treat this as
 377  // if the fetch failed (below) and immediately return a nil ticket, skipping
 378  // the callbacks
 379  //
 380  // this might be considered normal (say, updating a read-only entry
 381  // that lacks an edit link) though higher-level calls may assert or
 382  // returns errors depending on the specific usage
 383  if (feedURL == nil && uploadLocationURL == nil) {
 384    return nil;
 385  }
 386
 387  // we need to create a ticket unless one was created earlier (like during
 388  // authentication)
 389  if (!ticket) {
 390    ticket = [[[self class] ticketClass] ticketForService:self];
 391  }
 392
 393  NSMutableURLRequest *request = nil;
 394  if (feedURL) {
 395    request = [self objectRequestForURL:feedURL
 396                                 object:objectToPost
 397                                   ETag:etag
 398                             httpMethod:httpMethod
 399                                 ticket:ticket];
 400  }
 401
 402  GTMAssertSelectorNilOrImplementedWithArgs(delegate, [ticket uploadProgressSelector],
 403      @encode(GDataServiceTicketBase *), @encode(unsigned long long),
 404      @encode(unsigned long long), 0);
 405  GTMAssertSelectorNilOrImplementedWithArgs(delegate, [ticket retrySelector],
 406      @encode(GDataServiceTicketBase *), @encode(BOOL), @encode(NSError *), 0);
 407
 408  //
 409  // package the object's XML and any upload data
 410  //
 411
 412  NSInputStream *streamToPost = nil;
 413  NSData *dataToPost = nil;
 414  SEL sentDataSel = NULL;
 415  NSData *uploadData = nil;
 416  NSFileHandle *uploadFileHandle = nil;
 417  BOOL shouldUploadDataChunked = ([self serviceUploadChunkSize] > 0);
 418  BOOL isUploadingDataChunked = NO;
 419
 420  NSMutableDictionary *uploadProperties = nil;
 421  if (objectToPost) {
 422
 423    NSData *xmlData = nil;
 424
 425    [ticket setPostedObject:objectToPost];
 426
 427    // An upload object may provide a custom input stream, such as for
 428    // multi-part MIME or media uploads.
 429    NSInputStream *contentInputStream = nil;
 430    unsigned long long contentLength = 0;
 431    NSDictionary *contentHeaders = nil;
 432
 433    uploadProperties = [NSMutableDictionary dictionary];
 434
 435    BOOL doesSupportSentData = [GTMHTTPFetcher doesSupportSentDataCallback];
 436
 437    uploadData = [objectToPost uploadData];
 438    uploadFileHandle = [objectToPost uploadFileHandle];
 439
 440    isUploadingDataChunked = ((uploadData != nil || uploadFileHandle != nil)
 441                              && shouldUploadDataChunked);
 442    BOOL shouldUploadDataOnly = ([objectToPost shouldUploadDataOnly]
 443                                 || uploadLocationURL != nil);
 444
 445    BOOL shouldReportUploadProgress;
 446#if NS_BLOCKS_AVAILABLE
 447    shouldReportUploadProgress = ([ticket uploadProgressSelector] != NULL
 448                                  || [ticket uploadProgressHandler] != NULL);
 449#else
 450    shouldReportUploadProgress = ([ticket uploadProgressSelector] != NULL);
 451#endif
 452
 453    if ((!isUploadingDataChunked) &&
 454        [objectToPost generateContentInputStream:&contentInputStream
 455                                          length:&contentLength
 456                                         headers:&contentHeaders]) {
 457      // now we have a stream containing the XML and the upload data
 458
 459    } else if (isUploadingDataChunked && shouldUploadDataOnly) {
 460      // no XML is needed since we're uploading data only, and there's no upload
 461      // data for the first fetch because the data will be sent chunked later,
 462      // so now we'll have an empty body with appropriate headers
 463      contentHeaders = [objectToPost performSelector:@selector(contentHeaders)];
 464      contentLength = 0;
 465
 466    } else {
 467      // we're sending either just XML, or XML now with chunked upload data
 468      // later
 469      xmlData = [[objectToPost XMLDocument] XMLData];
 470      contentLength = [xmlData length];
 471
 472      if (!shouldReportUploadProgress
 473          || doesSupportSentData
 474          || isUploadingDataChunked) {
 475        // there is no progress selector, or the fetcher can call us back on
 476        // sent data, or we're uploading chunked; we can post plain NSData,
 477        // which is simpler because it survives http redirects
 478        dataToPost = xmlData;
 479
 480      } else {
 481        // there is a progress selector and NSURLConnection won't call us back,
 482        // so we need to be posting a stream
 483        //
 484        // we'll make a default input stream from the XML data
 485        contentInputStream = [NSInputStream inputStreamWithData:xmlData];
 486
 487        // NSInputStream fails to retain or copy its data in 10.4, so we will
 488        // retain it in the callback dictionary.  We won't use this property in
 489        // the callbacks at all, but retaining it will ensure it's still around
 490        // until the upload completes.
 491        //
 492        // If it weren't for this bug in NSInputStream, we could just have
 493        // GDataObject's -contentInputStream method create the input stream for
 494        // us, so this service class wouldn't ever need to have the plain XML.
 495        [uploadProperties setObject:xmlData forKey:kFetcherStreamDataKey];
 496      }
 497
 498      if ([objectToPost respondsToSelector:@selector(uploadSlug)]) {
 499        NSString *slug = [objectToPost performSelector:@selector(uploadSlug)];
 500        if ([slug length] > 0) {
 501          [request setValue:slug forHTTPHeaderField:@"Slug"];
 502        }
 503      }
 504    }
 505
 506    if (contentHeaders) {
 507      // add the content-specific headers, if any
 508      for (NSString *key in contentHeaders) {
 509        NSString *value = [contentHeaders objectForKey:key];
 510        [request setValue:value forHTTPHeaderField:key];
 511      }
 512    }
 513
 514    streamToPost = contentInputStream;
 515
 516    NSNumber* num = [NSNumber numberWithUnsignedLongLong:contentLength];
 517    [request setValue:[num stringValue] forHTTPHeaderField:@"Content-Length"];
 518
 519    if (shouldReportUploadProgress) {
 520      if (doesSupportSentData || isUploadingDataChunked) {
 521        // there is sentData callback support in NSURLConnection,
 522        // or we're using an upload fetcher which can always call us
 523        // back
 524        sentDataSel = @selector(objectFetcher:didSendBytes:totalBytesSent:totalBytesExpectedToSend:);
 525      }
 526    }
 527  }
 528
 529  //
 530  // now that we have all the request header info ready,
 531  // create and set up the fetcher for this request
 532  //
 533  GTMHTTPFetcher* fetcher = nil;
 534
 535  if (isUploadingDataChunked) {
 536    // hang on to the user's requested chunk size, and ensure it's not tiny
 537    NSUInteger uploadChunkSize = [self serviceUploadChunkSize];
 538    if (uploadChunkSize < kMinimumUploadChunkSize) {
 539      uploadChunkSize = kMinimumUploadChunkSize;
 540    }
 541
 542#ifdef GDATA_TARGET_NAMESPACE
 543    // prepend the class name prefix
 544    Class uploadClass = NSClassFromString(@GDATA_TARGET_NAMESPACE_STRING
 545                                          "_GTMHTTPUploadFetcher");
 546#else
 547    Class uploadClass = NSClassFromString(@"GTMHTTPUploadFetcher");
 548#endif
 549    GDATA_ASSERT(uploadClass != nil, @"GTMHTTPUploadFetcher needed");
 550
 551    NSString *uploadMIMEType = [objectToPost uploadMIMEType];
 552
 553    if (uploadData) {
 554      fetcher = [uploadClass uploadFetcherWithRequest:request
 555                                           uploadData:uploadData
 556                                       uploadMIMEType:uploadMIMEType
 557                                            chunkSize:uploadChunkSize
 558                                       fetcherService:fetcherService_];
 559    } else if (uploadLocationURL) {
 560      fetcher = [uploadClass uploadFetcherWithLocation:uploadLocationURL
 561                                      uploadFileHandle:uploadFileHandle
 562                                        uploadMIMEType:uploadMIMEType
 563                                             chunkSize:uploadChunkSize
 564                                        fetcherService:fetcherService_];
 565    } else {
 566      fetcher = [uploadClass uploadFetcherWithRequest:request
 567                                     uploadFileHandle:uploadFileHandle
 568                                       uploadMIMEType:uploadMIMEType
 569                                            chunkSize:uploadChunkSize
 570                                       fetcherService:fetcherService_];
 571    }
 572  } else {
 573    fetcher = [fetcherService_ fetcherWithRequest:request];
 574  }
 575
 576  // allow the user to specify static app-wide cookies for fetching
 577  NSInteger cookieStorageMethod = [self cookieStorageMethod];
 578  if (cookieStorageMethod >= 0) {
 579    [fetcher setCookieStorageMethod:cookieStorageMethod];
 580  }
 581
 582  // copy the ticket's retry settings into the fetcher
 583  [fetcher setRetryEnabled:[ticket isRetryEnabled]];
 584  [fetcher setMaxRetryInterval:[ticket maxRetryInterval]];
 585
 586  if ([ticket retrySelector]) {
 587    [fetcher setRetrySelector:@selector(objectFetcher:willRetry:forError:)];
 588  }
 589
 590  // remember the object fetcher in the ticket
 591  [ticket setObjectFetcher:fetcher];
 592  [ticket setCurrentFetcher:fetcher];
 593
 594  // add parameters used by the callbacks
 595
 596  [fetcher setProperty:objectClass forKey:kFetcherObjectClassKey];
 597
 598  [fetcher setProperty:delegate forKey:kFetcherDelegateKey];
 599
 600  [fetcher setProperty:NSStringFromSelector(finishedSelector)
 601                forKey:kFetcherFinishedSelectorKey];
 602
 603  [fetcher setProperty:ticket
 604                forKey:kFetcherTicketKey];
 605
 606#if NS_BLOCKS_AVAILABLE
 607  // copy the completion handler block to the heap; this does nothing if the
 608  // block is already on the heap
 609  completionHandler = [[completionHandler copy] autorelease];
 610  [fetcher setProperty:completionHandler
 611                forKey:kFetcherCompletionHandlerKey];
 612#endif
 613
 614  // we want to add the invocation itself, not the value wrapper of it,
 615  // to ensure the invocation is retained until the callback completes
 616  NSInvocation *retryInvocation = [retryInvocationValue nonretainedObjectValue];
 617  [fetcher setProperty:retryInvocation
 618                forKey:kFetcherRetryInvocationKey];
 619
 620  // set the upload data
 621  GDATA_DEBUG_ASSERT(dataToPost == nil || streamToPost == nil,
 622                     @"upload conflict");
 623  [fetcher setPostData:dataToPost];
 624  [fetcher setPostStream:streamToPost];
 625  [fetcher setSentDataSelector:sentDataSel];
 626  [fetcher addPropertiesFromDictionary:uploadProperties];
 627
 628  // attach OAuth authorization object, if any
 629  //
 630  // the fetcher already has this authorizer from the fetcher service, but this
 631  // lets the client remove the authorizer from the ticket to make an
 632  // unauthorized request
 633  [fetcher setAuthorizer:[ticket authorizer]];
 634
 635  // add username/password, if any
 636  [self addAuthenticationToFetcher:fetcher];
 637
 638  if (finishedSelector) {
 639    [fetcher setComment:NSStringFromSelector(finishedSelector)];
 640  }
 641
 642  // failed fetches call the failure selector, which will delete the ticket
 643  BOOL didFetch = [fetcher beginFetchWithDelegate:self
 644                                didFinishSelector:@selector(objectFetcher:finishedWithData:error:)];
 645
 646  // If something weird happens and the networking callbacks have been called
 647  // already synchronously, we don't want to return the ticket since the caller
 648  // will never know when to stop retaining it, so we'll make sure the
 649  // success/failure callbacks have not yet been called by checking the
 650  // ticket
 651  if (!didFetch || [ticket hasCalledCallback]) {
 652    [fetcher setProperties:nil];
 653    [ticket setCurrentFetcher:nil];
 654    return nil;
 655  }
 656
 657  return ticket;
 658}
 659
 660- (void)invokeProgressCallbackForTicket:(GDataServiceTicketBase *)ticket
 661                         deliveredBytes:(unsigned long long)numReadSoFar
 662                             totalBytes:(unsigned long long)total {
 663
 664  SEL progressSelector = [ticket uploadProgressSelector];
 665  if (progressSelector) {
 666
 667    GTMHTTPFetcher *fetcher = [ticket objectFetcher];
 668    id delegate = [fetcher propertyForKey:kFetcherDelegateKey];
 669
 670    NSMethodSignature *signature = [delegate methodSignatureForSelector:progressSelector];
 671    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
 672
 673    [invocation setSelector:progressSelector];
 674    [invocation setTarget:delegate];
 675    [invocation setArgument:&ticket atIndex:2];
 676    [invocation setArgument:&numReadSoFar atIndex:3];
 677    [invocation setArgument:&total atIndex:4];
 678    [invocation invoke];
 679  }
 680
 681#if NS_BLOCKS_AVAILABLE
 682  GDataServiceUploadProgressHandler block = [ticket uploadProgressHandler];
 683  if (block) {
 684    block(ticket, numReadSoFar, total);
 685  }
 686#endif
 687}
 688
 689// sentData callback from fetcher
 690- (void)objectFetcher:(GTMHTTPFetcher *)fetcher
 691         didSendBytes:(NSInteger)bytesSent
 692       totalBytesSent:(NSInteger)totalBytesSent
 693totalBytesExpectedToSend:(NSInteger)totalBytesExpected {
 694
 695  GDataServiceTicketBase *ticket = [fetcher propertyForKey:kFetcherTicketKey];
 696
 697  [self invokeProgressCallbackForTicket:ticket
 698                         deliveredBytes:(unsigned long long)totalBytesSent
 699                             totalBytes:(unsigned long long)totalBytesExpected];
 700}
 701
 702- (void)objectFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data error:(NSError *)error {
 703  if (error) {
 704    [self objectFetcher:fetcher failedWithData:data error:error];
 705    return;
 706  }
 707
 708  // we now have the XML data for a feed or entry
 709
 710  // save the current thread into the fetcher, since we'll handle additional
 711  // fetches and callbacks on this thread
 712  [fetcher setProperty:[NSThread currentThread]
 713                forKey:kFetcherCallbackThreadKey];
 714
 715  // copy the run loop modes, if any, so we don't need to access them
 716  // from the parsing thread
 717  [fetcher setProperty:[[[self runLoopModes] copy] autorelease]
 718                forKey:kFetcherCallbackRunLoopModesKey];
 719
 720  // we post parsing notifications now to ensure they're on caller's
 721  // original thread
 722  GDataServiceTicketBase *ticket = [fetcher propertyForKey:kFetcherTicketKey];
 723  NSNotificationCenter *defaultNC = [NSNotificationCenter defaultCenter];
 724  [defaultNC postNotificationName:kGDataServiceTicketParsingStartedNotification
 725                           object:ticket];
 726
 727  // if there's an operation queue, then use that to schedule parsing on another
 728  // thread
 729  SEL parseSel = @selector(parseObjectFromDataOfFetcher:);
 730  if (operationQueue_ != nil) {
 731
 732    NSInvocationOperation *op;
 733    op = [[[NSInvocationOperation alloc] initWithTarget:self
 734                                               selector:parseSel
 735                                                 object:fetcher] autorelease];
 736    [ticket setParseOperation:op];
 737    [operationQueue_ addOperation:op];
 738    // the fetcher now belongs to the parsing thread
 739  } else {
 740    // parse on the current thread, on Mac OS X 10.4 through 10.5.7
 741    // or when the project defines GDATA_SKIP_PARSE_THREADING
 742    [self performSelector:parseSel
 743               withObject:fetcher];
 744  }
 745}
 746
 747- (void)parseObjectFromDataOfFetcher:(GTMHTTPFetcher *)fetcher {
 748
 749  // this may be invoked in a separate thread
 750  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 751
 752#if GDATA_LOG_PERFORMANCE
 753  NSTimeInterval secs1, secs2;
 754  secs1 = [NSDate timeIntervalSinceReferenceDate];
 755#endif
 756
 757  NSError *error = nil;
 758  GDataObject* object = nil;
 759
 760  // Generally protect the fetcher properties, since canceling a ticket would
 761  // release the fetcher properties dictionary
 762  NSMutableDictionary *properties = [[[fetcher properties] retain] autorelease];
 763
 764  // The callback thread is retaining the fetcher, so the fetcher shouldn't keep
 765  // retaining the callback thread
 766  NSThread *callbackThread = [properties valueForKey:kFetcherCallbackThreadKey];
 767  [[callbackThread retain] autorelease];
 768  [properties removeObjectForKey:kFetcherCallbackThreadKey];
 769
 770  GDataServiceTicketBase *ticket = [properties valueForKey:kFetcherTicketKey];
 771  [[ticket retain] autorelease];
 772
 773  NSDictionary *responseHeaders = [fetcher responseHeaders];
 774  [[responseHeaders retain] autorelease];
 775
 776  NSOperation *parseOperation = [ticket parseOperation];
 777
 778  Class objectClass = (Class)[properties objectForKey:kFetcherObjectClassKey];
 779
 780  NSData *data = [fetcher downloadedData];
 781  NSXMLDocument *xmlDocument = [[[NSXMLDocument alloc] initWithData:data
 782                                                            options:0
 783                                                              error:&error] autorelease];
 784  if ([parseOperation isCancelled]) return;
 785
 786  if (xmlDocument) {
 787
 788    NSXMLElement* root = [xmlDocument rootElement];
 789
 790    if (!objectClass) {
 791      objectClass = [GDataObject objectClassForXMLElement:root];
 792    }
 793
 794    // see if the top-level class for the XML is listed in the surrogates;
 795    // if so, instantiate the surrogate class instead
 796    NSDictionary *surrogates = [ticket surrogates];
 797    Class baseSurrogate = (Class)[surrogates objectForKey:objectClass];
 798    if (baseSurrogate) {
 799      objectClass = baseSurrogate;
 800    }
 801
 802    // use the actual service version indicated by the response headers
 803    NSString *serviceVersion = [responseHeaders objectForKey:@"Gdata-Version"];
 804
 805    // feeds may optionally be instantiated without unknown elements tracked
 806    //
 807    // we only ever want to fetch feeds and discard the unknown XML, never
 808    // entries
 809    BOOL shouldIgnoreUnknowns = ([ticket shouldFeedsIgnoreUnknowns]
 810                                 && [objectClass isSubclassOfClass:[GDataFeedBase class]]);
 811
 812    object = [[objectClass alloc] initWithXMLElement:root
 813                                              parent:nil
 814                                      serviceVersion:serviceVersion
 815                                          surrogates:surrogates
 816                                shouldIgnoreUnknowns:shouldIgnoreUnknowns];
 817
 818    // we're done parsing; the extension declarations won't be needed again
 819    [object clearExtensionDeclarationsCache];
 820
 821
 822#if GDATA_USES_LIBXML
 823    // retain the document so that pointers to internal nodes remain valid
 824    [object setProperty:xmlDocument forKey:kGDataXMLDocumentPropertyKey];
 825#endif
 826
 827    [properties setValue:object forKey:kFetcherParsedObjectKey];
 828    [object release];
 829
 830#if GDATA_LOG_PERFORMANCE
 831    secs2 = [NSDate timeIntervalSinceReferenceDate];
 832    NSLog(@"allocation of %@ took %f seconds", objectClass, secs2 - secs1);
 833#endif
 834  }
 835  [properties setValue:error forKey:kFetcherParseErrorKey];
 836
 837  if ([parseOperation isCancelled]) return;
 838
 839  SEL parseDoneSel = @selector(handleParsedObjectForFetcher:);
 840
 841  if (operationQueue_ != nil) {
 842    NSArray *runLoopModes = [properties valueForKey:kFetcherCallbackRunLoopModesKey];
 843    if (runLoopModes) {
 844      [self performSelector:parseDoneSel
 845                   onThread:callbackThread
 846                 withObject:fetcher
 847              waitUntilDone:NO
 848                      modes:runLoopModes];
 849    } else {
 850      // defaults to common modes
 851      [self performSelector:parseDoneSel
 852                   onThread:callbackThread
 853                 withObject:fetcher
 854              waitUntilDone:NO];
 855    }
 856    // the fetcher now belongs to the callback thread
 857  } else {
 858    // in 10.4, there's no performSelector:onThread:
 859    [self performSelector:parseDoneSel withObject:fetcher];
 860    [properties removeObjectForKey:kFetcherCallbackThreadKey];
 861  }
 862
 863  // We drain here to keep the clang static analyzer quiet.
 864  [pool drain];
 865}
 866
 867- (void)handleParsedObjectForFetcher:(GTMHTTPFetcher *)fetcher {
 868
 869  // after parsing is complete, this is invoked on the thread that the
 870  // fetch was performed on
 871
 872  GDataServiceTicketBase *ticket = [fetcher propertyForKey:kFetcherTicketKey];
 873  [ticket setParseOperation:nil];
 874
 875  // unpack the callback parameters
 876  id delegate = [fetcher propertyForKey:kFetcherDelegateKey];
 877  GDataObject *object = [fetcher propertyForKey:kFetcherParsedObjectKey];
 878  NSError *error = [fetcher propertyForKey:kFetcherParseErrorKey];
 879  SEL finishedSelector = NSSelectorFromString([fetcher propertyForKey:kFetcherFinishedSelectorKey]);
 880
 881  GDataServiceCompletionHandler completionHandler;
 882#if NS_BLOCKS_AVAILABLE
 883  completionHandler = [fetcher propertyForKey:kFetcherCompletionHandlerKey];
 884#else
 885  completionHandler = NULL;
 886#endif
 887
 888  NSNotificationCenter *defaultNC = [NSNotificationCenter defaultCenter];
 889  [defaultNC postNotificationName:kGDataServiceTicketParsingStoppedNotification
 890                           object:ticket];
 891
 892  NSData *data = [fetcher downloadedData];
 893  NSUInteger dataLength = [data length];
 894
 895  // if we created the object (or we got empty data back, as from a GData
 896  // delete resource request) then we succeeded
 897  if (object != nil || dataLength == 0) {
 898
 899    // if the user is fetching a feed and the ticket specifies that "next" links
 900    // should be followed, then do that now
 901    if ([ticket shouldFollowNextLinks]
 902        && [object isKindOfClass:[GDataFeedBase class]]) {
 903
 904      GDataFeedBase *latestFeed = (GDataFeedBase *)object;
 905
 906      // append the latest feed
 907      [ticket accumulateFeed:latestFeed];
 908
 909      NSURL *nextURL = [[latestFeed nextLink] URL];
 910      if (nextURL) {
 911
 912        BOOL isFetchingNextFeed = [self fetchNextFeedWithURL:nextURL
 913                                                    delegate:delegate
 914                                         didFinishedSelector:finishedSelector
 915                                           completionHandler:completionHandler
 916                                                      ticket:ticket];
 917
 918        // skip calling the callbacks since the ticket is still in progress
 919        if (isFetchingNextFeed) {
 920
 921          return;
 922
 923        } else {
 924          // the fetch didn't start; fall through to the callback for the
 925          // feed accumulated so far
 926        }
 927      }
 928
 929      // no more "next" links are present, so we don't need to accumulate more
 930      // entries
 931      GDataFeedBase *accumulatedFeed = [ticket accumulatedFeed];
 932      if (accumulatedFeed) {
 933
 934        // remove the misleading "next" link from the accumulated feed
 935        GDataLink *accumulatedFeedNextLink = [accumulatedFeed nextLink];
 936        if (accumulatedFeedNextLink) {
 937          [accumulatedFeed removeLink:accumulatedFeedNextLink];
 938        }
 939
 940#if DEBUG && !GDATA_SKIP_NEXTLINK_WARNING
 941        // each next link followed to accumulate all pages of a feed takes up to
 942        // a few seconds.  When multiple next links are being followed, that
 943        // usually indicates that a larger page size (that is, more entries per
 944        // feed fetched) should be requested.
 945        //
 946        // To avoid following next links, when fetching a feed, make it a query
 947        // fetch instead; when fetching a query, use setMaxResults: so the feed
 948        // requested is large enough to rarely need to follow next links.
 949        NSUInteger feedPageCount = [ticket nextLinksFollowedCounter];
 950        if (feedPageCount > 2) {
 951          NSLog(@"Fetching %@ required following %u \"next\" links; use a query with a larger setMaxResults: for faster feed accumulation",
 952                NSStringFromClass([accumulatedFeed class]),
 953                (unsigned int) [ticket nextLinksFollowedCounter]);
 954        }
 955#endif
 956        // return the completed feed as the object that was fetched
 957        object = accumulatedFeed;
 958      }
 959    }
 960
 961    if (finishedSelector) {
 962      [[self class] invokeCallback:finishedSelector
 963                            target:delegate
 964                            ticket:ticket
 965                            object:object
 966                             error:nil];
 967    }
 968
 969#if NS_BLOCKS_AVAILABLE
 970    if (completionHandler) {
 971      completionHandler(ticket, object, nil);
 972    }
 973#endif
 974
 975    [ticket setFetchedObject:object];
 976
 977  } else {
 978    if (error == nil) {
 979      error = [NSError errorWithDomain:kGDataServiceErrorDomain
 980                                  code:kGDataCouldNotConstructObjectError
 981                              userInfo:nil];
 982    }
 983    if (finishedSelector) {
 984      [[self class] invokeCallback:finishedSelector
 985                            target:delegate
 986                            ticket:ticket
 987                            object:nil
 988                             error:error];
 989    }
 990
 991#if NS_BLOCKS_AVAILABLE
 992    if (completionHandler) {
 993      completionHandler(ticket, nil, error);
 994    }
 995#endif
 996    [ticket setFetchError:error];
 997  }
 998
 999  [fetcher setProperties:nil];
1000
1001  [ticket setHasCalledCallback:YES];
1002  [ticket setCurrentFetcher:nil];
1003}
1004
1005- (void)objectFetcher:(GTMHTTPFetcher *)fetcher failedWithData:(NSData *)data error:(NSError *)error {
1006
1007#if DEBUG
1008  NSString *dataString = [[[NSString alloc] initWithData:data
1009                                            encoding:NSUTF8StringEncoding] autorelease];
1010  if (dataString) {
1011   NSLog(@"serviceBase:%@ objectFetcher:%@ failedWithStatus:%ld data:%@",
1012         self, fetcher, (long) [error code], dataString);
1013  }
1014#endif
1015
1016  id delegate = [fetcher propertyForKey:kFetcherDelegateKey];
1017
1018  GDataServiceTicketBase *ticket = [fetcher propertyForKey:kFetcherTicketKey];
1019
1020  NSString *finishedSelectorStr = [fetcher propertyForKey:kFetcherFinishedSelectorKey];
1021  SEL finishedSelector = finishedSelectorStr ? NSSelectorFromString(finishedSelectorStr) : NULL;
1022
1023  if (error != nil) {
1024    // create structured errors in the userInfo, if appropriate
1025    NSDictionary *responseHeaders = [fetcher responseHeaders];
1026    NSString *contentType = [responseHeaders objectForKey:@"Content-Type"];
1027
1028    NSDictionary *newUserInfo = [self userInfoForErrorResponseData:data
1029                                                    contentType:contentType
1030                                                  previousUserInfo:[error userInfo]];
1031    error = [NSError errorWithDomain:[error domain]
1032                                code:[error code]
1033                            userInfo:newUserInfo];
1034  }
1035
1036  if (finishedSelector) {
1037    [[self class] invokeCallback:finishedSelector
1038                          target:delegate
1039                          ticket:ticket
1040                          object:nil
1041                           error:error];
1042  }
1043
1044#if NS_BLOCKS_AVAILABLE
1045  GDataServiceCompletionHandler completionHandler;
1046
1047  completionHandler = [fetcher propertyForKey:kFetcherCompletionHandlerKey];
1048  if (completionHandler) {
1049    completionHandler(ticket, nil, error);
1050  }
1051#endif
1052
1053  [fetcher setProperties:nil];
1054
1055  [ticket setFetchError:error];
1056  [ticket setHasCalledCallback:YES];
1057  [ticket setCurrentFetcher:nil];
1058}
1059
1060#pragma mark -
1061
1062// create an error userInfo dictionary containing a useful reason string and,
1063// for structured XML errors, a server error group object
1064- (NSDictionary *)userInfoForErrorResponseData:(NSData *)data
1065                                   contentType:(NSString *)contentType
1066                              previousUserInfo:(NSDictionary *)previousUserInfo {
1067  // NSError's default localizedReason value looks like
1068  //   "(com.google.GDataServiceDomain error -4.)"
1069  //
1070  // The NSError domain and numeric code aren't the ones we care about
1071  // so much as the error present in the server response data, so
1072  // we'll try to store a more useful reason in the userInfo dictionary
1073
1074  NSString *reasonStr = nil;
1075  NSMutableDictionary *userInfo;
1076  if (previousUserInfo) {
1077    userInfo = [NSMutableDictionary dictionaryWithDictionary:previousUserInfo];
1078  } else {
1079    userInfo = [NSMutableDictionary dictionary];
1080  }
1081
1082  if ([data length] > 0) {
1083
1084    // check if the response is a structured XML error string, according to the
1085    // response content-type header; if so, convert the XML to a
1086    // GDataServerErrorGroup
1087    if ([[contentType lowercaseString] hasPrefix:kXMLErrorContentType]) {
1088
1089      GDataServerErrorGroup *errorGroup
1090        = [[[GDataServerErrorGroup alloc] initWithData:data] autorelease];
1091
1092      if (errorGroup != nil) {
1093
1094        // store the server group in the userInfo for the error
1095        [userInfo setObject:errorGroup forKey:kGDataStructuredErrorsKey];
1096
1097        reasonStr = [[errorGroup mainError] summary];
1098      }
1099    }
1100
1101    if ([userInfo count] == 0) {
1102
1103      // no structured XML error was available; deal with a plaintext server
1104      // error response
1105      reasonStr = [[[NSString alloc] initWithData:data
1106                                         encoding:NSUTF8StringEncoding] autorelease];
1107    }
1108  }
1109
1110  if (reasonStr != nil) {
1111    // we always store an error in the userInfo key "error"
1112    [userInfo setObject:reasonStr forKey:kGDataServerErrorStringKey];
1113
1114    // store a user-readable "reason" to show up when an error is logged,
1115    // in parentheses like NSError does it
1116    NSString *parenthesized = [NSString stringWithFormat:@"(%@)", reasonStr];
1117    [userInfo setObject:parenthesized forKey:NSLocalizedFailureReasonErrorKey];
1118  }
1119
1120  return userInfo;
1121}
1122
1123+ (void)invokeCallback:(SEL)callbackSel
1124                target:(id)target
1125                ticket:(id)ticket
1126                object:(id)object
1127                 error:(id)error {
1128
1129  // GData fetch callbacks have no return value
1130  NSMethodSignature *signature = [target methodSignatureForSelector:callbackSel];
1131  NSInvocation *retryInvocation = [NSInvocation invocationWithMethodSignature:signature];
1132  [retryInvocation setSelector:callbackSel];
1133  [retryInvocation setTarget:target];
1134  [retryInvocation setArgument:&ticket atIndex:2];
1135  [retryInvocation setArgument:&object atIndex:3];
1136  [retryInvocation setArgument:&error atIndex:4];
1137  [retryInvocation invoke];
1138}
1139
1140// The object fetcher may call into this retry method; this one invokes the
1141// selector provided by the user.
1142- (BOOL)objectFetcher:(GTMHTTPFetcher *)fetcher willRetry:(BOOL)willRetry forError:(NSError *)error {
1143
1144  id delegate = [fetcher propertyForKey:kFetcherDelegateKey];
1145  GDataServiceTicketBase *ticket = [fetcher propertyForKey:kFetcherTicketKey];
1146
1147  SEL retrySelector = [ticket retrySelector];
1148  if (retrySelector) {
1149
1150    willRetry = [self invokeRetrySelector:retrySelector
1151                                 delegate:delegate
1152                                   ticket:ticket
1153                                willRetry:willRetry
1154                                    error:error];
1155  }
1156  return willRetry;
1157}
1158
1159- (BOOL)invokeRetrySelector:(SEL)retrySelector
1160                   delegate:(id)delegate
1161                     ticket:(GDataServiceTicketBase *)ticket
1162                  willRetry:(BOOL)willRetry
1163                      error:(NSError *)error {
1164
1165  if ([delegate respondsToSelector:retrySelector]) {
1166
1167    // Unlike the retry selector invocation in GTMHTTPFetcher, this invocation
1168    // passes the ticket rather than the fetcher as argument 2
1169    NSMethodSignature *signature = [delegate methodSignatureForSelector:retrySelector];
1170    NSInvocation *retryInvocation = [NSInvocation invocationWithMethodSignature:signature];
1171    [retryInvocation setSelector:retrySelector];
1172    [retryInvocation setTarget:delegate];
1173    [retryInvocation setArgument:&ticket atIndex:2]; // ticket passed
1174    [retryInvocation setArgument:&willRetry atIndex:3];
1175    [retryInvocation setArgument:&error atIndex:4];
1176    [retryInvocation invoke];
1177
1178    [retryInvocation getReturnValue:&willRetry];
1179  }
1180  return willRetry;
1181}
1182
1183- (void)addAuthenticationToFetcher:(GTMHTTPFetcher *)fetcher {
1184  NSString *username = [self username];
1185  NSString *password = [self password];
1186
1187  if ([username length] > 0 && [password length] > 0) {
1188    // We're avoiding +[NSURCredential credentialWithUser:password:persistence:]
1189    // because it fails to autorelease itself on OS X 10.4 .. 10.5.x
1190    // rdar://5596278
1191
1192    NSURLCredential *cred;
1193    cred = [[[NSURLCredential alloc] initWithUser:username
1194                                         password:password
1195                                      persistence:NSURLCredentialPersistenceForSession] autorelease];
1196    [fetcher setCredential:cred];
1197  }
1198}
1199
1200// when a ticket is set to follow "next" links for feeds, this routine
1201// initiates the fetch for each additional feed
1202- (BOOL)fetchNextFeedWithURL:(NSURL *)nextFeedURL
1203                    delegate:(id)delegate
1204         didFinishedSelector:(SEL)finishedSelector
1205           completionHandler:(GDataServiceCompletionHandler)completionHandler
1206                      ticket:(GDataServiceTicketBase *)ticket {
1207
1208  // sanity check the number of pages fetched already
1209  NSUInteger followedCounter = [ticket nextLinksFollowedCounter];
1210
1211  if (followedCounter > kMaxNumberOfNextLinksFollowed) {
1212
1213    // the client should be querying with a higher max results per page
1214    // to avoid this
1215    GDATA_DEBUG_ASSERT(0, @"Following next links retrieves too many pages (URL %@)",
1216              nextFeedURL);
1217    return NO;
1218  }
1219
1220  [ticket setNextLinksFollowedCounter:(1 + followedCounter)];
1221
1222  // by definition, feed requests are GETs, so objectToPost: and httpMethod:
1223  // should be nil
1224  GDataServiceTicketBase *startedTicket;
1225  startedTicket = [self fetchObjectWithURL:nextFeedURL
1226                               objectClass:[[ticket accumulatedFeed] class]
1227                              objectToPost:nil
1228                                      ETag:nil
1229                                httpMethod:nil
1230                                  delegate:delegate
1231                         didFinishSelector:finishedSelector
1232                         completionHandler:completionHandler
1233                      retryInvocationValue:nil
1234                                    ticket:ticket];
1235
1236  // in the bizarre case that the fetch didn't begin, startedTicket will be
1237  // nil.  So long as the started ticket is the same as the ticket we're
1238  // continuing, then we're happy.
1239  return (ticket == startedTicket);
1240}
1241
1242
1243- (BOOL)waitForTicket:(GDataServiceTicketBase *)ticket
1244              timeout:(NSTimeInterval)timeoutInSeconds
1245        fetchedObject:(GDataObject **)outObjectOrNil
1246                error:(NSError **)outErrorOrNil {
1247
1248  NSDate* giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds];
1249
1250  // loop until the fetch completes with an object or an error,
1251  // or until the timeout has expired
1252  while (![ticket hasCalledCallback]
1253         && [giveUpDate timeIntervalSinceNow] > 0) {
1254
1255    // run the current run loop 1/1000 of a second to give the networking
1256    // code a chance to work
1257    NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:0.001];
1258    [[NSRunLoop currentRunLoop] runUntilDate:stopDate];
1259  }
1260
1261  NSError *fetchError = [ticket fetchError];
1262
1263  if (![ticket hasCalledCallback] && fetchError == nil) {
1264    fetchError = [NSError errorWithDomain:kGDataServiceErrorDomain
1265                                     code:kGDataWaitTimedOutError
1266                                 userInfo:nil];
1267  }
1268
1269  if (outObjectOrNil) *outObjectOrNil = [ticket fetchedObject];
1270  if (outErrorOrNil)  *outErrorOrNil = fetchError;
1271
1272  return (fetchError == nil);
1273}
1274
1275#pragma mark -
1276
1277// These external entry points all call into fetchObjectWithURL: defined above
1278
1279- (GDataServiceTicketBase *)fetchPublicFeedWithURL:(NSURL *)feedURL
1280                                         feedClass:(Class)feedClass
1281                                          delegate:(id)delegate
1282                                 didFinishSelector:(SEL)finishedSelector {
1283
1284  return [self fetchObjectWithURL:feedURL
1285                      objectClass:feedClass
1286                     objectToPost:nil
1287                             ETag:nil
1288                       httpMethod:nil
1289                         delegate:delegate
1290                didFinishSelector:finishedSelector
1291                completionHandler:nil
1292             retryInvocationValue:nil
1293                           ticket:nil];
1294}
1295
1296- (GDataServiceTicketBase *)fetchPublicEntryWithURL:(NSURL *)entryURL
1297                                         entryClass:(Class)entryClass
1298                                           delegate:(id)delegate
1299                                  didFinishSelector:(SEL)finishedSelector {
1300
1301  return [self fetchObjectWithURL:entryURL
1302                      objectClass:entryClass
1303                     objectToPost:nil
1304                             ETag:nil
1305                       httpMethod:nil
1306                         delegate:delegate
1307                didFinishSelector:finish

Large files files are truncated, but you can click here to view the full file