PageRenderTime 64ms CodeModel.GetById 19ms RepoModel.GetById 0ms 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
Possible License(s): Apache-2.0, BSD-3-Clause, GPL-2.0
  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. // GDataServiceBase.m
  17. //
  18. #import <TargetConditionals.h>
  19. #if TARGET_OS_MAC
  20. #include <sys/utsname.h>
  21. #endif
  22. #if TARGET_OS_IPHONE
  23. #import <UIKit/UIKit.h>
  24. #endif
  25. #define GDATASERVICEBASE_DEFINE_GLOBALS 1
  26. #import "GDataServiceBase.h"
  27. #import "GDataServerError.h"
  28. #import "GDataFramework.h"
  29. static NSString *const kXMLErrorContentType = @"application/vnd.google.gdata.error+xml";
  30. static NSString* const kFetcherDelegateKey = @"_delegate";
  31. static NSString* const kFetcherObjectClassKey = @"_objectClass";
  32. static NSString* const kFetcherFinishedSelectorKey = @"_finishedSelector";
  33. static NSString* const kFetcherCompletionHandlerKey = @"_completionHandler";
  34. static NSString* const kFetcherTicketKey = @"_ticket";
  35. static NSString* const kFetcherStreamDataKey = @"_streamData";
  36. static NSString* const kFetcherParsedObjectKey = @"_parsedObject";
  37. static NSString* const kFetcherParseErrorKey = @"_parseError";
  38. static NSString* const kFetcherCallbackThreadKey = @"_callbackThread";
  39. static NSString* const kFetcherCallbackRunLoopModesKey = @"_runLoopModes";
  40. NSString* const kFetcherRetryInvocationKey = @"_retryInvocation";
  41. static const NSUInteger kMaxNumberOfNextLinksFollowed = 25;
  42. // we'll enforce 50K chunks minimum just to avoid the server getting hit
  43. // with too many small upload chunks
  44. static const NSUInteger kMinimumUploadChunkSize = 50000;
  45. // XorPlainMutableData is a simple way to keep passwords held in heap objects
  46. // from being visible as plain-text
  47. static void XorPlainMutableData(NSMutableData *mutableData) {
  48. // this helps avoid storing passwords on the heap in plaintext
  49. const unsigned char theXORValue = 0x95; // 0x95 = 0xb10010101
  50. unsigned char *dataPtr = [mutableData mutableBytes];
  51. NSUInteger length = [mutableData length];
  52. for (NSUInteger idx = 0; idx < length; idx++) {
  53. dataPtr[idx] ^= theXORValue;
  54. }
  55. }
  56. // category to provide opaque access to tickets stored in fetcher properties
  57. @implementation GTMHTTPFetcher (GDataServiceTicketAdditions)
  58. - (id)GDataTicket {
  59. return [self propertyForKey:kFetcherTicketKey];
  60. }
  61. @end
  62. // If GTMHTTPUploadFetcher is available, it can be used for chunked uploads
  63. //
  64. // We locally declare some methods of GTMHTTPUploadFetcher so we
  65. // do not need to import the header, as some projects may not have it available
  66. @interface GTMHTTPUploadFetcher : GTMHTTPFetcher
  67. + (GTMHTTPUploadFetcher *)uploadFetcherWithRequest:(NSURLRequest *)request
  68. uploadData:(NSData *)data
  69. uploadMIMEType:(NSString *)uploadMIMEType
  70. chunkSize:(NSUInteger)chunkSize
  71. fetcherService:(GTMHTTPFetcherService *)fetcherService;
  72. + (GTMHTTPUploadFetcher *)uploadFetcherWithRequest:(NSURLRequest *)request
  73. uploadFileHandle:(NSFileHandle *)uploadFileHandle
  74. uploadMIMEType:(NSString *)uploadMIMEType
  75. chunkSize:(NSUInteger)chunkSize
  76. fetcherService:(GTMHTTPFetcherService *)fetcherService;
  77. + (GTMHTTPUploadFetcher *)uploadFetcherWithLocation:(NSURL *)locationURL
  78. uploadFileHandle:(NSFileHandle *)uploadFileHandle
  79. uploadMIMEType:(NSString *)uploadMIMEType
  80. chunkSize:(NSUInteger)chunkSize
  81. fetcherService:(GTMHTTPFetcherService *)fetcherService;
  82. - (void)pauseFetching;
  83. - (void)resumeFetching;
  84. - (BOOL)isPaused;
  85. @end
  86. @interface GDataEntryBase (PrivateMethods)
  87. - (NSDictionary *)contentHeaders;
  88. @end
  89. @interface GDataServiceBase (PrivateMethods)
  90. - (BOOL)fetchNextFeedWithURL:(NSURL *)nextFeedURL
  91. delegate:(id)delegate
  92. didFinishedSelector:(SEL)finishedSelector
  93. completionHandler:(GDataServiceCompletionHandler)completionHandler
  94. ticket:(GDataServiceTicketBase *)ticket;
  95. - (NSDictionary *)userInfoForErrorResponseData:(NSData *)data
  96. contentType:(NSString *)contentType
  97. previousUserInfo:(NSDictionary *)previousUserInfo;
  98. - (void)objectFetcher:(GTMHTTPFetcher *)fetcher
  99. finishedWithData:(NSData *)data
  100. error:(NSError *)error;
  101. - (void)objectFetcher:(GTMHTTPFetcher *)fetcher
  102. failedWithData:(NSData *)data
  103. error:(NSError *)error;
  104. - (BOOL)objectFetcher:(GTMHTTPFetcher *)fetcher
  105. willRetry:(BOOL)willRetry
  106. forError:(NSError *)error;
  107. - (void)objectFetcher:(GTMHTTPFetcher *)fetcher
  108. didSendBytes:(NSInteger)bytesSent
  109. totalBytesSent:(NSInteger)totalBytesSent
  110. totalBytesExpectedToSend:(NSInteger)totalBytesExpected;
  111. - (void)parseObjectFromDataOfFetcher:(GTMHTTPFetcher *)fetcher;
  112. - (void)handleParsedObjectForFetcher:(GTMHTTPFetcher *)fetcher;
  113. @end
  114. @implementation GDataServiceBase
  115. + (Class)ticketClass {
  116. return [GDataServiceTicketBase class];
  117. }
  118. - (id)init {
  119. self = [super init];
  120. if (self) {
  121. #if GDATA_IPHONE || (MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_5)
  122. operationQueue_ = [[NSOperationQueue alloc] init];
  123. #elif !GDATA_SKIP_PARSE_THREADING
  124. // Avoid NSOperationQueue prior to 10.5.6, per
  125. // http://www.mikeash.com/?page=pyblog/use-nsoperationqueue.html
  126. SInt32 bcdSystemVersion = 0;
  127. (void) Gestalt(gestaltSystemVersion, &bcdSystemVersion);
  128. if (bcdSystemVersion >= 0x1057) {
  129. operationQueue_ = [[NSOperationQueue alloc] init];
  130. }
  131. #else
  132. // operationQueue_ defaults to nil, so parsing will be done immediately
  133. // on the current thread
  134. #endif
  135. fetcherService_ = [[GTMHTTPFetcherService alloc] init];
  136. [fetcherService_ setShouldRememberETags:YES];
  137. cookieStorageMethod_ = -1;
  138. NSUInteger chunkSize = [[self class] defaultServiceUploadChunkSize];
  139. [self setServiceUploadChunkSize:chunkSize];
  140. }
  141. return self;
  142. }
  143. - (void)dealloc {
  144. [operationQueue_ release];
  145. [serviceVersion_ release];
  146. [userAgent_ release];
  147. [fetcherService_ release];
  148. [username_ release];
  149. [password_ release];
  150. [serviceUserData_ release];
  151. [serviceProperties_ release];
  152. [serviceSurrogates_ release];
  153. #if NS_BLOCKS_AVAILABLE
  154. [serviceUploadProgressBlock_ release];
  155. #endif
  156. [super dealloc];
  157. }
  158. + (NSString *)systemVersionString {
  159. NSString *str = GTMSystemVersionString();
  160. return str;
  161. }
  162. - (NSString *)requestUserAgent {
  163. NSString *userAgent = [self userAgent];
  164. if ([userAgent length] == 0 || [userAgent hasPrefix:@"MyCompany-"]) {
  165. // the service instance is missing an explicit user-agent; use the bundle ID
  166. // or process name
  167. userAgent = [[self class] defaultApplicationIdentifier];
  168. }
  169. NSString *requestUserAgent = userAgent;
  170. // if the user agent already specifies the library version, we'll
  171. // use it verbatim in the request
  172. NSString *libraryString = @"GData-ObjectiveC";
  173. NSRange libRange = [userAgent rangeOfString:libraryString
  174. options:NSCaseInsensitiveSearch];
  175. if (libRange.location == NSNotFound) {
  176. // the user agent doesn't specify the client library, so append that
  177. // information, and the system version
  178. NSString *libVersionString = GDataFrameworkVersionString();
  179. NSString *systemString = [[self class] systemVersionString];
  180. // Google servers look for gzip in the user agent before sending gzip-
  181. // encoded responses. See Service.java
  182. requestUserAgent = [NSString stringWithFormat:@"%@ %@/%@ %@ (gzip)",
  183. userAgent, libraryString, libVersionString, systemString];
  184. }
  185. return requestUserAgent;
  186. }
  187. - (NSMutableURLRequest *)requestForURL:(NSURL *)url
  188. ETag:(NSString *)etag
  189. httpMethod:(NSString *)httpMethod
  190. ticket:(GDataServiceTicketBase *)ticket {
  191. // subclasses may add headers to this
  192. NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] initWithURL:url
  193. cachePolicy:NSURLRequestReloadIgnoringCacheData
  194. timeoutInterval:60] autorelease];
  195. NSString *requestUserAgent = [self requestUserAgent];
  196. [request setValue:requestUserAgent forHTTPHeaderField:@"User-Agent"];
  197. NSString *serviceVersion = [self serviceVersion];
  198. if ([serviceVersion length] > 0) {
  199. // only add a version header if the URL lacks a v= version parameter
  200. NSString *queryString = [url query];
  201. if (queryString == nil
  202. || ([queryString rangeOfString:@"&v="].location == NSNotFound
  203. && ![queryString hasPrefix:@"v="])) {
  204. [request setValue:serviceVersion forHTTPHeaderField:@"GData-Version"];
  205. }
  206. }
  207. if ([httpMethod length] > 0) {
  208. [request setHTTPMethod:httpMethod];
  209. }
  210. if ([etag length] > 0) {
  211. // it's rather unexpected for an etagged object to be provided for a GET,
  212. // but we'll check for an etag anyway, similar to HttpGDataRequest.java,
  213. // and if present use it to request only an unchanged resource
  214. BOOL isDoingHTTPGet = (httpMethod == nil
  215. || [httpMethod caseInsensitiveCompare:@"GET"] == NSOrderedSame);
  216. if (isDoingHTTPGet) {
  217. // set the etag header, even if weak, indicating we don't want
  218. // another copy of the resource if it's the same as the object
  219. [request setValue:etag forHTTPHeaderField:@"If-None-Match"];
  220. } else {
  221. // if we're doing PUT or DELETE, set the etag header indicating
  222. // we only want to update the resource if our copy matches the current
  223. // one (unless the etag is weak and so shouldn't be a constraint at all)
  224. BOOL isWeakETag = [etag hasPrefix:@"W/"];
  225. BOOL isModifying =
  226. [httpMethod caseInsensitiveCompare:@"PUT"] == NSOrderedSame
  227. || [httpMethod caseInsensitiveCompare:@"DELETE"] == NSOrderedSame
  228. || [httpMethod caseInsensitiveCompare:@"PATCH"] == NSOrderedSame;
  229. if (isModifying && !isWeakETag) {
  230. [request setValue:etag forHTTPHeaderField:@"If-Match"];
  231. }
  232. }
  233. }
  234. return request;
  235. }
  236. - (NSMutableURLRequest *)requestForURL:(NSURL *)url
  237. ETag:(NSString *)etag
  238. httpMethod:(NSString *)httpMethod {
  239. // this public entry point authenticates from the service object but
  240. // not from the auth token in the ticket
  241. return [self requestForURL:url ETag:etag httpMethod:httpMethod ticket:nil];
  242. }
  243. // objectRequestForURL returns an NSMutableURLRequest for a GData object as XML
  244. //
  245. // the object is the object being sent to the server, or nil;
  246. // the http method may be nil for get, or POST, PUT, DELETE
  247. - (NSMutableURLRequest *)objectRequestForURL:(NSURL *)url
  248. object:(GDataObject *)object
  249. ETag:(NSString *)etag
  250. httpMethod:(NSString *)httpMethod
  251. ticket:(GDataServiceTicketBase *)ticket {
  252. NSString *contentType = @"application/atom+xml; charset=utf-8";
  253. if (object) {
  254. // if the object being sent has an etag, add it to the request header to
  255. // avoid retrieving a duplicate or to avoid writing over an updated
  256. // version of the resource on the server
  257. //
  258. // Typically, delete requests will provide an explicit ETag parameter, and
  259. // other requests will have the ETag carried inside the object being updated
  260. if (etag == nil) {
  261. SEL selEtag = @selector(ETag);
  262. if ([object respondsToSelector:selEtag]) {
  263. etag = [object performSelector:selEtag];
  264. }
  265. }
  266. if (httpMethod != nil
  267. && [httpMethod caseInsensitiveCompare:@"PATCH"] == NSOrderedSame) {
  268. // PATCH isn't part of Atom
  269. contentType = @"application/xml; charset=utf-8";
  270. }
  271. }
  272. NSMutableURLRequest *request = [self requestForURL:url
  273. ETag:etag
  274. httpMethod:httpMethod
  275. ticket:ticket];
  276. [request setValue:@"application/atom+xml, text/xml" forHTTPHeaderField:@"Accept"];
  277. [request setValue:contentType forHTTPHeaderField:@"Content-Type"];
  278. [request setValue:@"no-cache" forHTTPHeaderField:@"Cache-Control"];
  279. return request;
  280. }
  281. #pragma mark -
  282. - (GDataServiceTicketBase *)fetchObjectWithURL:(NSURL *)feedURL
  283. objectClass:(Class)objectClass
  284. objectToPost:(GDataObject *)objectToPost
  285. ETag:(NSString *)etag
  286. httpMethod:(NSString *)httpMethod
  287. delegate:(id)delegate
  288. didFinishSelector:(SEL)finishedSelector
  289. completionHandler:(id)completionHandler // GDataServiceCompletionHandler
  290. retryInvocationValue:(NSValue *)retryInvocationValue
  291. ticket:(GDataServiceTicketBase *)ticket {
  292. GTMAssertSelectorNilOrImplementedWithArgs(delegate, finishedSelector, @encode(GDataServiceTicketBase *), @encode(GDataObject *), @encode(NSError *), 0);
  293. // The completionHandler argument is declared as an id, not as a block
  294. // pointer, so this can be built with the 10.6 SDK and still run on 10.5.
  295. // If the argument were declared as a block pointer, the invocation for
  296. // fetchObjectWithURL: created in GDataServiceGoogle would cause an exception
  297. // since 10.5's NSInvocation cannot deal with encoding of block pointers.
  298. NSURL *uploadLocationURL = [objectToPost uploadLocationURL];
  299. // if no URL was supplied, and we're not resuming an upload, treat this as
  300. // if the fetch failed (below) and immediately return a nil ticket, skipping
  301. // the callbacks
  302. //
  303. // this might be considered normal (say, updating a read-only entry
  304. // that lacks an edit link) though higher-level calls may assert or
  305. // returns errors depending on the specific usage
  306. if (feedURL == nil && uploadLocationURL == nil) {
  307. return nil;
  308. }
  309. // we need to create a ticket unless one was created earlier (like during
  310. // authentication)
  311. if (!ticket) {
  312. ticket = [[[self class] ticketClass] ticketForService:self];
  313. }
  314. NSMutableURLRequest *request = nil;
  315. if (feedURL) {
  316. request = [self objectRequestForURL:feedURL
  317. object:objectToPost
  318. ETag:etag
  319. httpMethod:httpMethod
  320. ticket:ticket];
  321. }
  322. GTMAssertSelectorNilOrImplementedWithArgs(delegate, [ticket uploadProgressSelector],
  323. @encode(GDataServiceTicketBase *), @encode(unsigned long long),
  324. @encode(unsigned long long), 0);
  325. GTMAssertSelectorNilOrImplementedWithArgs(delegate, [ticket retrySelector],
  326. @encode(GDataServiceTicketBase *), @encode(BOOL), @encode(NSError *), 0);
  327. //
  328. // package the object's XML and any upload data
  329. //
  330. NSInputStream *streamToPost = nil;
  331. NSData *dataToPost = nil;
  332. SEL sentDataSel = NULL;
  333. NSData *uploadData = nil;
  334. NSFileHandle *uploadFileHandle = nil;
  335. BOOL shouldUploadDataChunked = ([self serviceUploadChunkSize] > 0);
  336. BOOL isUploadingDataChunked = NO;
  337. NSMutableDictionary *uploadProperties = nil;
  338. if (objectToPost) {
  339. NSData *xmlData = nil;
  340. [ticket setPostedObject:objectToPost];
  341. // An upload object may provide a custom input stream, such as for
  342. // multi-part MIME or media uploads.
  343. NSInputStream *contentInputStream = nil;
  344. unsigned long long contentLength = 0;
  345. NSDictionary *contentHeaders = nil;
  346. uploadProperties = [NSMutableDictionary dictionary];
  347. BOOL doesSupportSentData = [GTMHTTPFetcher doesSupportSentDataCallback];
  348. uploadData = [objectToPost uploadData];
  349. uploadFileHandle = [objectToPost uploadFileHandle];
  350. isUploadingDataChunked = ((uploadData != nil || uploadFileHandle != nil)
  351. && shouldUploadDataChunked);
  352. BOOL shouldUploadDataOnly = ([objectToPost shouldUploadDataOnly]
  353. || uploadLocationURL != nil);
  354. BOOL shouldReportUploadProgress;
  355. #if NS_BLOCKS_AVAILABLE
  356. shouldReportUploadProgress = ([ticket uploadProgressSelector] != NULL
  357. || [ticket uploadProgressHandler] != NULL);
  358. #else
  359. shouldReportUploadProgress = ([ticket uploadProgressSelector] != NULL);
  360. #endif
  361. if ((!isUploadingDataChunked) &&
  362. [objectToPost generateContentInputStream:&contentInputStream
  363. length:&contentLength
  364. headers:&contentHeaders]) {
  365. // now we have a stream containing the XML and the upload data
  366. } else if (isUploadingDataChunked && shouldUploadDataOnly) {
  367. // no XML is needed since we're uploading data only, and there's no upload
  368. // data for the first fetch because the data will be sent chunked later,
  369. // so now we'll have an empty body with appropriate headers
  370. contentHeaders = [objectToPost performSelector:@selector(contentHeaders)];
  371. contentLength = 0;
  372. } else {
  373. // we're sending either just XML, or XML now with chunked upload data
  374. // later
  375. xmlData = [[objectToPost XMLDocument] XMLData];
  376. contentLength = [xmlData length];
  377. if (!shouldReportUploadProgress
  378. || doesSupportSentData
  379. || isUploadingDataChunked) {
  380. // there is no progress selector, or the fetcher can call us back on
  381. // sent data, or we're uploading chunked; we can post plain NSData,
  382. // which is simpler because it survives http redirects
  383. dataToPost = xmlData;
  384. } else {
  385. // there is a progress selector and NSURLConnection won't call us back,
  386. // so we need to be posting a stream
  387. //
  388. // we'll make a default input stream from the XML data
  389. contentInputStream = [NSInputStream inputStreamWithData:xmlData];
  390. // NSInputStream fails to retain or copy its data in 10.4, so we will
  391. // retain it in the callback dictionary. We won't use this property in
  392. // the callbacks at all, but retaining it will ensure it's still around
  393. // until the upload completes.
  394. //
  395. // If it weren't for this bug in NSInputStream, we could just have
  396. // GDataObject's -contentInputStream method create the input stream for
  397. // us, so this service class wouldn't ever need to have the plain XML.
  398. [uploadProperties setObject:xmlData forKey:kFetcherStreamDataKey];
  399. }
  400. if ([objectToPost respondsToSelector:@selector(uploadSlug)]) {
  401. NSString *slug = [objectToPost performSelector:@selector(uploadSlug)];
  402. if ([slug length] > 0) {
  403. [request setValue:slug forHTTPHeaderField:@"Slug"];
  404. }
  405. }
  406. }
  407. if (contentHeaders) {
  408. // add the content-specific headers, if any
  409. for (NSString *key in contentHeaders) {
  410. NSString *value = [contentHeaders objectForKey:key];
  411. [request setValue:value forHTTPHeaderField:key];
  412. }
  413. }
  414. streamToPost = contentInputStream;
  415. NSNumber* num = [NSNumber numberWithUnsignedLongLong:contentLength];
  416. [request setValue:[num stringValue] forHTTPHeaderField:@"Content-Length"];
  417. if (shouldReportUploadProgress) {
  418. if (doesSupportSentData || isUploadingDataChunked) {
  419. // there is sentData callback support in NSURLConnection,
  420. // or we're using an upload fetcher which can always call us
  421. // back
  422. sentDataSel = @selector(objectFetcher:didSendBytes:totalBytesSent:totalBytesExpectedToSend:);
  423. }
  424. }
  425. }
  426. //
  427. // now that we have all the request header info ready,
  428. // create and set up the fetcher for this request
  429. //
  430. GTMHTTPFetcher* fetcher = nil;
  431. if (isUploadingDataChunked) {
  432. // hang on to the user's requested chunk size, and ensure it's not tiny
  433. NSUInteger uploadChunkSize = [self serviceUploadChunkSize];
  434. if (uploadChunkSize < kMinimumUploadChunkSize) {
  435. uploadChunkSize = kMinimumUploadChunkSize;
  436. }
  437. #ifdef GDATA_TARGET_NAMESPACE
  438. // prepend the class name prefix
  439. Class uploadClass = NSClassFromString(@GDATA_TARGET_NAMESPACE_STRING
  440. "_GTMHTTPUploadFetcher");
  441. #else
  442. Class uploadClass = NSClassFromString(@"GTMHTTPUploadFetcher");
  443. #endif
  444. GDATA_ASSERT(uploadClass != nil, @"GTMHTTPUploadFetcher needed");
  445. NSString *uploadMIMEType = [objectToPost uploadMIMEType];
  446. if (uploadData) {
  447. fetcher = [uploadClass uploadFetcherWithRequest:request
  448. uploadData:uploadData
  449. uploadMIMEType:uploadMIMEType
  450. chunkSize:uploadChunkSize
  451. fetcherService:fetcherService_];
  452. } else if (uploadLocationURL) {
  453. fetcher = [uploadClass uploadFetcherWithLocation:uploadLocationURL
  454. uploadFileHandle:uploadFileHandle
  455. uploadMIMEType:uploadMIMEType
  456. chunkSize:uploadChunkSize
  457. fetcherService:fetcherService_];
  458. } else {
  459. fetcher = [uploadClass uploadFetcherWithRequest:request
  460. uploadFileHandle:uploadFileHandle
  461. uploadMIMEType:uploadMIMEType
  462. chunkSize:uploadChunkSize
  463. fetcherService:fetcherService_];
  464. }
  465. } else {
  466. fetcher = [fetcherService_ fetcherWithRequest:request];
  467. }
  468. // allow the user to specify static app-wide cookies for fetching
  469. NSInteger cookieStorageMethod = [self cookieStorageMethod];
  470. if (cookieStorageMethod >= 0) {
  471. [fetcher setCookieStorageMethod:cookieStorageMethod];
  472. }
  473. // copy the ticket's retry settings into the fetcher
  474. [fetcher setRetryEnabled:[ticket isRetryEnabled]];
  475. [fetcher setMaxRetryInterval:[ticket maxRetryInterval]];
  476. if ([ticket retrySelector]) {
  477. [fetcher setRetrySelector:@selector(objectFetcher:willRetry:forError:)];
  478. }
  479. // remember the object fetcher in the ticket
  480. [ticket setObjectFetcher:fetcher];
  481. [ticket setCurrentFetcher:fetcher];
  482. // add parameters used by the callbacks
  483. [fetcher setProperty:objectClass forKey:kFetcherObjectClassKey];
  484. [fetcher setProperty:delegate forKey:kFetcherDelegateKey];
  485. [fetcher setProperty:NSStringFromSelector(finishedSelector)
  486. forKey:kFetcherFinishedSelectorKey];
  487. [fetcher setProperty:ticket
  488. forKey:kFetcherTicketKey];
  489. #if NS_BLOCKS_AVAILABLE
  490. // copy the completion handler block to the heap; this does nothing if the
  491. // block is already on the heap
  492. completionHandler = [[completionHandler copy] autorelease];
  493. [fetcher setProperty:completionHandler
  494. forKey:kFetcherCompletionHandlerKey];
  495. #endif
  496. // we want to add the invocation itself, not the value wrapper of it,
  497. // to ensure the invocation is retained until the callback completes
  498. NSInvocation *retryInvocation = [retryInvocationValue nonretainedObjectValue];
  499. [fetcher setProperty:retryInvocation
  500. forKey:kFetcherRetryInvocationKey];
  501. // set the upload data
  502. GDATA_DEBUG_ASSERT(dataToPost == nil || streamToPost == nil,
  503. @"upload conflict");
  504. [fetcher setPostData:dataToPost];
  505. [fetcher setPostStream:streamToPost];
  506. [fetcher setSentDataSelector:sentDataSel];
  507. [fetcher addPropertiesFromDictionary:uploadProperties];
  508. // attach OAuth authorization object, if any
  509. //
  510. // the fetcher already has this authorizer from the fetcher service, but this
  511. // lets the client remove the authorizer from the ticket to make an
  512. // unauthorized request
  513. [fetcher setAuthorizer:[ticket authorizer]];
  514. // add username/password, if any
  515. [self addAuthenticationToFetcher:fetcher];
  516. if (finishedSelector) {
  517. [fetcher setComment:NSStringFromSelector(finishedSelector)];
  518. }
  519. // failed fetches call the failure selector, which will delete the ticket
  520. BOOL didFetch = [fetcher beginFetchWithDelegate:self
  521. didFinishSelector:@selector(objectFetcher:finishedWithData:error:)];
  522. // If something weird happens and the networking callbacks have been called
  523. // already synchronously, we don't want to return the ticket since the caller
  524. // will never know when to stop retaining it, so we'll make sure the
  525. // success/failure callbacks have not yet been called by checking the
  526. // ticket
  527. if (!didFetch || [ticket hasCalledCallback]) {
  528. [fetcher setProperties:nil];
  529. [ticket setCurrentFetcher:nil];
  530. return nil;
  531. }
  532. return ticket;
  533. }
  534. - (void)invokeProgressCallbackForTicket:(GDataServiceTicketBase *)ticket
  535. deliveredBytes:(unsigned long long)numReadSoFar
  536. totalBytes:(unsigned long long)total {
  537. SEL progressSelector = [ticket uploadProgressSelector];
  538. if (progressSelector) {
  539. GTMHTTPFetcher *fetcher = [ticket objectFetcher];
  540. id delegate = [fetcher propertyForKey:kFetcherDelegateKey];
  541. NSMethodSignature *signature = [delegate methodSignatureForSelector:progressSelector];
  542. NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
  543. [invocation setSelector:progressSelector];
  544. [invocation setTarget:delegate];
  545. [invocation setArgument:&ticket atIndex:2];
  546. [invocation setArgument:&numReadSoFar atIndex:3];
  547. [invocation setArgument:&total atIndex:4];
  548. [invocation invoke];
  549. }
  550. #if NS_BLOCKS_AVAILABLE
  551. GDataServiceUploadProgressHandler block = [ticket uploadProgressHandler];
  552. if (block) {
  553. block(ticket, numReadSoFar, total);
  554. }
  555. #endif
  556. }
  557. // sentData callback from fetcher
  558. - (void)objectFetcher:(GTMHTTPFetcher *)fetcher
  559. didSendBytes:(NSInteger)bytesSent
  560. totalBytesSent:(NSInteger)totalBytesSent
  561. totalBytesExpectedToSend:(NSInteger)totalBytesExpected {
  562. GDataServiceTicketBase *ticket = [fetcher propertyForKey:kFetcherTicketKey];
  563. [self invokeProgressCallbackForTicket:ticket
  564. deliveredBytes:(unsigned long long)totalBytesSent
  565. totalBytes:(unsigned long long)totalBytesExpected];
  566. }
  567. - (void)objectFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data error:(NSError *)error {
  568. if (error) {
  569. [self objectFetcher:fetcher failedWithData:data error:error];
  570. return;
  571. }
  572. // we now have the XML data for a feed or entry
  573. // save the current thread into the fetcher, since we'll handle additional
  574. // fetches and callbacks on this thread
  575. [fetcher setProperty:[NSThread currentThread]
  576. forKey:kFetcherCallbackThreadKey];
  577. // copy the run loop modes, if any, so we don't need to access them
  578. // from the parsing thread
  579. [fetcher setProperty:[[[self runLoopModes] copy] autorelease]
  580. forKey:kFetcherCallbackRunLoopModesKey];
  581. // we post parsing notifications now to ensure they're on caller's
  582. // original thread
  583. GDataServiceTicketBase *ticket = [fetcher propertyForKey:kFetcherTicketKey];
  584. NSNotificationCenter *defaultNC = [NSNotificationCenter defaultCenter];
  585. [defaultNC postNotificationName:kGDataServiceTicketParsingStartedNotification
  586. object:ticket];
  587. // if there's an operation queue, then use that to schedule parsing on another
  588. // thread
  589. SEL parseSel = @selector(parseObjectFromDataOfFetcher:);
  590. if (operationQueue_ != nil) {
  591. NSInvocationOperation *op;
  592. op = [[[NSInvocationOperation alloc] initWithTarget:self
  593. selector:parseSel
  594. object:fetcher] autorelease];
  595. [ticket setParseOperation:op];
  596. [operationQueue_ addOperation:op];
  597. // the fetcher now belongs to the parsing thread
  598. } else {
  599. // parse on the current thread, on Mac OS X 10.4 through 10.5.7
  600. // or when the project defines GDATA_SKIP_PARSE_THREADING
  601. [self performSelector:parseSel
  602. withObject:fetcher];
  603. }
  604. }
  605. - (void)parseObjectFromDataOfFetcher:(GTMHTTPFetcher *)fetcher {
  606. // this may be invoked in a separate thread
  607. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  608. #if GDATA_LOG_PERFORMANCE
  609. NSTimeInterval secs1, secs2;
  610. secs1 = [NSDate timeIntervalSinceReferenceDate];
  611. #endif
  612. NSError *error = nil;
  613. GDataObject* object = nil;
  614. // Generally protect the fetcher properties, since canceling a ticket would
  615. // release the fetcher properties dictionary
  616. NSMutableDictionary *properties = [[[fetcher properties] retain] autorelease];
  617. // The callback thread is retaining the fetcher, so the fetcher shouldn't keep
  618. // retaining the callback thread
  619. NSThread *callbackThread = [properties valueForKey:kFetcherCallbackThreadKey];
  620. [[callbackThread retain] autorelease];
  621. [properties removeObjectForKey:kFetcherCallbackThreadKey];
  622. GDataServiceTicketBase *ticket = [properties valueForKey:kFetcherTicketKey];
  623. [[ticket retain] autorelease];
  624. NSDictionary *responseHeaders = [fetcher responseHeaders];
  625. [[responseHeaders retain] autorelease];
  626. NSOperation *parseOperation = [ticket parseOperation];
  627. Class objectClass = (Class)[properties objectForKey:kFetcherObjectClassKey];
  628. NSData *data = [fetcher downloadedData];
  629. NSXMLDocument *xmlDocument = [[[NSXMLDocument alloc] initWithData:data
  630. options:0
  631. error:&error] autorelease];
  632. if ([parseOperation isCancelled]) return;
  633. if (xmlDocument) {
  634. NSXMLElement* root = [xmlDocument rootElement];
  635. if (!objectClass) {
  636. objectClass = [GDataObject objectClassForXMLElement:root];
  637. }
  638. // see if the top-level class for the XML is listed in the surrogates;
  639. // if so, instantiate the surrogate class instead
  640. NSDictionary *surrogates = [ticket surrogates];
  641. Class baseSurrogate = (Class)[surrogates objectForKey:objectClass];
  642. if (baseSurrogate) {
  643. objectClass = baseSurrogate;
  644. }
  645. // use the actual service version indicated by the response headers
  646. NSString *serviceVersion = [responseHeaders objectForKey:@"Gdata-Version"];
  647. // feeds may optionally be instantiated without unknown elements tracked
  648. //
  649. // we only ever want to fetch feeds and discard the unknown XML, never
  650. // entries
  651. BOOL shouldIgnoreUnknowns = ([ticket shouldFeedsIgnoreUnknowns]
  652. && [objectClass isSubclassOfClass:[GDataFeedBase class]]);
  653. object = [[objectClass alloc] initWithXMLElement:root
  654. parent:nil
  655. serviceVersion:serviceVersion
  656. surrogates:surrogates
  657. shouldIgnoreUnknowns:shouldIgnoreUnknowns];
  658. // we're done parsing; the extension declarations won't be needed again
  659. [object clearExtensionDeclarationsCache];
  660. #if GDATA_USES_LIBXML
  661. // retain the document so that pointers to internal nodes remain valid
  662. [object setProperty:xmlDocument forKey:kGDataXMLDocumentPropertyKey];
  663. #endif
  664. [properties setValue:object forKey:kFetcherParsedObjectKey];
  665. [object release];
  666. #if GDATA_LOG_PERFORMANCE
  667. secs2 = [NSDate timeIntervalSinceReferenceDate];
  668. NSLog(@"allocation of %@ took %f seconds", objectClass, secs2 - secs1);
  669. #endif
  670. }
  671. [properties setValue:error forKey:kFetcherParseErrorKey];
  672. if ([parseOperation isCancelled]) return;
  673. SEL parseDoneSel = @selector(handleParsedObjectForFetcher:);
  674. if (operationQueue_ != nil) {
  675. NSArray *runLoopModes = [properties valueForKey:kFetcherCallbackRunLoopModesKey];
  676. if (runLoopModes) {
  677. [self performSelector:parseDoneSel
  678. onThread:callbackThread
  679. withObject:fetcher
  680. waitUntilDone:NO
  681. modes:runLoopModes];
  682. } else {
  683. // defaults to common modes
  684. [self performSelector:parseDoneSel
  685. onThread:callbackThread
  686. withObject:fetcher
  687. waitUntilDone:NO];
  688. }
  689. // the fetcher now belongs to the callback thread
  690. } else {
  691. // in 10.4, there's no performSelector:onThread:
  692. [self performSelector:parseDoneSel withObject:fetcher];
  693. [properties removeObjectForKey:kFetcherCallbackThreadKey];
  694. }
  695. // We drain here to keep the clang static analyzer quiet.
  696. [pool drain];
  697. }
  698. - (void)handleParsedObjectForFetcher:(GTMHTTPFetcher *)fetcher {
  699. // after parsing is complete, this is invoked on the thread that the
  700. // fetch was performed on
  701. GDataServiceTicketBase *ticket = [fetcher propertyForKey:kFetcherTicketKey];
  702. [ticket setParseOperation:nil];
  703. // unpack the callback parameters
  704. id delegate = [fetcher propertyForKey:kFetcherDelegateKey];
  705. GDataObject *object = [fetcher propertyForKey:kFetcherParsedObjectKey];
  706. NSError *error = [fetcher propertyForKey:kFetcherParseErrorKey];
  707. SEL finishedSelector = NSSelectorFromString([fetcher propertyForKey:kFetcherFinishedSelectorKey]);
  708. GDataServiceCompletionHandler completionHandler;
  709. #if NS_BLOCKS_AVAILABLE
  710. completionHandler = [fetcher propertyForKey:kFetcherCompletionHandlerKey];
  711. #else
  712. completionHandler = NULL;
  713. #endif
  714. NSNotificationCenter *defaultNC = [NSNotificationCenter defaultCenter];
  715. [defaultNC postNotificationName:kGDataServiceTicketParsingStoppedNotification
  716. object:ticket];
  717. NSData *data = [fetcher downloadedData];
  718. NSUInteger dataLength = [data length];
  719. // if we created the object (or we got empty data back, as from a GData
  720. // delete resource request) then we succeeded
  721. if (object != nil || dataLength == 0) {
  722. // if the user is fetching a feed and the ticket specifies that "next" links
  723. // should be followed, then do that now
  724. if ([ticket shouldFollowNextLinks]
  725. && [object isKindOfClass:[GDataFeedBase class]]) {
  726. GDataFeedBase *latestFeed = (GDataFeedBase *)object;
  727. // append the latest feed
  728. [ticket accumulateFeed:latestFeed];
  729. NSURL *nextURL = [[latestFeed nextLink] URL];
  730. if (nextURL) {
  731. BOOL isFetchingNextFeed = [self fetchNextFeedWithURL:nextURL
  732. delegate:delegate
  733. didFinishedSelector:finishedSelector
  734. completionHandler:completionHandler
  735. ticket:ticket];
  736. // skip calling the callbacks since the ticket is still in progress
  737. if (isFetchingNextFeed) {
  738. return;
  739. } else {
  740. // the fetch didn't start; fall through to the callback for the
  741. // feed accumulated so far
  742. }
  743. }
  744. // no more "next" links are present, so we don't need to accumulate more
  745. // entries
  746. GDataFeedBase *accumulatedFeed = [ticket accumulatedFeed];
  747. if (accumulatedFeed) {
  748. // remove the misleading "next" link from the accumulated feed
  749. GDataLink *accumulatedFeedNextLink = [accumulatedFeed nextLink];
  750. if (accumulatedFeedNextLink) {
  751. [accumulatedFeed removeLink:accumulatedFeedNextLink];
  752. }
  753. #if DEBUG && !GDATA_SKIP_NEXTLINK_WARNING
  754. // each next link followed to accumulate all pages of a feed takes up to
  755. // a few seconds. When multiple next links are being followed, that
  756. // usually indicates that a larger page size (that is, more entries per
  757. // feed fetched) should be requested.
  758. //
  759. // To avoid following next links, when fetching a feed, make it a query
  760. // fetch instead; when fetching a query, use setMaxResults: so the feed
  761. // requested is large enough to rarely need to follow next links.
  762. NSUInteger feedPageCount = [ticket nextLinksFollowedCounter];
  763. if (feedPageCount > 2) {
  764. NSLog(@"Fetching %@ required following %u \"next\" links; use a query with a larger setMaxResults: for faster feed accumulation",
  765. NSStringFromClass([accumulatedFeed class]),
  766. (unsigned int) [ticket nextLinksFollowedCounter]);
  767. }
  768. #endif
  769. // return the completed feed as the object that was fetched
  770. object = accumulatedFeed;
  771. }
  772. }
  773. if (finishedSelector) {
  774. [[self class] invokeCallback:finishedSelector
  775. target:delegate
  776. ticket:ticket
  777. object:object
  778. error:nil];
  779. }
  780. #if NS_BLOCKS_AVAILABLE
  781. if (completionHandler) {
  782. completionHandler(ticket, object, nil);
  783. }
  784. #endif
  785. [ticket setFetchedObject:object];
  786. } else {
  787. if (error == nil) {
  788. error = [NSError errorWithDomain:kGDataServiceErrorDomain
  789. code:kGDataCouldNotConstructObjectError
  790. userInfo:nil];
  791. }
  792. if (finishedSelector) {
  793. [[self class] invokeCallback:finishedSelector
  794. target:delegate
  795. ticket:ticket
  796. object:nil
  797. error:error];
  798. }
  799. #if NS_BLOCKS_AVAILABLE
  800. if (completionHandler) {
  801. completionHandler(ticket, nil, error);
  802. }
  803. #endif
  804. [ticket setFetchError:error];
  805. }
  806. [fetcher setProperties:nil];
  807. [ticket setHasCalledCallback:YES];
  808. [ticket setCurrentFetcher:nil];
  809. }
  810. - (void)objectFetcher:(GTMHTTPFetcher *)fetcher failedWithData:(NSData *)data error:(NSError *)error {
  811. #if DEBUG
  812. NSString *dataString = [[[NSString alloc] initWithData:data
  813. encoding:NSUTF8StringEncoding] autorelease];
  814. if (dataString) {
  815. NSLog(@"serviceBase:%@ objectFetcher:%@ failedWithStatus:%ld data:%@",
  816. self, fetcher, (long) [error code], dataString);
  817. }
  818. #endif
  819. id delegate = [fetcher propertyForKey:kFetcherDelegateKey];
  820. GDataServiceTicketBase *ticket = [fetcher propertyForKey:kFetcherTicketKey];
  821. NSString *finishedSelectorStr = [fetcher propertyForKey:kFetcherFinishedSelectorKey];
  822. SEL finishedSelector = finishedSelectorStr ? NSSelectorFromString(finishedSelectorStr) : NULL;
  823. if (error != nil) {
  824. // create structured errors in the userInfo, if appropriate
  825. NSDictionary *responseHeaders = [fetcher responseHeaders];
  826. NSString *contentType = [responseHeaders objectForKey:@"Content-Type"];
  827. NSDictionary *newUserInfo = [self userInfoForErrorResponseData:data
  828. contentType:contentType
  829. previousUserInfo:[error userInfo]];
  830. error = [NSError errorWithDomain:[error domain]
  831. code:[error code]
  832. userInfo:newUserInfo];
  833. }
  834. if (finishedSelector) {
  835. [[self class] invokeCallback:finishedSelector
  836. target:delegate
  837. ticket:ticket
  838. object:nil
  839. error:error];
  840. }
  841. #if NS_BLOCKS_AVAILABLE
  842. GDataServiceCompletionHandler completionHandler;
  843. completionHandler = [fetcher propertyForKey:kFetcherCompletionHandlerKey];
  844. if (completionHandler) {
  845. completionHandler(ticket, nil, error);
  846. }
  847. #endif
  848. [fetcher setProperties:nil];
  849. [ticket setFetchError:error];
  850. [ticket setHasCalledCallback:YES];
  851. [ticket setCurrentFetcher:nil];
  852. }
  853. #pragma mark -
  854. // create an error userInfo dictionary containing a useful reason string and,
  855. // for structured XML errors, a server error group object
  856. - (NSDictionary *)userInfoForErrorResponseData:(NSData *)data
  857. contentType:(NSString *)contentType
  858. previousUserInfo:(NSDictionary *)previousUserInfo {
  859. // NSError's default localizedReason value looks like
  860. // "(com.google.GDataServiceDomain error -4.)"
  861. //
  862. // The NSError domain and numeric code aren't the ones we care about
  863. // so much as the error present in the server response data, so
  864. // we'll try to store a more useful reason in the userInfo dictionary
  865. NSString *reasonStr = nil;
  866. NSMutableDictionary *userInfo;
  867. if (previousUserInfo) {
  868. userInfo = [NSMutableDictionary dictionaryWithDictionary:previousUserInfo];
  869. } else {
  870. userInfo = [NSMutableDictionary dictionary];
  871. }
  872. if ([data length] > 0) {
  873. // check if the response is a structured XML error string, according to the
  874. // response content-type header; if so, convert the XML to a
  875. // GDataServerErrorGroup
  876. if ([[contentType lowercaseString] hasPrefix:kXMLErrorContentType]) {
  877. GDataServerErrorGroup *errorGroup
  878. = [[[GDataServerErrorGroup alloc] initWithData:data] autorelease];
  879. if (errorGroup != nil) {
  880. // store the server group in the userInfo for the error
  881. [userInfo setObject:errorGroup forKey:kGDataStructuredErrorsKey];
  882. reasonStr = [[errorGroup mainError] summary];
  883. }
  884. }
  885. if ([userInfo count] == 0) {
  886. // no structured XML error was available; deal with a plaintext server
  887. // error response
  888. reasonStr = [[[NSString alloc] initWithData:data
  889. encoding:NSUTF8StringEncoding] autorelease];
  890. }
  891. }
  892. if (reasonStr != nil) {
  893. // we always store an error in the userInfo key "error"
  894. [userInfo setObject:reasonStr forKey:kGDataServerErrorStringKey];
  895. // store a user-readable "reason" to show up when an error is logged,
  896. // in parentheses like NSError does it
  897. NSString *parenthesized = [NSString stringWithFormat:@"(%@)", reasonStr];
  898. [userInfo setObject:parenthesized forKey:NSLocalizedFailureReasonErrorKey];
  899. }
  900. return userInfo;
  901. }
  902. + (void)invokeCallback:(SEL)callbackSel
  903. target:(id)target
  904. ticket:(id)ticket
  905. object:(id)object
  906. error:(id)error {
  907. // GData fetch callbacks have no return value
  908. NSMethodSignature *signature = [target methodSignatureForSelector:callbackSel];
  909. NSInvocation *retryInvocation = [NSInvocation invocationWithMethodSignature:signature];
  910. [retryInvocation setSelector:callbackSel];
  911. [retryInvocation setTarget:target];
  912. [retryInvocation setArgument:&ticket atIndex:2];
  913. [retryInvocation setArgument:&object atIndex:3];
  914. [retryInvocation setArgument:&error atIndex:4];
  915. [retryInvocation invoke];
  916. }
  917. // The object fetcher may call into this retry method; this one invokes the
  918. // selector provided by the user.
  919. - (BOOL)objectFetcher:(GTMHTTPFetcher *)fetcher willRetry:(BOOL)willRetry forError:(NSError *)error {
  920. id delegate = [fetcher propertyForKey:kFetcherDelegateKey];
  921. GDataServiceTicketBase *ticket = [fetcher propertyForKey:kFetcherTicketKey];
  922. SEL retrySelector = [ticket retrySelector];
  923. if (retrySelector) {
  924. willRetry = [self invokeRetrySelector:retrySelector
  925. delegate:delegate
  926. ticket:ticket
  927. willRetry:willRetry
  928. error:error];
  929. }
  930. return willRetry;
  931. }
  932. - (BOOL)invokeRetrySelector:(SEL)retrySelector
  933. delegate:(id)delegate
  934. ticket:(GDataServiceTicketBase *)ticket
  935. willRetry:(BOOL)willRetry
  936. error:(NSError *)error {
  937. if ([delegate respondsToSelector:retrySelector]) {
  938. // Unlike the retry selector invocation in GTMHTTPFetcher, this invocation
  939. // passes the ticket rather than the fetcher as argument 2
  940. NSMethodSignature *signature = [delegate methodSignatureForSelector:retrySelector];
  941. NSInvocation *retryInvocation = [NSInvocation invocationWithMethodSignature:signature];
  942. [retryInvocation setSelector:retrySelector];
  943. [retryInvocation setTarget:delegate];
  944. [retryInvocation setArgument:&ticket atIndex:2]; // ticket passed
  945. [retryInvocation setArgument:&willRetry atIndex:3];
  946. [retryInvocation setArgument:&error atIndex:4];
  947. [retryInvocation invoke];
  948. [retryInvocation getReturnValue:&willRetry];
  949. }
  950. return willRetry;
  951. }
  952. - (void)addAuthenticationToFetcher:(GTMHTTPFetcher *)fetcher {
  953. NSString *username = [self username];
  954. NSString *password = [self password];
  955. if ([username length] > 0 && [password length] > 0) {
  956. // We're avoiding +[NSURCredential credentialWithUser:password:persistence:]
  957. // because it fails to autorelease itself on OS X 10.4 .. 10.5.x
  958. // rdar://5596278
  959. NSURLCredential *cred;
  960. cred = [[[NSURLCredential alloc] initWithUser:username
  961. password:password
  962. persistence:NSURLCredentialPersistenceForSession] autorelease];
  963. [fetcher setCredential:cred];
  964. }
  965. }
  966. // when a ticket is set to follow "next" links for feeds, this routine
  967. // initiates the fetch for each additional feed
  968. - (BOOL)fetchNextFeedWithURL:(NSURL *)nextFeedURL
  969. delegate:(id)delegate
  970. didFinishedSelector:(SEL)finishedSelector
  971. completionHandler:(GDataServiceCompletionHandler)completionHandler
  972. ticket:(GDataServiceTicketBase *)ticket {
  973. // sanity check the number of pages fetched already
  974. NSUInteger followedCounter = [ticket nextLinksFollowedCounter];
  975. if (followedCounter > kMaxNumberOfNextLinksFollowed) {
  976. // the client should be querying with a higher max results per page
  977. // to avoid this
  978. GDATA_DEBUG_ASSERT(0, @"Following next links retrieves too many pages (URL %@)",
  979. nextFeedURL);
  980. return NO;
  981. }
  982. [ticket setNextLinksFollowedCounter:(1 + followedCounter)];
  983. // by definition, feed requests are GETs, so objectToPost: and httpMethod:
  984. // should be nil
  985. GDataServiceTicketBase *startedTicket;
  986. startedTicket = [self fetchObjectWithURL:nextFeedURL
  987. objectClass:[[ticket accumulatedFeed] class]
  988. objectToPost:nil
  989. ETag:nil
  990. httpMethod:nil
  991. delegate:delegate
  992. didFinishSelector:finishedSelector
  993. completionHandler:completionHandler
  994. retryInvocationValue:nil
  995. ticket:ticket];
  996. // in the bizarre case that the fetch didn't begin, startedTicket will be
  997. // nil. So long as the started ticket is the same as the ticket we're
  998. // continuing, then we're happy.
  999. return (ticket == startedTicket);
  1000. }
  1001. - (BOOL)waitForTicket:(GDataServiceTicketBase *)ticket
  1002. timeout:(NSTimeInterval)timeoutInSeconds
  1003. fetchedObject:(GDataObject **)outObjectOrNil
  1004. error:(NSError **)outErrorOrNil {
  1005. NSDate* giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds];
  1006. // loop until the fetch completes with an object or an error,
  1007. // or until the timeout has expired
  1008. while (![ticket hasCalledCallback]
  1009. && [giveUpDate timeIntervalSinceNow] > 0) {
  1010. // run the current run loop 1/1000 of a second to give the networking
  1011. // code a chance to work
  1012. NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:0.001];
  1013. [[NSRunLoop currentRunLoop] runUntilDate:stopDate];
  1014. }
  1015. NSError *fetchError = [ticket fetchError];
  1016. if (![ticket hasCalledCallback] && fetchError == nil) {
  1017. fetchError = [NSError errorWithDomain:kGDataServiceErrorDomain
  1018. code:kGDataWaitTimedOutError
  1019. userInfo:nil];
  1020. }
  1021. if (outObjectOrNil) *outObjectOrNil = [ticket fetchedObject];
  1022. if (outErrorOrNil) *outErrorOrNil = fetchError;
  1023. return (fetchError == nil);
  1024. }
  1025. #pragma mark -
  1026. // These external entry points all call into fetchObjectWithURL: defined above
  1027. - (GDataServiceTicketBase *)fetchPublicFeedWithURL:(NSURL *)feedURL
  1028. feedClass:(Class)feedClass
  1029. delegate:(id)delegate
  1030. didFinishSelector:(SEL)finishedSelector {
  1031. return [self fetchObjectWithURL:feedURL
  1032. objectClass:feedClass
  1033. objectToPost:nil
  1034. ETag:nil
  1035. httpMethod:nil
  1036. delegate:delegate
  1037. didFinishSelector:finishedSelector
  1038. completionHandler:nil
  1039. retryInvocationValue:nil
  1040. ticket:nil];
  1041. }
  1042. - (GDataServiceTicketBase *)fetchPublicEntryWithURL:(NSURL *)entryURL
  1043. entryClass:(Class)entryClass
  1044. delegate:(id)delegate
  1045. didFinishSelector:(SEL)finishedSelector {
  1046. return [self fetchObjectWithURL:entryURL
  1047. objectClass:entryClass
  1048. objectToPost:nil
  1049. ETag:nil
  1050. httpMethod:nil
  1051. delegate:delegate
  1052. didFinishSelector:finishedSelector
  1053. completionHandler:nil
  1054. retryInvocationValue:nil
  1055. ticket:nil];
  1056. }
  1057. - (GDataServiceTicketBase *)fetchPublicFeedWithQuery:(GDataQuery *)query
  1058. feedClass:(Class)feedClass
  1059. delegate:(id)delegate
  1060. didFinishSelector:(SEL)finishedSelector {
  1061. return [self fetchPublicFeedWithURL:[query URL]
  1062. feedClass:feedClass
  1063. delegate:delegate
  1064. didFinishSelector:finishedSelector];
  1065. }
  1066. - (GDataServiceTicketBase *)fetchPublicFeedWithBatchFeed:(GDataFeedBase *)batchFeed
  1067. forFeedURL:(NSURL *)feedURL
  1068. delegate:(id)delegate
  1069. didFinishSelector:(SEL)finishedSelector
  1070. completionHandler:(GDataServiceCompletionHandler)completionHandler {
  1071. // internal routine, used for both callback and blocks style of batch feed
  1072. // fetches
  1073. // add basic namespaces to feed, if needed
  1074. if ([[batchFeed namespaces] objectForKey:kGDataNamespaceGDataPrefix] == nil) {
  1075. [batchFeed addNamespaces:[GDataEntryBase baseGDataNamespaces]];
  1076. }
  1077. // add batch namespace, if needed
  1078. if ([[batchFeed namespaces] objectForKey:kGDataNamespaceBatchPrefix] == nil) {
  1079. [batchFeed addNamespaces:[GDataEntryBase batchNamespaces]];
  1080. }
  1081. GDataServiceTicketBase *ticket;
  1082. ticket = [self fetchObjectWithURL:feedURL
  1083. objectClass:[batchFeed class]
  1084. objectToPost:batchFeed
  1085. ETag:nil
  1086. httpMethod:nil
  1087. delegate:delegate
  1088. didFinishSelector:finishedSelector
  1089. completionHandler:completionHandler
  1090. retryInvocationValue:nil
  1091. ticket:nil];
  1092. // batch feeds never ignore unknowns, since they are intrinsically
  1093. // used for updating so their entries need to include complete XML
  1094. [ticket setShouldFeedsIgnoreUnknowns:NO];
  1095. return ticket;
  1096. }
  1097. - (GDataServiceTicketBase *)fetchPublicFeedWithBatchFeed:(GDataFeedBase *)batchFeed
  1098. forFeedURL:(NSURL *)feedURL
  1099. delegate:(id)delegate
  1100. didFinishSelector:(SEL)finishedSelector {
  1101. return [self fetchPublicFeedWithBatchFeed:batchFeed
  1102. forFeedURL:feedURL
  1103. delegate:delegate
  1104. didFinishSelector:finishedSelector
  1105. completionHandler:NULL];
  1106. }
  1107. #if NS_BLOCKS_AVAILABLE
  1108. - (GDataServiceTicketBase *)fetchPublicFeedWithURL:(NSURL *)feedURL
  1109. feedClass:(Class)feedClass
  1110. completionHandler:(GDataServiceFeedBaseCompletionHandler)handler {
  1111. return [self fetchObjectWithURL:feedURL
  1112. objectClass:feedClass
  1113. objectToPost:nil
  1114. ETag:nil
  1115. httpMethod:nil
  1116. delegate:nil
  1117. didFinishSelector:NULL
  1118. completionHandler:(GDataServiceCompletionHandler)handler
  1119. retryInvocationValue:nil
  1120. ticket:nil];
  1121. }
  1122. - (GDataServiceTicketBase *)fetchPublicFeedWithQuery:(GDataQuery *)query
  1123. feedClass:(Class)feedClass
  1124. completionHandler:(GDataServiceFeedBaseCompletionHandler)handler {
  1125. return [self fetchPublicFeedWithURL:[query URL]
  1126. feedClass:feedClass
  1127. completionHandler:handler];
  1128. }
  1129. - (GDataServiceTicketBase *)fetchPublicEntryWithURL:(NSURL *)entryURL
  1130. entryClass:(Class)entryClass
  1131. completionHandler:(GDataServiceEntryBaseCompletionHandler)handler {
  1132. return [self fetchObjectWithURL:entryURL
  1133. objectClass:entryClass
  1134. objectToPost:nil
  1135. ETag:nil
  1136. httpMethod:nil
  1137. delegate:nil
  1138. didFinishSelector:NULL
  1139. completionHandler:(GDataServiceCompletionHandler)handler
  1140. retryInvocationValue:nil
  1141. ticket:nil];
  1142. }
  1143. - (GDataServiceTicketBase *)fetchPublicFeedWithBatchFeed:(GDataFeedBase *)batchFeed
  1144. forFeedURL:(NSURL *)feedURL
  1145. completionHandler:(GDataServiceFeedBaseCompletionHandler)handler {
  1146. return [self fetchPublicFeedWithBatchFeed:batchFeed
  1147. forFeedURL:feedURL
  1148. delegate:nil
  1149. didFinishSelector:NULL
  1150. completionHandler:(GDataServiceCompletionHandler)handler];
  1151. }
  1152. #endif // NS_BLOCKS_AVAILABLE
  1153. #pragma mark -
  1154. // Subclasses typically implement defaultServiceVersion to specify the expected
  1155. // version of the feed, but clients may also explicitly set the version
  1156. // if they are using an instance of the base class directly.
  1157. + (NSString *)defaultServiceVersion {
  1158. return nil;
  1159. }
  1160. - (NSString *)serviceVersion {
  1161. if (serviceVersion_ != nil) {
  1162. return serviceVersion_;
  1163. }
  1164. NSString *str = [[self class] defaultServiceVersion];
  1165. return str;
  1166. }
  1167. - (void)setServiceVersion:(NSString *)str {
  1168. [serviceVersion_ autorelease];
  1169. serviceVersion_ = [str copy];
  1170. }
  1171. - (NSString *)userAgent {
  1172. return userAgent_;
  1173. }
  1174. - (void)setExactUserAgent:(NSString *)userAgent {
  1175. // internal use only
  1176. [userAgent_ release];
  1177. userAgent_ = [userAgent copy];
  1178. }
  1179. - (void)setUserAgent:(NSString *)userAgent {
  1180. // remove whitespace and unfriendly characters
  1181. NSString *str = GTMCleanedUserAgentString(userAgent);
  1182. [self setExactUserAgent:str];
  1183. }
  1184. - (NSArray *)runLoopModes {
  1185. return [fetcherService_ runLoopModes];
  1186. }
  1187. - (void)setRunLoopModes:(NSArray *)modes {
  1188. [fetcherService_ setRunLoopModes:modes];
  1189. }
  1190. - (BOOL)shouldFetchInBackground {
  1191. return [fetcherService_ shouldFetchInBackground];
  1192. }
  1193. - (void)setShouldFetchInBackground:(BOOL)flag {
  1194. [fetcherService_ setShouldFetchInBackground:flag];
  1195. }
  1196. // save the username and password, converting the password to non-plaintext
  1197. - (void)setUserCredentialsWithUsername:(NSString *)username
  1198. password:(NSString *)password {
  1199. if (username && ![username_ isEqual:username]) {
  1200. // username changed; discard history so we're not caching for the wrong
  1201. // user
  1202. [fetcherService_ clearHistory];
  1203. }
  1204. [username_ release];
  1205. username_ = [username copy];
  1206. [password_ release];
  1207. if (password) {
  1208. const char *utf8password = [password UTF8String];
  1209. size_t passwordLength = strlen(utf8password);
  1210. password_ = [[NSMutableData alloc] initWithBytes:utf8password
  1211. length:passwordLength];
  1212. XorPlainMutableData(password_);
  1213. } else {
  1214. password_ = nil;
  1215. }
  1216. }
  1217. - (NSString *)username {
  1218. return username_;
  1219. }
  1220. // return the password as plaintext
  1221. - (NSString *)password {
  1222. if (password_) {
  1223. XorPlainMutableData(password_);
  1224. NSString *result = [[[NSString alloc] initWithData:password_
  1225. encoding:NSUTF8StringEncoding] autorelease];
  1226. XorPlainMutableData(password_);
  1227. return result;
  1228. }
  1229. return nil;
  1230. }
  1231. - (id)authorizer {
  1232. return [fetcherService_ authorizer];
  1233. }
  1234. - (void)setAuthorizer:(id)obj {
  1235. if (obj != nil) {
  1236. // clear any existing username/password
  1237. [self setUserCredentialsWithUsername:nil
  1238. password:nil];
  1239. }
  1240. if (obj != [fetcherService_ authorizer]) {
  1241. [self clearResponseDataCache];
  1242. [fetcherService_ setAuthorizer:obj];
  1243. }
  1244. }
  1245. // Turn on data caching to receive a copy of previously-retrieved objects.
  1246. // Otherwise, fetches may return status 304 (Not Modified) rather than actual
  1247. // data
  1248. - (void)setShouldCacheResponseData:(BOOL)flag {
  1249. [fetcherService_ setShouldCacheETaggedData:flag];
  1250. }
  1251. - (BOOL)shouldCacheResponseData {
  1252. return [fetcherService_ shouldCacheETaggedData];
  1253. }
  1254. - (void)setResponseDataCacheCapacity:(NSUInteger)totalBytes {
  1255. [[fetcherService_ fetchHistory] setMemoryCapacity:totalBytes];
  1256. }
  1257. - (NSUInteger)responseDataCacheCapacity {
  1258. return [[fetcherService_ fetchHistory] memoryCapacity];
  1259. }
  1260. // reset the cache to avoid getting a Not Modified status
  1261. // based on prior queries
  1262. - (void)clearResponseDataCache {
  1263. [fetcherService_ clearETaggedDataCache];
  1264. }
  1265. - (void)setFetcherService:(GTMHTTPFetcherService *)obj {
  1266. [fetcherService_ autorelease];
  1267. fetcherService_ = [obj retain];
  1268. }
  1269. - (GTMHTTPFetcherService *)fetcherService {
  1270. return fetcherService_;
  1271. }
  1272. - (void)setCookieStorageMethod:(NSInteger)method {
  1273. cookieStorageMethod_ = method;
  1274. }
  1275. - (NSInteger)cookieStorageMethod {
  1276. return cookieStorageMethod_;
  1277. }
  1278. - (void)setServiceShouldFollowNextLinks:(BOOL)flag {
  1279. serviceShouldFollowNextLinks_ = flag;
  1280. }
  1281. - (BOOL)serviceShouldFollowNextLinks {
  1282. return serviceShouldFollowNextLinks_;
  1283. }
  1284. // The service userData becomes the initial value for each future ticket's
  1285. // userData.
  1286. //
  1287. // Since the network transactions may begin before the client has been
  1288. // returned the ticket by the fetch call, it's preferable to call
  1289. // setServiceUserData before the ticket is created rather than call the
  1290. // ticket's setUserData:. Either way, the ticket's userData:
  1291. // method will return the value.
  1292. - (void)setServiceUserData:(id)userData {
  1293. [serviceUserData_ autorelease];
  1294. serviceUserData_ = [userData retain];
  1295. }
  1296. - (id)serviceUserData {
  1297. return serviceUserData_;
  1298. }
  1299. // The service properties dictionary becomes the dictionary for each future
  1300. // ticket.
  1301. - (void)setServiceProperties:(NSDictionary *)dict {
  1302. [serviceProperties_ autorelease];
  1303. serviceProperties_ = [dict mutableCopy];
  1304. }
  1305. - (NSDictionary *)serviceProperties {
  1306. // be sure the returned pointer has the life of the autorelease pool,
  1307. // in case self is released immediately
  1308. return [[serviceProperties_ retain] autorelease];
  1309. }
  1310. - (void)setServiceProperty:(id)obj forKey:(NSString *)key {
  1311. if (obj == nil) {
  1312. // user passed in nil, so delete the property
  1313. [serviceProperties_ removeObjectForKey:key];
  1314. } else {
  1315. // be sure the property dictionary exists
  1316. if (serviceProperties_ == nil) {
  1317. [self setServiceProperties:[NSDictionary dictionary]];
  1318. }
  1319. [serviceProperties_ setObject:obj forKey:key];
  1320. }
  1321. }
  1322. - (id)servicePropertyForKey:(NSString *)key {
  1323. id obj = [serviceProperties_ objectForKey:key];
  1324. // be sure the returned pointer has the life of the autorelease pool,
  1325. // in case self is released immediately
  1326. return [[obj retain] autorelease];
  1327. }
  1328. - (NSDictionary *)serviceSurrogates {
  1329. return serviceSurrogates_;
  1330. }
  1331. - (void)setServiceSurrogates:(NSDictionary *)dict {
  1332. [serviceSurrogates_ autorelease];
  1333. serviceSurrogates_ = [dict retain];
  1334. }
  1335. - (BOOL)shouldServiceFeedsIgnoreUnknowns {
  1336. return shouldServiceFeedsIgnoreUnknowns_;
  1337. }
  1338. - (void)setShouldServiceFeedsIgnoreUnknowns:(BOOL)flag {
  1339. shouldServiceFeedsIgnoreUnknowns_ = flag;
  1340. }
  1341. - (SEL)serviceUploadProgressSelector {
  1342. return serviceUploadProgressSelector_;
  1343. }
  1344. - (void)setServiceUploadProgressSelector:(SEL)progressSelector {
  1345. serviceUploadProgressSelector_ = progressSelector;
  1346. }
  1347. #if NS_BLOCKS_AVAILABLE
  1348. - (void)setServiceUploadProgressHandler:(GDataServiceUploadProgressHandler)block {
  1349. [serviceUploadProgressBlock_ autorelease];
  1350. serviceUploadProgressBlock_ = [block copy];
  1351. }
  1352. - (GDataServiceUploadProgressHandler)serviceUploadProgressHandler {
  1353. return serviceUploadProgressBlock_;
  1354. }
  1355. #endif
  1356. + (NSUInteger)defaultServiceUploadChunkSize {
  1357. // subclasses may override
  1358. return 0;
  1359. }
  1360. - (NSUInteger)serviceUploadChunkSize {
  1361. return uploadChunkSize_;
  1362. }
  1363. - (void)setServiceUploadChunkSize:(NSUInteger)val {
  1364. if (val == kGDataStandardUploadChunkSize) {
  1365. // determine an appropriate upload chunk size for the system
  1366. #if GDATA_IPHONE
  1367. if (![GTMHTTPFetcher doesSupportSentDataCallback]) {
  1368. // for iPhone 2, we need a small upload chunk size so there
  1369. // are frequent intrachunk callbacks for progress monitoring
  1370. //
  1371. // Picasa Web Albums has a minimum 256K chunk size when uploading photos
  1372. val = 256*1024;
  1373. } else {
  1374. val = 1024*1024;
  1375. }
  1376. #else
  1377. if (NSFoundationVersionNumber >= 751.00) {
  1378. // Mac OS X 10.6 and later
  1379. //
  1380. // we'll pick a huge upload chunk size, which minimizes http overhead
  1381. // and server effort, and we'll hope that NSURLConnection can finally
  1382. // handle big uploads reliably
  1383. val = 25*1024*1024;
  1384. } else {
  1385. // Mac OS X 10.5
  1386. //
  1387. // NSURLConnection is more reliable on POSTs in 10.5 than it was in
  1388. // 10.4, but it still fails mysteriously on big uploads on some
  1389. // systems, so we'll limit the chunks to a megabyte
  1390. val = 1024*1024;
  1391. }
  1392. #endif
  1393. }
  1394. uploadChunkSize_ = val;
  1395. }
  1396. // retrying; see comments on retry support at the top of GTMHTTPFetcher
  1397. - (BOOL)isServiceRetryEnabled {
  1398. return isServiceRetryEnabled_;
  1399. }
  1400. - (void)setIsServiceRetryEnabled:(BOOL)flag {
  1401. isServiceRetryEnabled_ = flag;
  1402. }
  1403. - (SEL)serviceRetrySelector {
  1404. return serviceRetrySEL_;
  1405. }
  1406. - (void)setServiceRetrySelector:(SEL)theSel {
  1407. serviceRetrySEL_ = theSel;
  1408. }
  1409. - (NSTimeInterval)serviceMaxRetryInterval {
  1410. return serviceMaxRetryInterval_;
  1411. }
  1412. - (void)setServiceMaxRetryInterval:(NSTimeInterval)secs {
  1413. serviceMaxRetryInterval_ = secs;
  1414. }
  1415. - (id)operationQueue {
  1416. return operationQueue_;
  1417. }
  1418. - (void)setOperationQueue:(id)queue {
  1419. [operationQueue_ autorelease];
  1420. operationQueue_ = [queue retain];
  1421. }
  1422. #pragma mark -
  1423. + (NSBundle *)owningBundle {
  1424. NSBundle *bundle = [NSBundle bundleForClass:self];
  1425. if (bundle == nil
  1426. || [[bundle bundleIdentifier] isEqual:@"com.google.GDataFramework"]) {
  1427. bundle = [NSBundle mainBundle];
  1428. }
  1429. return bundle;
  1430. }
  1431. // return a generic name and version for the current application; this avoids
  1432. // anonymous server transactions. Applications should call setUserAgent
  1433. // to avoid the need for this method to be used.
  1434. + (NSString *)defaultApplicationIdentifier {
  1435. NSBundle *bundle = [[self class] owningBundle];
  1436. NSString *appID = GTMApplicationIdentifier(bundle);
  1437. return appID;
  1438. }
  1439. @end
  1440. @implementation GDataServiceTicketBase
  1441. + (id)ticketForService:(GDataServiceBase *)service {
  1442. return [[[self alloc] initWithService:service] autorelease];
  1443. }
  1444. - (id)initWithService:(GDataServiceBase *)service {
  1445. self = [super init];
  1446. if (self) {
  1447. service_ = [service retain];
  1448. [self setUserData:[service serviceUserData]];
  1449. [self setProperties:[service serviceProperties]];
  1450. [self setSurrogates:[service serviceSurrogates]];
  1451. [self setUploadProgressSelector:[service serviceUploadProgressSelector]];
  1452. [self setIsRetryEnabled:[service isServiceRetryEnabled]];
  1453. [self setRetrySelector:[service serviceRetrySelector]];
  1454. [self setMaxRetryInterval:[service serviceMaxRetryInterval]];
  1455. [self setShouldFollowNextLinks:[service serviceShouldFollowNextLinks]];
  1456. [self setShouldFeedsIgnoreUnknowns:[service shouldServiceFeedsIgnoreUnknowns]];
  1457. #if NS_BLOCKS_AVAILABLE
  1458. [self setUploadProgressHandler:[service serviceUploadProgressHandler]];
  1459. #endif
  1460. [self setAuthorizer:[service authorizer]];
  1461. }
  1462. return self;
  1463. }
  1464. - (void)dealloc {
  1465. [service_ release];
  1466. [userData_ release];
  1467. [ticketProperties_ release];
  1468. [surrogates_ release];
  1469. [currentFetcher_ release];
  1470. [objectFetcher_ release];
  1471. #if NS_BLOCKS_AVAILABLE
  1472. [uploadProgressBlock_ release];
  1473. #endif
  1474. [postedObject_ release];
  1475. [fetchedObject_ release];
  1476. [accumulatedFeed_ release];
  1477. [fetchError_ release];
  1478. [parseOperation_ release];
  1479. [authorizer_ release];
  1480. [super dealloc];
  1481. }
  1482. - (NSString *)description {
  1483. NSString *const templateStr = @"%@ %p: {service:%@ currentFetcher:%@ userData:%@}";
  1484. return [NSString stringWithFormat:templateStr,
  1485. [self class], self, service_, currentFetcher_, userData_];
  1486. }
  1487. - (void)pauseUpload {
  1488. BOOL canPause = [objectFetcher_ respondsToSelector:@selector(pauseFetching)];
  1489. GDATA_DEBUG_ASSERT(canPause, @"unpauseable ticket");
  1490. if (canPause) {
  1491. [(GTMHTTPUploadFetcher *)objectFetcher_ pauseFetching];
  1492. }
  1493. }
  1494. - (void)resumeUpload {
  1495. BOOL canResume = [objectFetcher_ respondsToSelector:@selector(resumeFetching)];
  1496. GDATA_DEBUG_ASSERT(canResume, @"unresumable ticket");
  1497. if (canResume) {
  1498. [(GTMHTTPUploadFetcher *)objectFetcher_ resumeFetching];
  1499. }
  1500. }
  1501. - (BOOL)isUploadPaused {
  1502. BOOL isPausable = [objectFetcher_ respondsToSelector:@selector(isPaused)];
  1503. GDATA_DEBUG_ASSERT(isPausable, @"unpauseable ticket");
  1504. if (isPausable) {
  1505. return [(GTMHTTPUploadFetcher *)objectFetcher_ isPaused];
  1506. }
  1507. return NO;
  1508. }
  1509. - (void)cancelTicket {
  1510. NSOperation *op = [self parseOperation];
  1511. [op cancel];
  1512. [self setParseOperation:nil];
  1513. [objectFetcher_ stopFetching];
  1514. [objectFetcher_ setProperties:nil];
  1515. [self setObjectFetcher:nil];
  1516. [self setCurrentFetcher:nil];
  1517. [self setUserData:nil];
  1518. [self setProperties:nil];
  1519. [self setUploadProgressSelector:NULL];
  1520. #if NS_BLOCKS_AVAILABLE
  1521. [self setUploadProgressHandler:nil];
  1522. #endif
  1523. [service_ autorelease];
  1524. service_ = nil;
  1525. }
  1526. - (id)service {
  1527. return service_;
  1528. }
  1529. - (id)userData {
  1530. return [[userData_ retain] autorelease];
  1531. }
  1532. - (void)setUserData:(id)obj {
  1533. [userData_ autorelease];
  1534. userData_ = [obj retain];
  1535. }
  1536. - (void)setProperties:(NSDictionary *)dict {
  1537. [ticketProperties_ autorelease];
  1538. ticketProperties_ = [dict mutableCopy];
  1539. }
  1540. - (NSDictionary *)properties {
  1541. // be sure the returned pointer has the life of the autorelease pool,
  1542. // in case self is released immediately
  1543. return [[ticketProperties_ retain] autorelease];
  1544. }
  1545. - (void)setProperty:(id)obj forKey:(NSString *)key {
  1546. if (obj == nil) {
  1547. // user passed in nil, so delete the property
  1548. [ticketProperties_ removeObjectForKey:key];
  1549. } else {
  1550. // be sure the property dictionary exists
  1551. if (ticketProperties_ == nil) {
  1552. [self setProperties:[NSDictionary dictionary]];
  1553. }
  1554. [ticketProperties_ setObject:obj forKey:key];
  1555. }
  1556. }
  1557. - (id)propertyForKey:(NSString *)key {
  1558. id obj = [ticketProperties_ objectForKey:key];
  1559. // be sure the returned pointer has the life of the autorelease pool,
  1560. // in case self is released immediately
  1561. return [[obj retain] autorelease];
  1562. }
  1563. - (NSDictionary *)surrogates {
  1564. return surrogates_;
  1565. }
  1566. - (void)setSurrogates:(NSDictionary *)dict {
  1567. [surrogates_ autorelease];
  1568. surrogates_ = [dict retain];
  1569. }
  1570. - (SEL)uploadProgressSelector {
  1571. return uploadProgressSelector_;
  1572. }
  1573. - (void)setUploadProgressSelector:(SEL)progressSelector {
  1574. uploadProgressSelector_ = progressSelector;
  1575. // if the user is turning on the progress selector in the ticket after the
  1576. // ticket's fetcher has been created, we need to give the fetcher our sentData
  1577. // callback.
  1578. if (progressSelector != NULL) {
  1579. SEL sentDataSel = @selector(objectFetcher:didSendBytes:totalBytesSent:totalBytesExpectedToSend:);
  1580. [[self objectFetcher] setSentDataSelector:sentDataSel];
  1581. }
  1582. }
  1583. #if NS_BLOCKS_AVAILABLE
  1584. - (void)setUploadProgressHandler:(GDataServiceUploadProgressHandler)block {
  1585. [uploadProgressBlock_ autorelease];
  1586. uploadProgressBlock_ = [block copy];
  1587. if (uploadProgressBlock_) {
  1588. // as above, we need the fetcher to call us back when bytes are sent
  1589. SEL sentDataSel = @selector(objectFetcher:didSendBytes:totalBytesSent:totalBytesExpectedToSend:);
  1590. [[self objectFetcher] setSentDataSelector:sentDataSel];
  1591. }
  1592. }
  1593. - (GDataServiceUploadProgressHandler)uploadProgressHandler {
  1594. return uploadProgressBlock_;
  1595. }
  1596. #endif
  1597. - (BOOL)shouldFollowNextLinks {
  1598. return shouldFollowNextLinks_;
  1599. }
  1600. - (void)setShouldFollowNextLinks:(BOOL)flag {
  1601. shouldFollowNextLinks_ = flag;
  1602. }
  1603. - (BOOL)shouldFeedsIgnoreUnknowns {
  1604. return shouldFeedsIgnoreUnknowns_;
  1605. }
  1606. - (void)setShouldFeedsIgnoreUnknowns:(BOOL)flag {
  1607. shouldFeedsIgnoreUnknowns_ = flag;
  1608. }
  1609. - (BOOL)isRetryEnabled {
  1610. return isRetryEnabled_;
  1611. }
  1612. - (void)setIsRetryEnabled:(BOOL)flag {
  1613. isRetryEnabled_ = flag;
  1614. };
  1615. - (SEL)retrySelector {
  1616. return retrySEL_;
  1617. }
  1618. - (void)setRetrySelector:(SEL)theSelector {
  1619. retrySEL_ = theSelector;
  1620. }
  1621. - (NSTimeInterval)maxRetryInterval {
  1622. return maxRetryInterval_;
  1623. }
  1624. - (void)setMaxRetryInterval:(NSTimeInterval)secs {
  1625. maxRetryInterval_ = secs;
  1626. }
  1627. - (GTMHTTPFetcher *)currentFetcher {
  1628. return [[currentFetcher_ retain] autorelease];
  1629. }
  1630. - (void)setCurrentFetcher:(GTMHTTPFetcher *)fetcher {
  1631. [currentFetcher_ autorelease];
  1632. currentFetcher_ = [fetcher retain];
  1633. }
  1634. - (GTMHTTPFetcher *)objectFetcher {
  1635. return [[objectFetcher_ retain] autorelease];
  1636. }
  1637. - (void)setObjectFetcher:(GTMHTTPFetcher *)fetcher {
  1638. [objectFetcher_ autorelease];
  1639. objectFetcher_ = [fetcher retain];
  1640. }
  1641. - (NSInteger)statusCode {
  1642. return [objectFetcher_ statusCode];
  1643. }
  1644. - (void)setHasCalledCallback:(BOOL)flag {
  1645. hasCalledCallback_ = flag;
  1646. }
  1647. - (BOOL)hasCalledCallback {
  1648. return hasCalledCallback_;
  1649. }
  1650. - (void)setPostedObject:(GDataObject *)obj {
  1651. [postedObject_ autorelease];
  1652. postedObject_ = [obj retain];
  1653. }
  1654. - (id)postedObject {
  1655. return postedObject_;
  1656. }
  1657. - (void)setFetchedObject:(GDataObject *)obj {
  1658. [fetchedObject_ autorelease];
  1659. fetchedObject_ = [obj retain];
  1660. }
  1661. - (GDataObject *)fetchedObject {
  1662. return fetchedObject_;
  1663. }
  1664. - (void)setFetchError:(NSError *)error {
  1665. [fetchError_ autorelease];
  1666. fetchError_ = [error retain];
  1667. }
  1668. - (NSError *)fetchError {
  1669. return fetchError_;
  1670. }
  1671. - (void)setAccumulatedFeed:(GDataFeedBase *)feed {
  1672. [accumulatedFeed_ autorelease];
  1673. accumulatedFeed_ = [feed retain];
  1674. }
  1675. // accumulateFeed is used by the service to append an incomplete feed
  1676. // to the ticket when shouldFollowNextLinks is enabled
  1677. - (GDataFeedBase *)accumulatedFeed {
  1678. return accumulatedFeed_;
  1679. }
  1680. - (void)accumulateFeed:(GDataFeedBase *)newFeed {
  1681. GDataFeedBase *accumulatedFeed = [self accumulatedFeed];
  1682. if (accumulatedFeed == nil) {
  1683. // the new feed becomes the accumulated feed
  1684. [self setAccumulatedFeed:newFeed];
  1685. } else {
  1686. // A feed's addEntry: routine requires that a new entry's parent
  1687. // not be set to some other feed. Calling addEntryWithEntry: would make
  1688. // a new, parentless copy of the entry for us, but that would be a needless
  1689. // copy. Instead, we'll explicitly clear the entry's parent and call
  1690. // addEntry:.
  1691. NSArray *newEntries = [newFeed entries];
  1692. for (GDataEntryBase *entry in newEntries) {
  1693. [entry setParent:nil];
  1694. [accumulatedFeed addEntry:entry];
  1695. }
  1696. }
  1697. }
  1698. - (void)setNextLinksFollowedCounter:(NSUInteger)val {
  1699. nextLinksFollowedCounter_ = val;
  1700. }
  1701. - (NSUInteger)nextLinksFollowedCounter {
  1702. return nextLinksFollowedCounter_;
  1703. }
  1704. - (NSOperation *)parseOperation {
  1705. return parseOperation_;
  1706. }
  1707. - (void)setParseOperation:(NSOperation *)op {
  1708. [parseOperation_ autorelease];
  1709. parseOperation_ = [op retain];
  1710. }
  1711. // OAuth support
  1712. - (id)authorizer {
  1713. return authorizer_;
  1714. }
  1715. - (void)setAuthorizer:(id)obj {
  1716. [authorizer_ autorelease];
  1717. authorizer_ = [obj retain];
  1718. GDATA_DEBUG_ASSERT([obj respondsToSelector:@selector(authorizeRequest:delegate:didFinishSelector:)]
  1719. || obj == nil, @"invalid authorization object");
  1720. }
  1721. @end