PageRenderTime 40ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Three20Network/Sources/TTURLRequestQueue.m

https://github.com/GetMoPix/three20
Objective C | 703 lines | 482 code | 146 blank | 75 comment | 131 complexity | 2f0c5be422e10a0d777ec88fb2971cc4 MD5 | raw file
  1. //
  2. // Copyright 2009-2011 Facebook
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. #import "Three20Network/TTURLRequestQueue.h"
  17. // Network
  18. #import "Three20Network/TTGlobalNetwork.h"
  19. #import "Three20Network/TTURLRequest.h"
  20. #import "Three20Network/TTURLRequestDelegate.h"
  21. #import "Three20Network/TTUserInfo.h"
  22. #import "Three20Network/TTURLResponse.h"
  23. #import "Three20Network/TTURLCache.h"
  24. // Network (Private)
  25. #import "Three20Network/private/TTRequestLoader.h"
  26. // Core
  27. #import "Three20Core/TTGlobalCore.h"
  28. #import "Three20Core/TTGlobalCorePaths.h"
  29. #import "Three20Core/TTDebugFlags.h"
  30. #import "Three20Core/TTDebug.h"
  31. static const NSTimeInterval kFlushDelay = 0.3;
  32. static const NSTimeInterval kTimeout = 300.0;
  33. static const NSInteger kMaxConcurrentLoads = 5;
  34. static NSUInteger kDefaultMaxContentLength = 150000;
  35. static TTURLRequestQueue* gMainQueue = nil;
  36. ///////////////////////////////////////////////////////////////////////////////////////////////////
  37. ///////////////////////////////////////////////////////////////////////////////////////////////////
  38. ///////////////////////////////////////////////////////////////////////////////////////////////////
  39. @implementation TTURLRequestQueue
  40. @synthesize maxContentLength = _maxContentLength;
  41. @synthesize userAgent = _userAgent;
  42. @synthesize suspended = _suspended;
  43. @synthesize imageCompressionQuality = _imageCompressionQuality;
  44. @synthesize defaultTimeout = _defaultTimeout;
  45. ///////////////////////////////////////////////////////////////////////////////////////////////////
  46. + (TTURLRequestQueue*)mainQueue {
  47. if (!gMainQueue) {
  48. gMainQueue = [[TTURLRequestQueue alloc] init];
  49. }
  50. return gMainQueue;
  51. }
  52. ///////////////////////////////////////////////////////////////////////////////////////////////////
  53. + (void)setMainQueue:(TTURLRequestQueue*)queue {
  54. if (gMainQueue != queue) {
  55. [gMainQueue release];
  56. gMainQueue = [queue retain];
  57. }
  58. }
  59. ///////////////////////////////////////////////////////////////////////////////////////////////////
  60. - (id)init {
  61. self = [super init];
  62. if (self) {
  63. _loaders = [[NSMutableDictionary alloc] init];
  64. _loaderQueue = [[NSMutableArray alloc] init];
  65. _maxContentLength = kDefaultMaxContentLength;
  66. _imageCompressionQuality = 0.75;
  67. _defaultTimeout = kTimeout;
  68. }
  69. return self;
  70. }
  71. ///////////////////////////////////////////////////////////////////////////////////////////////////
  72. - (void)dealloc {
  73. [_loaderQueueTimer invalidate];
  74. TT_RELEASE_SAFELY(_loaders);
  75. TT_RELEASE_SAFELY(_loaderQueue);
  76. TT_RELEASE_SAFELY(_userAgent);
  77. [super dealloc];
  78. }
  79. ///////////////////////////////////////////////////////////////////////////////////////////////////
  80. /**
  81. * TODO (jverkoey May 3, 2010): Clean up this redundant code.
  82. */
  83. - (BOOL)dataExistsInBundle:(NSString*)URL {
  84. NSString* path = TTPathForBundleResource([URL substringFromIndex:9]);
  85. NSFileManager* fm = [NSFileManager defaultManager];
  86. return [fm fileExistsAtPath:path];
  87. }
  88. ///////////////////////////////////////////////////////////////////////////////////////////////////
  89. - (BOOL)dataExistsInDocuments:(NSString*)URL {
  90. NSString* path = TTPathForDocumentsResource([URL substringFromIndex:12]);
  91. NSFileManager* fm = [NSFileManager defaultManager];
  92. return [fm fileExistsAtPath:path];
  93. }
  94. ///////////////////////////////////////////////////////////////////////////////////////////////////
  95. - (NSData*)loadFromBundle:(NSString*)URL error:(NSError**)error {
  96. NSString* path = TTPathForBundleResource([URL substringFromIndex:9]);
  97. NSFileManager* fm = [NSFileManager defaultManager];
  98. if ([fm fileExistsAtPath:path]) {
  99. return [NSData dataWithContentsOfFile:path];
  100. } else if (error) {
  101. *error = [NSError errorWithDomain:NSCocoaErrorDomain
  102. code:NSFileReadNoSuchFileError userInfo:nil];
  103. }
  104. return nil;
  105. }
  106. ///////////////////////////////////////////////////////////////////////////////////////////////////
  107. - (NSData*)loadFromDocuments:(NSString*)URL error:(NSError**)error {
  108. NSString* path = TTPathForDocumentsResource([URL substringFromIndex:12]);
  109. NSFileManager* fm = [NSFileManager defaultManager];
  110. if ([fm fileExistsAtPath:path]) {
  111. return [NSData dataWithContentsOfFile:path];
  112. } else if (error) {
  113. *error = [NSError errorWithDomain:NSCocoaErrorDomain
  114. code:NSFileReadNoSuchFileError userInfo:nil];
  115. }
  116. return nil;
  117. }
  118. ///////////////////////////////////////////////////////////////////////////////////////////////////
  119. - (BOOL)loadFromCache: (NSString*)URL
  120. cacheKey: (NSString*)cacheKey
  121. expires: (NSTimeInterval)expirationAge
  122. fromDisk: (BOOL)fromDisk
  123. data: (id*)data
  124. error: (NSError**)error
  125. timestamp: (NSDate**)timestamp {
  126. TTDASSERT(nil != data);
  127. if (nil == data) {
  128. return NO;
  129. }
  130. UIImage* image = [[TTURLCache sharedCache] imageForURL:URL fromDisk:fromDisk];
  131. if (nil != image) {
  132. *data = image;
  133. return YES;
  134. } else if (fromDisk) {
  135. if (TTIsBundleURL(URL)) {
  136. *data = [self loadFromBundle:URL error:error];
  137. return YES;
  138. } else if (TTIsDocumentsURL(URL)) {
  139. *data = [self loadFromDocuments:URL error:error];
  140. return YES;
  141. } else {
  142. *data = [[TTURLCache sharedCache] dataForKey:cacheKey expires:expirationAge
  143. timestamp:timestamp];
  144. if (*data) {
  145. return YES;
  146. }
  147. }
  148. }
  149. return NO;
  150. }
  151. ///////////////////////////////////////////////////////////////////////////////////////////////////
  152. - (BOOL)cacheDataExists: (NSString*)URL
  153. cacheKey: (NSString*)cacheKey
  154. expires: (NSTimeInterval)expirationAge
  155. fromDisk: (BOOL)fromDisk {
  156. BOOL hasData = [[TTURLCache sharedCache] hasImageForURL:URL fromDisk:fromDisk];
  157. if (!hasData && fromDisk) {
  158. if (TTIsBundleURL(URL)) {
  159. hasData = [self dataExistsInBundle:URL];
  160. } else if (TTIsDocumentsURL(URL)) {
  161. hasData = [self dataExistsInDocuments:URL];
  162. } else {
  163. hasData = [[TTURLCache sharedCache] hasDataForKey:cacheKey expires:expirationAge];
  164. }
  165. }
  166. return hasData;
  167. }
  168. ///////////////////////////////////////////////////////////////////////////////////////////////////
  169. - (BOOL)loadRequestFromCache:(TTURLRequest*)request {
  170. if (!request.cacheKey) {
  171. request.cacheKey = [[TTURLCache sharedCache] keyForURL:request.urlPath];
  172. }
  173. if (IS_MASK_SET(request.cachePolicy, TTURLRequestCachePolicyEtag)) {
  174. // Etags always make the request. The request headers will then include the etag.
  175. // - If there is new data, server returns 200 with data.
  176. // - Otherwise, returns a 304, with empty request body.
  177. return NO;
  178. } else if (request.cachePolicy & (TTURLRequestCachePolicyDisk|TTURLRequestCachePolicyMemory)) {
  179. id data = nil;
  180. NSDate* timestamp = nil;
  181. NSError* error = nil;
  182. if ([self loadFromCache:request.urlPath cacheKey:request.cacheKey
  183. expires:request.cacheExpirationAge
  184. fromDisk:!_suspended && (request.cachePolicy & TTURLRequestCachePolicyDisk)
  185. data:&data error:&error timestamp:&timestamp]) {
  186. request.isLoading = NO;
  187. if (!error) {
  188. error = [request.response request:request processResponse:nil data:data];
  189. }
  190. if (error) {
  191. for (id<TTURLRequestDelegate> delegate in request.delegates) {
  192. if ([delegate respondsToSelector:@selector(request:didFailLoadWithError:)]) {
  193. [delegate request:request didFailLoadWithError:error];
  194. }
  195. }
  196. } else {
  197. request.timestamp = timestamp ? timestamp : [NSDate date];
  198. request.respondedFromCache = YES;
  199. for (id<TTURLRequestDelegate> delegate in request.delegates) {
  200. if ([delegate respondsToSelector:@selector(requestDidFinishLoad:)]) {
  201. [delegate requestDidFinishLoad:request];
  202. }
  203. }
  204. }
  205. return YES;
  206. }
  207. }
  208. return NO;
  209. }
  210. ///////////////////////////////////////////////////////////////////////////////////////////////////
  211. - (void)executeLoader:(TTRequestLoader*)loader {
  212. id data = nil;
  213. NSDate* timestamp = nil;
  214. NSError* error = nil;
  215. if ((loader.cachePolicy & (TTURLRequestCachePolicyDisk|TTURLRequestCachePolicyMemory))
  216. && [self loadFromCache:loader.urlPath cacheKey:loader.cacheKey
  217. expires:loader.cacheExpirationAge
  218. fromDisk:loader.cachePolicy & TTURLRequestCachePolicyDisk
  219. data:&data error:&error timestamp:&timestamp]) {
  220. [_loaders removeObjectForKey:loader.cacheKey];
  221. if (!error) {
  222. error = [loader processResponse:nil data:data];
  223. }
  224. if (error) {
  225. [loader dispatchError:error];
  226. } else {
  227. [loader dispatchLoaded:timestamp];
  228. }
  229. } else {
  230. ++_totalLoading;
  231. [loader load:[NSURL URLWithString:loader.urlPath]];
  232. }
  233. }
  234. ///////////////////////////////////////////////////////////////////////////////////////////////////
  235. - (void)loadNextInQueueDelayed {
  236. if (!_loaderQueueTimer) {
  237. _loaderQueueTimer = [NSTimer scheduledTimerWithTimeInterval:kFlushDelay target:self
  238. selector:@selector(loadNextInQueue) userInfo:nil repeats:NO];
  239. }
  240. }
  241. ///////////////////////////////////////////////////////////////////////////////////////////////////
  242. - (void)loadNextInQueue {
  243. _loaderQueueTimer = nil;
  244. for (int i = 0;
  245. i < kMaxConcurrentLoads && _totalLoading < kMaxConcurrentLoads
  246. && _loaderQueue.count;
  247. ++i) {
  248. TTRequestLoader* loader = [[_loaderQueue objectAtIndex:0] retain];
  249. [_loaderQueue removeObjectAtIndex:0];
  250. [self executeLoader:loader];
  251. [loader release];
  252. }
  253. if (_loaderQueue.count && !_suspended) {
  254. [self loadNextInQueueDelayed];
  255. }
  256. }
  257. ///////////////////////////////////////////////////////////////////////////////////////////////////
  258. - (void)removeLoader:(TTRequestLoader*)loader {
  259. --_totalLoading;
  260. [_loaders removeObjectForKey:loader.cacheKey];
  261. }
  262. ///////////////////////////////////////////////////////////////////////////////////////////////////
  263. - (void)setSuspended:(BOOL)isSuspended {
  264. TTDCONDITIONLOG(TTDFLAG_URLREQUEST, @"SUSPEND LOADING %d", isSuspended);
  265. _suspended = isSuspended;
  266. if (!_suspended) {
  267. [self loadNextInQueue];
  268. } else if (_loaderQueueTimer) {
  269. [_loaderQueueTimer invalidate];
  270. _loaderQueueTimer = nil;
  271. }
  272. }
  273. ///////////////////////////////////////////////////////////////////////////////////////////////////
  274. - (BOOL)sendRequest:(TTURLRequest*)request {
  275. if ([self loadRequestFromCache:request]) {
  276. return YES;
  277. }
  278. for (id<TTURLRequestDelegate> delegate in request.delegates) {
  279. if ([delegate respondsToSelector:@selector(requestDidStartLoad:)]) {
  280. [delegate requestDidStartLoad:request];
  281. }
  282. }
  283. // If the url is empty, fail.
  284. if (!request.urlPath.length) {
  285. NSError* error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
  286. for (id<TTURLRequestDelegate> delegate in request.delegates) {
  287. if ([delegate respondsToSelector:@selector(request:didFailLoadWithError:)]) {
  288. [delegate request:request didFailLoadWithError:error];
  289. }
  290. }
  291. return NO;
  292. }
  293. request.isLoading = YES;
  294. TTRequestLoader* loader = nil;
  295. // If we're not POSTing or PUTting data, let's see if we can jump on an existing request.
  296. if (![request.httpMethod isEqualToString:@"POST"]
  297. && ![request.httpMethod isEqualToString:@"PUT"]) {
  298. // Next, see if there is an active loader for the URL and if so join that bandwagon.
  299. loader = [_loaders objectForKey:request.cacheKey];
  300. if (loader) {
  301. [loader addRequest:request];
  302. return NO;
  303. }
  304. }
  305. // Finally, create a new loader and hit the network (unless we are suspended)
  306. loader = [[TTRequestLoader alloc] initForRequest:request queue:self];
  307. [_loaders setObject:loader forKey:request.cacheKey];
  308. if (_suspended || _totalLoading == kMaxConcurrentLoads) {
  309. [_loaderQueue addObject:loader];
  310. } else {
  311. ++_totalLoading;
  312. [loader load:[NSURL URLWithString:request.urlPath]];
  313. }
  314. [loader release];
  315. return NO;
  316. }
  317. ///////////////////////////////////////////////////////////////////////////////////////////////////
  318. - (BOOL)sendSynchronousRequest:(TTURLRequest*)request {
  319. if ([self loadRequestFromCache:request]) {
  320. return YES;
  321. }
  322. for (id<TTURLRequestDelegate> delegate in request.delegates) {
  323. if ([delegate respondsToSelector:@selector(requestDidStartLoad:)]) {
  324. [delegate requestDidStartLoad:request];
  325. }
  326. }
  327. if (!request.urlPath.length) {
  328. NSError* error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
  329. for (id<TTURLRequestDelegate> delegate in request.delegates) {
  330. if ([delegate respondsToSelector:@selector(request:didFailLoadWithError:)]) {
  331. [delegate request:request didFailLoadWithError:error];
  332. }
  333. }
  334. return NO;
  335. }
  336. request.isLoading = YES;
  337. // Finally, create a new loader and hit the network (unless we are suspended)
  338. TTRequestLoader* loader = [[TTRequestLoader alloc] initForRequest:request queue:self];
  339. // Should be decremented eventually by loadSynchronously
  340. ++_totalLoading;
  341. [loader loadSynchronously:[NSURL URLWithString:request.urlPath]];
  342. TT_RELEASE_SAFELY(loader);
  343. return NO;
  344. }
  345. ///////////////////////////////////////////////////////////////////////////////////////////////////
  346. - (void)cancelRequest:(TTURLRequest*)request {
  347. if (request) {
  348. TTRequestLoader* loader = [_loaders objectForKey:request.cacheKey];
  349. if (loader) {
  350. [loader retain];
  351. if (![loader cancel:request]) {
  352. [_loaderQueue removeObject:loader];
  353. }
  354. [loader release];
  355. }
  356. }
  357. }
  358. ///////////////////////////////////////////////////////////////////////////////////////////////////
  359. - (void)cancelRequestsWithDelegate:(id)delegate {
  360. NSMutableArray* requestsToCancel = nil;
  361. for (TTRequestLoader* loader in [_loaders objectEnumerator]) {
  362. for (TTURLRequest* request in loader.requests) {
  363. for (id<TTURLRequestDelegate> requestDelegate in request.delegates) {
  364. if (delegate == requestDelegate) {
  365. if (!requestsToCancel) {
  366. requestsToCancel = [NSMutableArray array];
  367. }
  368. [requestsToCancel addObject:request];
  369. break;
  370. }
  371. }
  372. if ([request.userInfo isKindOfClass:[TTUserInfo class]]) {
  373. TTUserInfo* userInfo = request.userInfo;
  374. if (userInfo.weakRef && userInfo.weakRef == delegate) {
  375. if (!requestsToCancel) {
  376. requestsToCancel = [NSMutableArray array];
  377. }
  378. [requestsToCancel addObject:request];
  379. }
  380. }
  381. }
  382. }
  383. for (TTURLRequest* request in requestsToCancel) {
  384. [self cancelRequest:request];
  385. }
  386. }
  387. ///////////////////////////////////////////////////////////////////////////////////////////////////
  388. - (void)cancelAllRequests {
  389. for (TTRequestLoader* loader in [[[_loaders copy] autorelease] objectEnumerator]) {
  390. [loader cancel];
  391. }
  392. }
  393. ///////////////////////////////////////////////////////////////////////////////////////////////////
  394. - (NSURLRequest*)createNSURLRequest:(TTURLRequest*)request URL:(NSURL*)URL {
  395. if (!URL) {
  396. URL = [NSURL URLWithString:request.urlPath];
  397. }
  398. NSTimeInterval usedTimeout = request.timeoutInterval;
  399. if (usedTimeout < 0.0) {
  400. usedTimeout = self.defaultTimeout;
  401. }
  402. NSMutableURLRequest* URLRequest = [NSMutableURLRequest requestWithURL:URL
  403. cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
  404. timeoutInterval:usedTimeout];
  405. if (self.userAgent) {
  406. [URLRequest setValue:self.userAgent forHTTPHeaderField:@"User-Agent"];
  407. }
  408. if (request) {
  409. [URLRequest setHTTPShouldHandleCookies:request.shouldHandleCookies];
  410. NSString* method = request.httpMethod;
  411. if (method) {
  412. [URLRequest setHTTPMethod:method];
  413. }
  414. NSString* contentType = request.contentType;
  415. if (contentType) {
  416. [URLRequest setValue:contentType forHTTPHeaderField:@"Content-Type"];
  417. }
  418. NSData* body = request.httpBody;
  419. if (body) {
  420. [URLRequest setHTTPBody:body];
  421. }
  422. NSDictionary* headers = request.headers;
  423. for (NSString *key in [headers keyEnumerator]) {
  424. [URLRequest setValue:[headers objectForKey:key] forHTTPHeaderField:key];
  425. }
  426. if (![[TTURLCache sharedCache] disableDiskCache]
  427. && IS_MASK_SET(request.cachePolicy, TTURLRequestCachePolicyEtag)) {
  428. NSString* etag = [[TTURLCache sharedCache] etagForKey:request.cacheKey];
  429. TTDCONDITIONLOG(TTDFLAG_ETAGS, @"Etag: %@", etag);
  430. if (TTIsStringWithAnyText(etag)
  431. && [self cacheDataExists: request.urlPath
  432. cacheKey: request.cacheKey
  433. expires: request.cacheExpirationAge
  434. fromDisk: !_suspended
  435. && (request.cachePolicy & TTURLRequestCachePolicyDisk)]) {
  436. // By setting the etag here, we let the server know what the last "version" of the file
  437. // was that we saw. If the file has changed since this etag, we'll get data back in our
  438. // response. Otherwise we'll get a 304.
  439. [URLRequest setValue:etag forHTTPHeaderField:@"If-None-Match"];
  440. }
  441. }
  442. }
  443. return URLRequest;
  444. }
  445. @end
  446. ///////////////////////////////////////////////////////////////////////////////////////////////////
  447. ///////////////////////////////////////////////////////////////////////////////////////////////////
  448. ///////////////////////////////////////////////////////////////////////////////////////////////////
  449. @implementation TTURLRequestQueue (TTRequestLoader)
  450. ///////////////////////////////////////////////////////////////////////////////////////////////////
  451. - (void) loader: (TTRequestLoader*)loader
  452. didLoadResponse: (NSHTTPURLResponse*)response
  453. data: (id)data {
  454. [loader retain];
  455. [self removeLoader:loader];
  456. NSError* error = [loader processResponse:response data:data];
  457. if (error) {
  458. [loader dispatchError:error];
  459. } else {
  460. if (!(loader.cachePolicy & TTURLRequestCachePolicyNoCache)) {
  461. // Store the etag key if the etag cache policy has been requested.
  462. if (![[TTURLCache sharedCache] disableDiskCache]
  463. && IS_MASK_SET(loader.cachePolicy, TTURLRequestCachePolicyEtag)) {
  464. NSDictionary* headers = [response allHeaderFields];
  465. // First, try to use the casing as defined by the standard for ETag headers.
  466. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
  467. NSString* etag = [headers objectForKey:@"ETag"];
  468. if (nil == etag) {
  469. // Some servers don't use the standard casing (e.g. twitter).
  470. etag = [headers objectForKey:@"Etag"];
  471. }
  472. // Still no etag?
  473. if (nil == etag) {
  474. TTDWARNING(@"Etag expected, but none found.");
  475. TTDWARNING(@"Here are the headers: %@", headers);
  476. } else {
  477. // At last, we have our etag. Let's cache it.
  478. // First, let's pull out the etag key. This is necessary due to some servers who append
  479. // information to the etag, such as -gzip for a gzipped request. However, the etag
  480. // standard states that etags are defined as a quoted string, and that is all.
  481. NSRange firstQuote = [etag rangeOfString:@"\""];
  482. NSRange secondQuote = [etag rangeOfString: @"\""
  483. options: 0
  484. range: NSMakeRange(firstQuote.location + 1,
  485. etag.length
  486. - (firstQuote.location + 1))];
  487. if (0 == firstQuote.length || 0 == secondQuote.length ||
  488. firstQuote.location == secondQuote.location) {
  489. TTDWARNING(@"Invalid etag format. Unable to find a quoted key.");
  490. } else {
  491. NSRange keyRange;
  492. keyRange.location = firstQuote.location;
  493. keyRange.length = (secondQuote.location - firstQuote.location) + 1;
  494. NSString* etagKey = [etag substringWithRange:keyRange];
  495. TTDCONDITIONLOG(TTDFLAG_ETAGS, @"Response etag: %@", etagKey);
  496. [[TTURLCache sharedCache] storeEtag:etagKey forKey:loader.cacheKey];
  497. }
  498. }
  499. }
  500. [[TTURLCache sharedCache] storeData:data forKey:loader.cacheKey];
  501. }
  502. [loader dispatchLoaded:[NSDate date]];
  503. }
  504. [loader release];
  505. [self loadNextInQueue];
  506. }
  507. ///////////////////////////////////////////////////////////////////////////////////////////////////
  508. - (void) loader:(TTRequestLoader*)loader
  509. didLoadUnmodifiedResponse:(NSHTTPURLResponse*)response {
  510. [loader retain];
  511. [self removeLoader:loader];
  512. NSData* data = nil;
  513. NSError* error = nil;
  514. NSDate* timestamp = nil;
  515. if ([self loadFromCache:loader.urlPath cacheKey:loader.cacheKey
  516. expires:TT_CACHE_EXPIRATION_AGE_NEVER
  517. fromDisk:!_suspended && (loader.cachePolicy & TTURLRequestCachePolicyDisk)
  518. data:&data error:&error timestamp:&timestamp]) {
  519. if (nil == error) {
  520. error = [loader processResponse:response data:data];
  521. }
  522. if (nil == error) {
  523. for (TTURLRequest* request in loader.requests) {
  524. request.respondedFromCache = YES;
  525. }
  526. [loader dispatchLoaded:[NSDate date]];
  527. }
  528. }
  529. if (nil != error) {
  530. [loader dispatchError:error];
  531. }
  532. [loader release];
  533. [self loadNextInQueue];
  534. }
  535. ///////////////////////////////////////////////////////////////////////////////////////////////////
  536. - (void) loader: (TTRequestLoader*)loader
  537. didReceiveAuthenticationChallenge: (NSURLAuthenticationChallenge*) challenge {
  538. TTDCONDITIONLOG(TTDFLAG_URLREQUEST, @"CHALLENGE: %@", challenge);
  539. [loader dispatchAuthenticationChallenge:challenge];
  540. }
  541. ///////////////////////////////////////////////////////////////////////////////////////////////////
  542. - (void)loader:(TTRequestLoader*)loader didFailLoadWithError:(NSError*)error {
  543. TTDCONDITIONLOG(TTDFLAG_URLREQUEST, @"ERROR: %@", error);
  544. [self removeLoader:loader];
  545. [loader dispatchError:error];
  546. [self loadNextInQueue];
  547. }
  548. ///////////////////////////////////////////////////////////////////////////////////////////////////
  549. - (void)loaderDidCancel:(TTRequestLoader*)loader wasLoading:(BOOL)wasLoading {
  550. if (wasLoading) {
  551. [self removeLoader:loader];
  552. [self loadNextInQueue];
  553. } else {
  554. [_loaders removeObjectForKey:loader.cacheKey];
  555. }
  556. }
  557. @end