PageRenderTime 88ms CodeModel.GetById 21ms app.highlight 63ms RepoModel.GetById 1ms app.codeStats 0ms

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

http://macfuse.googlecode.com/
Objective C | 499 lines | 358 code | 79 blank | 62 comment | 72 complexity | 2034353ba6b541a464d1e02b5d0e0392 MD5 | raw file
  1/* Copyright (c) 2010 Google Inc.
  2 *
  3 * Licensed under the Apache License, Version 2.0 (the "License");
  4 * you may not use this file except in compliance with the License.
  5 * You may obtain a copy of the License at
  6 *
  7 *     http://www.apache.org/licenses/LICENSE-2.0
  8 *
  9 * Unless required by applicable law or agreed to in writing, software
 10 * distributed under the License is distributed on an "AS IS" BASIS,
 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 * See the License for the specific language governing permissions and
 13 * limitations under the License.
 14 */
 15
 16//
 17//  GTMHTTPFetcherService.m
 18//
 19
 20#import "GTMHTTPFetcherService.h"
 21
 22@interface GTMHTTPFetcher (ServiceMethods)
 23- (BOOL)beginFetchMayDelay:(BOOL)mayDelay
 24              mayAuthorize:(BOOL)mayAuthorize;
 25@end
 26
 27@interface GTMHTTPFetcherService ()
 28@property (retain, readwrite) NSDictionary *delayedHosts;
 29@property (retain, readwrite) NSDictionary *runningHosts;
 30
 31- (void)detachAuthorizer;
 32@end
 33
 34@implementation GTMHTTPFetcherService
 35
 36@synthesize maxRunningFetchersPerHost = maxRunningFetchersPerHost_,
 37            userAgent = userAgent_,
 38            timeout = timeout_,
 39            delegateQueue = delegateQueue_,
 40            runLoopModes = runLoopModes_,
 41            credential = credential_,
 42            proxyCredential = proxyCredential_,
 43            cookieStorageMethod = cookieStorageMethod_,
 44            shouldFetchInBackground = shouldFetchInBackground_,
 45            fetchHistory = fetchHistory_;
 46
 47- (id)init {
 48  self = [super init];
 49  if (self) {
 50    fetchHistory_ = [[GTMHTTPFetchHistory alloc] init];
 51    delayedHosts_ = [[NSMutableDictionary alloc] init];
 52    runningHosts_ = [[NSMutableDictionary alloc] init];
 53    cookieStorageMethod_ = kGTMHTTPFetcherCookieStorageMethodFetchHistory;
 54
 55    maxRunningFetchersPerHost_ = 10;
 56}
 57  return self;
 58}
 59
 60- (void)dealloc {
 61  [self detachAuthorizer];
 62
 63  [delayedHosts_ release];
 64  [runningHosts_ release];
 65  [fetchHistory_ release];
 66  [userAgent_ release];
 67  [delegateQueue_ release];
 68  [runLoopModes_ release];
 69  [credential_ release];
 70  [proxyCredential_ release];
 71  [authorizer_ release];
 72
 73  [super dealloc];
 74}
 75
 76#pragma mark Generate a new fetcher
 77
 78- (id)fetcherWithRequest:(NSURLRequest *)request
 79            fetcherClass:(Class)fetcherClass {
 80  GTMHTTPFetcher *fetcher = [fetcherClass fetcherWithRequest:request];
 81
 82  fetcher.fetchHistory = self.fetchHistory;
 83  fetcher.delegateQueue = self.delegateQueue;
 84  fetcher.runLoopModes = self.runLoopModes;
 85  fetcher.cookieStorageMethod = self.cookieStorageMethod;
 86  fetcher.credential = self.credential;
 87  fetcher.proxyCredential = self.proxyCredential;
 88  fetcher.shouldFetchInBackground = self.shouldFetchInBackground;
 89  fetcher.authorizer = self.authorizer;
 90  fetcher.service = self;
 91
 92  NSString *userAgent = self.userAgent;
 93  if ([userAgent length] > 0
 94      && [request valueForHTTPHeaderField:@"User-Agent"] == nil) {
 95    [fetcher.mutableRequest setValue:userAgent
 96                  forHTTPHeaderField:@"User-Agent"];
 97  }
 98
 99  NSTimeInterval timeout = self.timeout;
100  if (timeout > 0.0) {
101    [fetcher.mutableRequest setTimeoutInterval:timeout];
102  }
103
104  return fetcher;
105}
106
107- (GTMHTTPFetcher *)fetcherWithRequest:(NSURLRequest *)request {
108  return [self fetcherWithRequest:request
109                     fetcherClass:[GTMHTTPFetcher class]];
110}
111
112- (GTMHTTPFetcher *)fetcherWithURL:(NSURL *)requestURL {
113  return [self fetcherWithRequest:[NSURLRequest requestWithURL:requestURL]];
114}
115
116- (GTMHTTPFetcher *)fetcherWithURLString:(NSString *)requestURLString {
117  return [self fetcherWithURL:[NSURL URLWithString:requestURLString]];
118}
119
120#pragma mark Queue Management
121
122- (void)addRunningFetcher:(GTMHTTPFetcher *)fetcher
123                  forHost:(NSString *)host {
124  // Add to the array of running fetchers for this host, creating the array
125  // if needed
126  NSMutableArray *runningForHost = [runningHosts_ objectForKey:host];
127  if (runningForHost == nil) {
128    runningForHost = [NSMutableArray arrayWithObject:fetcher];
129    [runningHosts_ setObject:runningForHost forKey:host];
130  } else {
131    [runningForHost addObject:fetcher];
132  }
133}
134
135- (void)addDelayedFetcher:(GTMHTTPFetcher *)fetcher
136                  forHost:(NSString *)host {
137  // Add to the array of delayed fetchers for this host, creating the array
138  // if needed
139  NSMutableArray *delayedForHost = [delayedHosts_ objectForKey:host];
140  if (delayedForHost == nil) {
141    delayedForHost = [NSMutableArray arrayWithObject:fetcher];
142    [delayedHosts_ setObject:delayedForHost forKey:host];
143  } else {
144    [delayedForHost addObject:fetcher];
145  }
146}
147
148- (BOOL)isDelayingFetcher:(GTMHTTPFetcher *)fetcher {
149  @synchronized(self) {
150    NSString *host = [[[fetcher mutableRequest] URL] host];
151    NSArray *delayedForHost = [delayedHosts_ objectForKey:host];
152    NSUInteger idx = [delayedForHost indexOfObjectIdenticalTo:fetcher];
153    BOOL isDelayed = (delayedForHost != nil) && (idx != NSNotFound);
154    return isDelayed;
155  }
156}
157
158- (BOOL)fetcherShouldBeginFetching:(GTMHTTPFetcher *)fetcher {
159  // Entry point from the fetcher
160  @synchronized(self) {
161    NSURL *requestURL = [[fetcher mutableRequest] URL];
162    NSString *host = [requestURL host];
163
164    // Addresses "file:///path" case where localhost is the implicit host.
165    if ([host length] == 0 && [requestURL isFileURL]) {
166      host = @"localhost";
167    }
168
169    if ([host length] == 0) {
170#if DEBUG
171      // Data URIs legitimately have no host, reject other hostless URLs.
172      NSAssert1([[requestURL scheme] isEqual:@"data"], @"%@ lacks host", fetcher);
173#endif
174      return YES;
175    }
176
177    NSMutableArray *runningForHost = [runningHosts_ objectForKey:host];
178    if (runningForHost != nil
179        && [runningForHost indexOfObjectIdenticalTo:fetcher] != NSNotFound) {
180#if DEBUG
181      NSAssert1(0, @"%@ was already running", fetcher);
182#endif
183      return YES;
184    }
185
186    // We'll save the host that serves as the key for this fetcher's array
187    // to avoid any chance of the underlying request changing, stranding
188    // the fetcher in the wrong array
189    fetcher.serviceHost = host;
190    fetcher.thread = [NSThread currentThread];
191
192    if (maxRunningFetchersPerHost_ == 0
193        || maxRunningFetchersPerHost_ > [runningForHost count]) {
194      [self addRunningFetcher:fetcher forHost:host];
195      return YES;
196    } else {
197      [self addDelayedFetcher:fetcher forHost:host];
198      return NO;
199    }
200  }
201  return YES;
202}
203
204// Fetcher start and stop methods, invoked on the appropriate thread for
205// the fetcher
206- (void)performSelector:(SEL)sel onStartThreadForFetcher:(GTMHTTPFetcher *)fetcher {
207  NSOperationQueue *delegateQueue = fetcher.delegateQueue;
208  NSThread *thread = fetcher.thread;
209  if (delegateQueue != nil || [thread isEqual:[NSThread currentThread]]) {
210    // The fetcher should run on the thread we're on now, or there's a delegate
211    // queue specified so it doesn't matter what thread the fetcher is started
212    // on, since it will call back on the queue.
213    [self performSelector:sel withObject:fetcher];
214  } else {
215    // Fetcher must run on a specified thread (and that thread must have a
216    // run loop.)
217    [self performSelector:sel
218                 onThread:thread
219               withObject:fetcher
220            waitUntilDone:NO];
221  }
222}
223
224- (void)startFetcherOnCurrentThread:(GTMHTTPFetcher *)fetcher {
225  [fetcher beginFetchMayDelay:NO
226                 mayAuthorize:YES];
227}
228
229- (void)startFetcher:(GTMHTTPFetcher *)fetcher {
230  [self performSelector:@selector(startFetcherOnCurrentThread:)
231    onStartThreadForFetcher:fetcher];
232}
233
234- (void)stopFetcherOnCurrentThread:(GTMHTTPFetcher *)fetcher {
235  [fetcher stopFetching];
236}
237
238- (void)stopFetcher:(GTMHTTPFetcher *)fetcher {
239  [self performSelector:@selector(stopFetcherOnCurrentThread:)
240    onStartThreadForFetcher:fetcher];
241}
242
243- (void)fetcherDidStop:(GTMHTTPFetcher *)fetcher {
244  // Entry point from the fetcher
245  @synchronized(self) {
246    NSString *host = fetcher.serviceHost;
247    if (!host) {
248      // fetcher has been stopped previously
249      return;
250    }
251
252    NSMutableArray *runningForHost = [runningHosts_ objectForKey:host];
253    [runningForHost removeObject:fetcher];
254
255    NSMutableArray *delayedForHost = [delayedHosts_ objectForKey:host];
256    [delayedForHost removeObject:fetcher];
257
258    while ([delayedForHost count] > 0
259           && [runningForHost count] < maxRunningFetchersPerHost_) {
260      // Start another delayed fetcher running, scanning for the minimum
261      // priority value, defaulting to FIFO for equal priorities
262      GTMHTTPFetcher *nextFetcher = nil;
263      for (GTMHTTPFetcher *delayedFetcher in delayedForHost) {
264        if (nextFetcher == nil
265            || delayedFetcher.servicePriority < nextFetcher.servicePriority) {
266          nextFetcher = delayedFetcher;
267        }
268      }
269
270      if (nextFetcher) {
271        [self addRunningFetcher:nextFetcher forHost:host];
272        runningForHost = [runningHosts_ objectForKey:host];
273
274        [delayedForHost removeObjectIdenticalTo:nextFetcher];
275        [self startFetcher:nextFetcher];
276      }
277    }
278
279    if ([runningForHost count] == 0) {
280      // None left; remove the empty array
281      [runningHosts_ removeObjectForKey:host];
282    }
283
284    if ([delayedForHost count] == 0) {
285      [delayedHosts_ removeObjectForKey:host];
286    }
287
288    // The fetcher is no longer in the running or the delayed array,
289    // so remove its host and thread properties
290    fetcher.serviceHost = nil;
291    fetcher.thread = nil;
292  }
293}
294
295- (NSUInteger)numberOfFetchers {
296  @synchronized(self) {
297    NSUInteger running = [self numberOfRunningFetchers];
298    NSUInteger delayed = [self numberOfDelayedFetchers];
299    return running + delayed;
300  }
301}
302
303- (NSUInteger)numberOfRunningFetchers {
304  @synchronized(self) {
305    NSUInteger sum = 0;
306    for (NSString *host in runningHosts_) {
307      NSArray *fetchers = [runningHosts_ objectForKey:host];
308      sum += [fetchers count];
309    }
310    return sum;
311  }
312}
313
314- (NSUInteger)numberOfDelayedFetchers {
315  @synchronized(self) {
316    NSUInteger sum = 0;
317    for (NSString *host in delayedHosts_) {
318      NSArray *fetchers = [delayedHosts_ objectForKey:host];
319      sum += [fetchers count];
320    }
321    return sum;
322  }
323}
324
325- (NSArray *)issuedFetchersWithRequestURL:(NSURL *)requestURL {
326  @synchronized(self) {
327    NSMutableArray *array = nil;
328    NSString *host = [requestURL host];
329    if ([host length] == 0) return nil;
330
331    NSURL *absRequestURL = [requestURL absoluteURL];
332
333    NSArray *runningForHost = [runningHosts_ objectForKey:host];
334    for (GTMHTTPFetcher *fetcher in runningForHost) {
335      NSURL *fetcherURL = [[[fetcher mutableRequest] URL] absoluteURL];
336      if ([fetcherURL isEqual:absRequestURL]) {
337        if (array == nil) {
338          array = [NSMutableArray array];
339        }
340        [array addObject:fetcher];
341      }
342    }
343
344    NSArray *delayedForHost = [delayedHosts_ objectForKey:host];
345    for (GTMHTTPFetcher *fetcher in delayedForHost) {
346      NSURL *fetcherURL = [[[fetcher mutableRequest] URL] absoluteURL];
347      if ([fetcherURL isEqual:absRequestURL]) {
348        if (array == nil) {
349          array = [NSMutableArray array];
350        }
351        [array addObject:fetcher];
352      }
353    }
354    return array;
355  }
356}
357
358- (void)stopAllFetchers {
359  @synchronized(self) {
360    // Remove fetchers from the delayed list to avoid fetcherDidStop: from
361    // starting more fetchers running as a side effect of stopping one
362    NSArray *delayedForHosts = [delayedHosts_ allValues];
363    [delayedHosts_ removeAllObjects];
364
365    for (NSArray *delayedForHost in delayedForHosts) {
366      for (GTMHTTPFetcher *fetcher in delayedForHost) {
367        [self stopFetcher:fetcher];
368      }
369    }
370
371    NSArray *runningForHosts = [runningHosts_ allValues];
372    [runningHosts_ removeAllObjects];
373
374    for (NSArray *runningForHost in runningForHosts) {
375      for (GTMHTTPFetcher *fetcher in runningForHost) {
376        [self stopFetcher:fetcher];
377      }
378    }
379  }
380}
381
382#pragma mark Fetch History Settings
383
384// Turn on data caching to receive a copy of previously-retrieved objects.
385// Otherwise, fetches may return status 304 (No Change) rather than actual data
386- (void)setShouldCacheETaggedData:(BOOL)flag {
387  self.fetchHistory.shouldCacheETaggedData = flag;
388}
389
390- (BOOL)shouldCacheETaggedData {
391  return self.fetchHistory.shouldCacheETaggedData;
392}
393
394- (void)setETaggedDataCacheCapacity:(NSUInteger)totalBytes {
395  self.fetchHistory.memoryCapacity = totalBytes;
396}
397
398- (NSUInteger)ETaggedDataCacheCapacity {
399  return self.fetchHistory.memoryCapacity;
400}
401
402- (void)setShouldRememberETags:(BOOL)flag {
403  self.fetchHistory.shouldRememberETags = flag;
404}
405
406- (BOOL)shouldRememberETags {
407  return self.fetchHistory.shouldRememberETags;
408}
409
410// reset the ETag cache to avoid getting a Not Modified status
411// based on prior queries
412- (void)clearETaggedDataCache {
413  [self.fetchHistory clearETaggedDataCache];
414}
415
416- (void)clearHistory {
417  [self clearETaggedDataCache];
418  [self.fetchHistory removeAllCookies];
419}
420
421#pragma mark Synchronous Wait for Unit Testing
422
423- (void)waitForCompletionOfAllFetchersWithTimeout:(NSTimeInterval)timeoutInSeconds {
424  NSDate* giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds];
425  BOOL isMainThread = [NSThread isMainThread];
426
427  while ([self numberOfFetchers] > 0
428         && [giveUpDate timeIntervalSinceNow] > 0) {
429    // Run the current run loop 1/1000 of a second to give the networking
430    // code a chance to work
431    if (isMainThread || delegateQueue_ == nil) {
432      NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:0.001];
433      [[NSRunLoop currentRunLoop] runUntilDate:stopDate];
434    } else {
435      // Sleep on the delegate queue's background thread.
436      [NSThread sleepForTimeInterval:0.001];
437    }
438  }
439}
440
441#pragma mark Accessors
442
443- (NSDictionary *)runningHosts {
444  return runningHosts_;
445}
446
447- (void)setRunningHosts:(NSDictionary *)dict {
448  [runningHosts_ autorelease];
449  runningHosts_ = [dict mutableCopy];
450}
451
452- (NSDictionary *)delayedHosts {
453  return delayedHosts_;
454}
455
456- (void)setDelayedHosts:(NSDictionary *)dict {
457  [delayedHosts_ autorelease];
458  delayedHosts_ = [dict mutableCopy];
459}
460
461- (id <GTMFetcherAuthorizationProtocol>)authorizer {
462  return authorizer_;
463}
464
465- (void)setAuthorizer:(id <GTMFetcherAuthorizationProtocol>)obj {
466  if (obj != authorizer_) {
467    [self detachAuthorizer];
468  }
469
470  [authorizer_ autorelease];
471  authorizer_ = [obj retain];
472
473  // Use the fetcher service for the authorization fetches if the auth
474  // object supports fetcher services
475  if ([authorizer_ respondsToSelector:@selector(setFetcherService:)]) {
476    [authorizer_ setFetcherService:self];
477  }
478}
479
480- (void)detachAuthorizer {
481  // This method is called by the fetcher service's dealloc and setAuthorizer:
482  // methods; do not override.
483  //
484  // The fetcher service retains the authorizer, and the authorizer has a
485  // weak pointer to the fetcher service (a non-zeroing pointer for
486  // compatibility with iOS 4 and Mac OS X 10.5/10.6.)
487  //
488  // When this fetcher service no longer uses the authorizer, we want to remove
489  // the authorizer's dependence on the fetcher service.  Authorizers can still
490  // function without a fetcher service.
491  if ([authorizer_ respondsToSelector:@selector(fetcherService)]) {
492    GTMHTTPFetcherService *authFS = [authorizer_ fetcherService];
493    if (authFS == self) {
494      [authorizer_ setFetcherService:nil];
495    }
496  }
497}
498
499@end