diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2020-08-31 23:07:20 +0200 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2020-08-31 23:07:20 +0200 |
| commit | 22721a013bdd10d5eb395ba18453585f5f3f1f7f (patch) | |
| tree | 5a920e31d6026ed5dc55265e5fd057febccc50e3 /StoneIsland/platforms/ios/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m | |
| parent | d22d51a1ae49680015326857360eb699f31efced (diff) | |
rebuild the ios platform and the plugins
Diffstat (limited to 'StoneIsland/platforms/ios/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m')
| -rw-r--r-- | StoneIsland/platforms/ios/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m | 1381 |
1 files changed, 1381 insertions, 0 deletions
diff --git a/StoneIsland/platforms/ios/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m b/StoneIsland/platforms/ios/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m new file mode 100644 index 00000000..f9942c01 --- /dev/null +++ b/StoneIsland/platforms/ios/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m @@ -0,0 +1,1381 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "GTMSessionFetcherService.h" + +NSString *const kGTMSessionFetcherServiceSessionBecameInvalidNotification + = @"kGTMSessionFetcherServiceSessionBecameInvalidNotification"; +NSString *const kGTMSessionFetcherServiceSessionKey + = @"kGTMSessionFetcherServiceSessionKey"; + +#if !GTMSESSION_BUILD_COMBINED_SOURCES +@interface GTMSessionFetcher (ServiceMethods) +- (BOOL)beginFetchMayDelay:(BOOL)mayDelay + mayAuthorize:(BOOL)mayAuthorize; +@end +#endif // !GTMSESSION_BUILD_COMBINED_SOURCES + +@interface GTMSessionFetcherService () + +@property(atomic, strong, readwrite) NSDictionary *delayedFetchersByHost; +@property(atomic, strong, readwrite) NSDictionary *runningFetchersByHost; + +@end + +// Since NSURLSession doesn't support a separate delegate per task (!), instances of this +// class serve as a session delegate trampoline. +// +// This class maps a session's tasks to fetchers, and resends delegate messages to the task's +// fetcher. +@interface GTMSessionFetcherSessionDelegateDispatcher : NSObject<NSURLSessionDelegate> + +// The session for the tasks in this dispatcher's task-to-fetcher map. +@property(atomic) NSURLSession *session; + +// The timer interval for invalidating a session that has no active tasks. +@property(atomic) NSTimeInterval discardInterval; + +// The current discard timer. +@property(atomic, readonly) NSTimer *discardTimer; + + +- (instancetype)initWithParentService:(GTMSessionFetcherService *)parentService + sessionDiscardInterval:(NSTimeInterval)discardInterval; + +- (void)setFetcher:(GTMSessionFetcher *)fetcher + forTask:(NSURLSessionTask *)task; +- (void)removeFetcher:(GTMSessionFetcher *)fetcher; + +// Before using a session, tells the delegate dispatcher to stop the discard timer. +- (void)startSessionUsage; + +// When abandoning a delegate dispatcher, we want to avoid the session retaining +// the delegate after tasks complete. +- (void)abandon; + +@end + + +@implementation GTMSessionFetcherService { + NSMutableDictionary *_delayedFetchersByHost; + NSMutableDictionary *_runningFetchersByHost; + NSUInteger _maxRunningFetchersPerHost; + + // When this ivar is nil, the service will not reuse sessions. + GTMSessionFetcherSessionDelegateDispatcher *_delegateDispatcher; + + // Fetchers will wait on this if another fetcher is creating the shared NSURLSession. + dispatch_semaphore_t _sessionCreationSemaphore; + + dispatch_queue_t _callbackQueue; + NSOperationQueue *_delegateQueue; + NSHTTPCookieStorage *_cookieStorage; + NSString *_userAgent; + NSTimeInterval _timeout; + + NSURLCredential *_credential; // Username & password. + NSURLCredential *_proxyCredential; // Credential supplied to proxy servers. + + NSInteger _cookieStorageMethod; + + id<GTMFetcherAuthorizationProtocol> _authorizer; + + // For waitForCompletionOfAllFetchersWithTimeout: we need to wait on stopped fetchers since + // they've not yet finished invoking their queued callbacks. This array is nil except when + // waiting on fetchers. + NSMutableArray *_stoppedFetchersToWaitFor; + + // For fetchers that enqueued their callbacks before stopAllFetchers was called on the service, + // set a barrier so the callbacks know to bail out. + NSDate *_stoppedAllFetchersDate; +} + +@synthesize maxRunningFetchersPerHost = _maxRunningFetchersPerHost, + configuration = _configuration, + configurationBlock = _configurationBlock, + cookieStorage = _cookieStorage, + userAgent = _userAgent, + challengeBlock = _challengeBlock, + credential = _credential, + proxyCredential = _proxyCredential, + allowedInsecureSchemes = _allowedInsecureSchemes, + allowLocalhostRequest = _allowLocalhostRequest, + allowInvalidServerCertificates = _allowInvalidServerCertificates, + retryEnabled = _retryEnabled, + retryBlock = _retryBlock, + maxRetryInterval = _maxRetryInterval, + minRetryInterval = _minRetryInterval, + metricsCollectionBlock = _metricsCollectionBlock, + properties = _properties, + unusedSessionTimeout = _unusedSessionTimeout, + testBlock = _testBlock; + +#if GTM_BACKGROUND_TASK_FETCHING +@synthesize skipBackgroundTask = _skipBackgroundTask; +#endif + +- (instancetype)init { + self = [super init]; + if (self) { + _delayedFetchersByHost = [[NSMutableDictionary alloc] init]; + _runningFetchersByHost = [[NSMutableDictionary alloc] init]; + _maxRunningFetchersPerHost = 10; + _cookieStorageMethod = -1; + _unusedSessionTimeout = 60.0; + _delegateDispatcher = + [[GTMSessionFetcherSessionDelegateDispatcher alloc] initWithParentService:self + sessionDiscardInterval:_unusedSessionTimeout]; + _callbackQueue = dispatch_get_main_queue(); + + _delegateQueue = [[NSOperationQueue alloc] init]; + _delegateQueue.maxConcurrentOperationCount = 1; + _delegateQueue.name = @"com.google.GTMSessionFetcher.NSURLSessionDelegateQueue"; + + _sessionCreationSemaphore = dispatch_semaphore_create(1); + + // Starting with the SDKs for OS X 10.11/iOS 9, the service has a default useragent. + // Apps can remove this and get the default system "CFNetwork" useragent by setting the + // fetcher service's userAgent property to nil. +#if (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_11) \ + || (TARGET_OS_IPHONE && defined(__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0) + _userAgent = GTMFetcherStandardUserAgentString(nil); +#endif + } + return self; +} + +- (void)dealloc { + [self detachAuthorizer]; + [_delegateDispatcher abandon]; +} + +#pragma mark Generate a new fetcher + +// Clients may override this method. Clients should not override any other library methods. +- (id)fetcherWithRequest:(NSURLRequest *)request + fetcherClass:(Class)fetcherClass { + GTMSessionFetcher *fetcher = [[fetcherClass alloc] initWithRequest:request + configuration:self.configuration]; + fetcher.callbackQueue = self.callbackQueue; + fetcher.sessionDelegateQueue = self.sessionDelegateQueue; + fetcher.challengeBlock = self.challengeBlock; + fetcher.credential = self.credential; + fetcher.proxyCredential = self.proxyCredential; + fetcher.authorizer = self.authorizer; + fetcher.cookieStorage = self.cookieStorage; + fetcher.allowedInsecureSchemes = self.allowedInsecureSchemes; + fetcher.allowLocalhostRequest = self.allowLocalhostRequest; + fetcher.allowInvalidServerCertificates = self.allowInvalidServerCertificates; + fetcher.configurationBlock = self.configurationBlock; + fetcher.retryEnabled = self.retryEnabled; + fetcher.retryBlock = self.retryBlock; + fetcher.maxRetryInterval = self.maxRetryInterval; + fetcher.minRetryInterval = self.minRetryInterval; + if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)) { + fetcher.metricsCollectionBlock = self.metricsCollectionBlock; + } + fetcher.properties = self.properties; + fetcher.service = self; + if (self.cookieStorageMethod >= 0) { + [fetcher setCookieStorageMethod:self.cookieStorageMethod]; + } + +#if GTM_BACKGROUND_TASK_FETCHING + fetcher.skipBackgroundTask = self.skipBackgroundTask; +#endif + + NSString *userAgent = self.userAgent; + if (userAgent.length > 0 + && [request valueForHTTPHeaderField:@"User-Agent"] == nil) { + [fetcher setRequestValue:userAgent + forHTTPHeaderField:@"User-Agent"]; + } + fetcher.testBlock = self.testBlock; + + return fetcher; +} + +- (GTMSessionFetcher *)fetcherWithRequest:(NSURLRequest *)request { + return [self fetcherWithRequest:request + fetcherClass:[GTMSessionFetcher class]]; +} + +- (GTMSessionFetcher *)fetcherWithURL:(NSURL *)requestURL { + return [self fetcherWithRequest:[NSURLRequest requestWithURL:requestURL]]; +} + +- (GTMSessionFetcher *)fetcherWithURLString:(NSString *)requestURLString { + NSURL *url = [NSURL URLWithString:requestURLString]; + return [self fetcherWithURL:url]; +} + +// Returns a session for the fetcher's host, or nil. +- (NSURLSession *)session { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSURLSession *session = _delegateDispatcher.session; + return session; + } +} + +// Returns a session for the fetcher's host, or nil. For shared sessions, this +// waits on a semaphore, blocking other fetchers while the caller creates the +// session if needed. +- (NSURLSession *)sessionForFetcherCreation { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + if (!_delegateDispatcher) { + // This fetcher is creating a non-shared session, so skip the semaphore usage. + return nil; + } + } + + // Wait if another fetcher is currently creating a session; avoid waiting + // inside the @synchronized block, as that can deadlock. + dispatch_semaphore_wait(_sessionCreationSemaphore, DISPATCH_TIME_FOREVER); + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // Before getting the NSURLSession for task creation, it is + // important to invalidate and nil out the session discard timer; otherwise + // the session can be invalidated between when it is returned to the + // fetcher, and when the fetcher attempts to create its NSURLSessionTask. + [_delegateDispatcher startSessionUsage]; + + NSURLSession *session = _delegateDispatcher.session; + if (session) { + // The calling fetcher will receive a preexisting session, so + // we can allow other fetchers to create a session. + dispatch_semaphore_signal(_sessionCreationSemaphore); + } else { + // No existing session was obtained, so the calling fetcher will create the session; + // it *must* invoke fetcherDidCreateSession: to signal the dispatcher's semaphore after + // the session has been created (or fails to be created) to avoid a hang. + } + return session; + } +} + +- (id<NSURLSessionDelegate>)sessionDelegate { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _delegateDispatcher; + } +} + +#pragma mark Queue Management + +- (void)addRunningFetcher:(GTMSessionFetcher *)fetcher + forHost:(NSString *)host { + // Add to the array of running fetchers for this host, creating the array if needed. + NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host]; + if (runningForHost == nil) { + runningForHost = [NSMutableArray arrayWithObject:fetcher]; + [_runningFetchersByHost setObject:runningForHost forKey:host]; + } else { + [runningForHost addObject:fetcher]; + } +} + +- (void)addDelayedFetcher:(GTMSessionFetcher *)fetcher + forHost:(NSString *)host { + // Add to the array of delayed fetchers for this host, creating the array if needed. + NSMutableArray *delayedForHost = [_delayedFetchersByHost objectForKey:host]; + if (delayedForHost == nil) { + delayedForHost = [NSMutableArray arrayWithObject:fetcher]; + [_delayedFetchersByHost setObject:delayedForHost forKey:host]; + } else { + [delayedForHost addObject:fetcher]; + } +} + +- (BOOL)isDelayingFetcher:(GTMSessionFetcher *)fetcher { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSString *host = fetcher.request.URL.host; + if (host == nil) { + return NO; + } + NSArray *delayedForHost = [_delayedFetchersByHost objectForKey:host]; + NSUInteger idx = [delayedForHost indexOfObjectIdenticalTo:fetcher]; + BOOL isDelayed = (delayedForHost != nil) && (idx != NSNotFound); + return isDelayed; + } +} + +- (BOOL)fetcherShouldBeginFetching:(GTMSessionFetcher *)fetcher { + // Entry point from the fetcher + NSURL *requestURL = fetcher.request.URL; + NSString *host = requestURL.host; + + // Addresses "file:///path" case where localhost is the implicit host. + if (host.length == 0 && [requestURL isFileURL]) { + host = @"localhost"; + } + + if (host.length == 0) { + // Data URIs legitimately have no host, reject other hostless URLs. + GTMSESSION_ASSERT_DEBUG([[requestURL scheme] isEqual:@"data"], @"%@ lacks host", fetcher); + return YES; + } + + BOOL shouldBeginResult; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host]; + if (runningForHost != nil + && [runningForHost indexOfObjectIdenticalTo:fetcher] != NSNotFound) { + GTMSESSION_ASSERT_DEBUG(NO, @"%@ was already running", fetcher); + return YES; + } + + BOOL shouldRunNow = (fetcher.usingBackgroundSession + || _maxRunningFetchersPerHost == 0 + || _maxRunningFetchersPerHost > + [[self class] numberOfNonBackgroundSessionFetchers:runningForHost]); + if (shouldRunNow) { + [self addRunningFetcher:fetcher forHost:host]; + shouldBeginResult = YES; + } else { + [self addDelayedFetcher:fetcher forHost:host]; + shouldBeginResult = NO; + } + } // @synchronized(self) + + // We'll save the host that serves as the key for this fetcher's array + // to avoid any chance of the underlying request changing, stranding + // the fetcher in the wrong array + fetcher.serviceHost = host; + + return shouldBeginResult; +} + +- (void)startFetcher:(GTMSessionFetcher *)fetcher { + [fetcher beginFetchMayDelay:NO + mayAuthorize:YES]; +} + +// Internal utility. Returns a fetcher's delegate if it's a dispatcher, or nil if the fetcher +// is its own delegate (possibly via proxy) and has no dispatcher. +- (GTMSessionFetcherSessionDelegateDispatcher *)delegateDispatcherForFetcher:(GTMSessionFetcher *)fetcher { + GTMSessionCheckNotSynchronized(self); + + NSURLSession *fetcherSession = fetcher.session; + if (fetcherSession) { + id<NSURLSessionDelegate> fetcherDelegate = fetcherSession.delegate; + // If the delegate is non-nil and claims to be a GTMSessionFetcher, there is no dispatcher; + // assume the fetcher is the delegate or has been proxied (some third-party frameworks + // are known to swizzle NSURLSession to proxy its delegate). + BOOL hasDispatcher = (fetcherDelegate != nil && + ![fetcherDelegate isKindOfClass:[GTMSessionFetcher class]]); + if (hasDispatcher) { + GTMSESSION_ASSERT_DEBUG([fetcherDelegate isKindOfClass:[GTMSessionFetcherSessionDelegateDispatcher class]], + @"Fetcher delegate class: %@", [fetcherDelegate class]); + return (GTMSessionFetcherSessionDelegateDispatcher *)fetcherDelegate; + } + } + return nil; +} + +- (void)fetcherDidCreateSession:(GTMSessionFetcher *)fetcher { + if (fetcher.canShareSession) { + NSURLSession *fetcherSession = fetcher.session; + GTMSESSION_ASSERT_DEBUG(fetcherSession != nil, @"Fetcher missing its session: %@", fetcher); + + GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher = + [self delegateDispatcherForFetcher:fetcher]; + if (delegateDispatcher) { + GTMSESSION_ASSERT_DEBUG(delegateDispatcher.session == nil, + @"Fetcher made an extra session: %@", fetcher); + + // Save this fetcher's session. + delegateDispatcher.session = fetcherSession; + + // Allow other fetchers to request this session now. + dispatch_semaphore_signal(_sessionCreationSemaphore); + } + } +} + +- (void)fetcherDidBeginFetching:(GTMSessionFetcher *)fetcher { + // If this fetcher has a separate delegate with a shared session, then + // this fetcher should be added to the delegate's map of tasks to fetchers. + GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher = + [self delegateDispatcherForFetcher:fetcher]; + if (delegateDispatcher) { + GTMSESSION_ASSERT_DEBUG(fetcher.canShareSession, + @"Inappropriate shared session: %@", fetcher); + + // There should already be a session, from this or a previous fetcher. + // + // Sanity check that the fetcher's session is the delegate's shared session. + NSURLSession *sharedSession = delegateDispatcher.session; + NSURLSession *fetcherSession = fetcher.session; + GTMSESSION_ASSERT_DEBUG(sharedSession != nil, @"Missing delegate session: %@", fetcher); + GTMSESSION_ASSERT_DEBUG(fetcherSession == sharedSession, + @"Inconsistent session: %@ %@ (shared: %@)", + fetcher, fetcherSession, sharedSession); + + if (sharedSession != nil && fetcherSession == sharedSession) { + NSURLSessionTask *task = fetcher.sessionTask; + GTMSESSION_ASSERT_DEBUG(task != nil, @"Missing session task: %@", fetcher); + + if (task) { + [delegateDispatcher setFetcher:fetcher + forTask:task]; + } + } + } +} + +- (void)stopFetcher:(GTMSessionFetcher *)fetcher { + [fetcher stopFetching]; +} + +- (void)fetcherDidStop:(GTMSessionFetcher *)fetcher { + // Entry point from the fetcher + NSString *host = fetcher.serviceHost; + if (!host) { + // fetcher has been stopped previously + return; + } + + // This removeFetcher: invocation is a fallback; typically, fetchers are removed from the task + // map when the task completes. + GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher = + [self delegateDispatcherForFetcher:fetcher]; + [delegateDispatcher removeFetcher:fetcher]; + + NSMutableArray *fetchersToStart; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // If a test is waiting for all fetchers to stop, it needs to wait for this one + // to invoke its callbacks on the callback queue. + [_stoppedFetchersToWaitFor addObject:fetcher]; + + NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host]; + [runningForHost removeObject:fetcher]; + + NSMutableArray *delayedForHost = [_delayedFetchersByHost objectForKey:host]; + [delayedForHost removeObject:fetcher]; + + while (delayedForHost.count > 0 + && [[self class] numberOfNonBackgroundSessionFetchers:runningForHost] + < _maxRunningFetchersPerHost) { + // Start another delayed fetcher running, scanning for the minimum + // priority value, defaulting to FIFO for equal priorities + GTMSessionFetcher *nextFetcher = nil; + for (GTMSessionFetcher *delayedFetcher in delayedForHost) { + if (nextFetcher == nil + || delayedFetcher.servicePriority < nextFetcher.servicePriority) { + nextFetcher = delayedFetcher; + } + } + + if (nextFetcher) { + [self addRunningFetcher:nextFetcher forHost:host]; + runningForHost = [_runningFetchersByHost objectForKey:host]; + + [delayedForHost removeObjectIdenticalTo:nextFetcher]; + + if (!fetchersToStart) { + fetchersToStart = [NSMutableArray array]; + } + [fetchersToStart addObject:nextFetcher]; + } + } + + if (runningForHost.count == 0) { + // None left; remove the empty array + [_runningFetchersByHost removeObjectForKey:host]; + } + + if (delayedForHost.count == 0) { + [_delayedFetchersByHost removeObjectForKey:host]; + } + } // @synchronized(self) + + // Start fetchers outside of the synchronized block to avoid a deadlock. + for (GTMSessionFetcher *nextFetcher in fetchersToStart) { + [self startFetcher:nextFetcher]; + } + + // The fetcher is no longer in the running or the delayed array, + // so remove its host and thread properties + fetcher.serviceHost = nil; +} + +- (NSUInteger)numberOfFetchers { + NSUInteger running = [self numberOfRunningFetchers]; + NSUInteger delayed = [self numberOfDelayedFetchers]; + return running + delayed; +} + +- (NSUInteger)numberOfRunningFetchers { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSUInteger sum = 0; + for (NSString *host in _runningFetchersByHost) { + NSArray *fetchers = [_runningFetchersByHost objectForKey:host]; + sum += fetchers.count; + } + return sum; + } +} + +- (NSUInteger)numberOfDelayedFetchers { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSUInteger sum = 0; + for (NSString *host in _delayedFetchersByHost) { + NSArray *fetchers = [_delayedFetchersByHost objectForKey:host]; + sum += fetchers.count; + } + return sum; + } +} + +- (NSArray *)issuedFetchers { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSMutableArray *allFetchers = [NSMutableArray array]; + void (^accumulateFetchers)(id, id, BOOL *) = ^(NSString *host, + NSArray *fetchersForHost, + BOOL *stop) { + [allFetchers addObjectsFromArray:fetchersForHost]; + }; + [_runningFetchersByHost enumerateKeysAndObjectsUsingBlock:accumulateFetchers]; + [_delayedFetchersByHost enumerateKeysAndObjectsUsingBlock:accumulateFetchers]; + + GTMSESSION_ASSERT_DEBUG(allFetchers.count == [NSSet setWithArray:allFetchers].count, + @"Fetcher appears multiple times\n running: %@\n delayed: %@", + _runningFetchersByHost, _delayedFetchersByHost); + + return allFetchers.count > 0 ? allFetchers : nil; + } +} + +- (NSArray *)issuedFetchersWithRequestURL:(NSURL *)requestURL { + NSString *host = requestURL.host; + if (host.length == 0) return nil; + + NSURL *targetURL = [requestURL absoluteURL]; + + NSArray *allFetchers = [self issuedFetchers]; + NSIndexSet *indexes = [allFetchers indexesOfObjectsPassingTest:^BOOL(GTMSessionFetcher *fetcher, + NSUInteger idx, + BOOL *stop) { + NSURL *fetcherURL = [fetcher.request.URL absoluteURL]; + return [fetcherURL isEqual:targetURL]; + }]; + + NSArray *result = nil; + if (indexes.count > 0) { + result = [allFetchers objectsAtIndexes:indexes]; + } + return result; +} + +- (void)stopAllFetchers { + NSArray *delayedFetchersByHost; + NSArray *runningFetchersByHost; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // Set the time barrier so fetchers know not to call back even if + // the stop calls below occur after the fetchers naturally + // stopped and so were removed from _runningFetchersByHost, + // but while the callbacks were already enqueued before stopAllFetchers + // was invoked. + _stoppedAllFetchersDate = [[NSDate alloc] init]; + + // Remove fetchers from the delayed list to avoid fetcherDidStop: from + // starting more fetchers running as a side effect of stopping one + delayedFetchersByHost = _delayedFetchersByHost.allValues; + [_delayedFetchersByHost removeAllObjects]; + + runningFetchersByHost = _runningFetchersByHost.allValues; + [_runningFetchersByHost removeAllObjects]; + } + + for (NSArray *delayedForHost in delayedFetchersByHost) { + for (GTMSessionFetcher *fetcher in delayedForHost) { + [self stopFetcher:fetcher]; + } + } + + for (NSArray *runningForHost in runningFetchersByHost) { + for (GTMSessionFetcher *fetcher in runningForHost) { + [self stopFetcher:fetcher]; + } + } +} + +- (NSDate *)stoppedAllFetchersDate { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _stoppedAllFetchersDate; + } +} + +#pragma mark Accessors + +- (BOOL)reuseSession { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _delegateDispatcher != nil; + } +} + +- (void)setReuseSession:(BOOL)shouldReuse { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + BOOL wasReusing = (_delegateDispatcher != nil); + if (shouldReuse != wasReusing) { + [self abandonDispatcher]; + if (shouldReuse) { + _delegateDispatcher = + [[GTMSessionFetcherSessionDelegateDispatcher alloc] initWithParentService:self + sessionDiscardInterval:_unusedSessionTimeout]; + } else { + _delegateDispatcher = nil; + } + } + } +} + +- (void)resetSession { + GTMSessionCheckNotSynchronized(self); + dispatch_semaphore_wait(_sessionCreationSemaphore, DISPATCH_TIME_FOREVER); + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + [self resetSessionInternal]; + } + + dispatch_semaphore_signal(_sessionCreationSemaphore); +} + +- (void)resetSessionInternal { + GTMSessionCheckSynchronized(self); + + // The old dispatchers may be retained as delegates of any ongoing sessions by those sessions. + if (_delegateDispatcher) { + [self abandonDispatcher]; + _delegateDispatcher = + [[GTMSessionFetcherSessionDelegateDispatcher alloc] initWithParentService:self + sessionDiscardInterval:_unusedSessionTimeout]; + } +} + +- (void)resetSessionForDispatcherDiscardTimer:(NSTimer *)timer { + GTMSessionCheckNotSynchronized(self); + + dispatch_semaphore_wait(_sessionCreationSemaphore, DISPATCH_TIME_FOREVER); + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_delegateDispatcher.discardTimer == timer) { + // If the delegate dispatcher's current discardTimer is the same object as the timer + // that fired, no fetcher has recently attempted to start using the session by calling + // startSessionUsage, which invalidates and nils out the timer. + [self resetSessionInternal]; + } else { + // A fetcher has invalidated the timer between its triggering and now, potentially + // meaning a fetcher has requested access to the NSURLSession, and may be in the process + // of starting a new task. The dispatcher should not be abandoned, as this can lead + // to a race condition between calling -finishTasksAndInvalidate on the NSURLSession + // and the fetcher attempting to create a new task. + } + } + + dispatch_semaphore_signal(_sessionCreationSemaphore); +} + +- (NSTimeInterval)unusedSessionTimeout { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _unusedSessionTimeout; + } +} + +- (void)setUnusedSessionTimeout:(NSTimeInterval)timeout { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _unusedSessionTimeout = timeout; + _delegateDispatcher.discardInterval = timeout; + } +} + +// This method should be called inside of @synchronized(self) +- (void)abandonDispatcher { + GTMSessionCheckSynchronized(self); + [_delegateDispatcher abandon]; +} + +- (NSDictionary *)runningFetchersByHost { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return [_runningFetchersByHost copy]; + } +} + +- (void)setRunningFetchersByHost:(NSDictionary *)dict { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _runningFetchersByHost = [dict mutableCopy]; + } +} + +- (NSDictionary *)delayedFetchersByHost { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return [_delayedFetchersByHost copy]; + } +} + +- (void)setDelayedFetchersByHost:(NSDictionary *)dict { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _delayedFetchersByHost = [dict mutableCopy]; + } +} + +- (id<GTMFetcherAuthorizationProtocol>)authorizer { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _authorizer; + } +} + +- (void)setAuthorizer:(id<GTMFetcherAuthorizationProtocol>)obj { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (obj != _authorizer) { + [self detachAuthorizer]; + } + + _authorizer = obj; + } + + // Use the fetcher service for the authorization fetches if the auth + // object supports fetcher services + if ([obj respondsToSelector:@selector(setFetcherService:)]) { +#if GTM_USE_SESSION_FETCHER + [obj setFetcherService:self]; +#else + [obj setFetcherService:(id)self]; +#endif + } +} + +// This should be called inside a @synchronized(self) block except during dealloc. +- (void)detachAuthorizer { + // This method is called by the fetcher service's dealloc and setAuthorizer: + // methods; do not override. + // + // The fetcher service retains the authorizer, and the authorizer has a + // weak pointer to the fetcher service (a non-zeroing pointer for + // compatibility with iOS 4 and Mac OS X 10.5/10.6.) + // + // When this fetcher service no longer uses the authorizer, we want to remove + // the authorizer's dependence on the fetcher service. Authorizers can still + // function without a fetcher service. + if ([_authorizer respondsToSelector:@selector(fetcherService)]) { + id authFetcherService = [_authorizer fetcherService]; + if (authFetcherService == self) { + [_authorizer setFetcherService:nil]; + } + } +} + +- (dispatch_queue_t GTM_NONNULL_TYPE)callbackQueue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _callbackQueue; + } // @synchronized(self) +} + +- (void)setCallbackQueue:(dispatch_queue_t GTM_NULLABLE_TYPE)queue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _callbackQueue = queue ?: dispatch_get_main_queue(); + } // @synchronized(self) +} + +- (NSOperationQueue * GTM_NONNULL_TYPE)sessionDelegateQueue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _delegateQueue; + } // @synchronized(self) +} + +- (void)setSessionDelegateQueue:(NSOperationQueue * GTM_NULLABLE_TYPE)queue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _delegateQueue = queue ?: [NSOperationQueue mainQueue]; + } // @synchronized(self) +} + +- (NSOperationQueue *)delegateQueue { + // Provided for compatibility with the old fetcher service. The gtm-oauth2 code respects + // any custom delegate queue for calling the app. + return nil; +} + ++ (NSUInteger)numberOfNonBackgroundSessionFetchers:(NSArray *)fetchers { + NSUInteger sum = 0; + for (GTMSessionFetcher *fetcher in fetchers) { + if (!fetcher.usingBackgroundSession) { + ++sum; + } + } + return sum; +} + +@end + +@implementation GTMSessionFetcherService (TestingSupport) + ++ (instancetype)mockFetcherServiceWithFakedData:(NSData *)fakedDataOrNil + fakedError:(NSError *)fakedErrorOrNil { +#if !GTM_DISABLE_FETCHER_TEST_BLOCK + NSURL *url = [NSURL URLWithString:@"http://example.invalid"]; + NSHTTPURLResponse *fakedResponse = + [[NSHTTPURLResponse alloc] initWithURL:url + statusCode:(fakedErrorOrNil ? 500 : 200) + HTTPVersion:@"HTTP/1.1" + headerFields:nil]; + return [self mockFetcherServiceWithFakedData:fakedDataOrNil + fakedResponse:fakedResponse + fakedError:fakedErrorOrNil]; +#else + GTMSESSION_ASSERT_DEBUG(0, @"Test blocks disabled"); + return nil; +#endif // GTM_DISABLE_FETCHER_TEST_BLOCK +} + ++ (instancetype)mockFetcherServiceWithFakedData:(NSData *)fakedDataOrNil + fakedResponse:(NSHTTPURLResponse *)fakedResponse + fakedError:(NSError *)fakedErrorOrNil { +#if !GTM_DISABLE_FETCHER_TEST_BLOCK + GTMSessionFetcherService *service = [[self alloc] init]; + service.allowedInsecureSchemes = @[ @"http" ]; + service.testBlock = ^(GTMSessionFetcher *fetcherToTest, + GTMSessionFetcherTestResponse testResponse) { + testResponse(fakedResponse, fakedDataOrNil, fakedErrorOrNil); + }; + return service; +#else + GTMSESSION_ASSERT_DEBUG(0, @"Test blocks disabled"); + return nil; +#endif // GTM_DISABLE_FETCHER_TEST_BLOCK +} + +#pragma mark Synchronous Wait for Unit Testing + +- (BOOL)waitForCompletionOfAllFetchersWithTimeout:(NSTimeInterval)timeoutInSeconds { + NSDate *giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds]; + _stoppedFetchersToWaitFor = [NSMutableArray array]; + + BOOL shouldSpinRunLoop = [NSThread isMainThread]; + const NSTimeInterval kSpinInterval = 0.001; + BOOL didTimeOut = NO; + while (([self numberOfFetchers] > 0 || _stoppedFetchersToWaitFor.count > 0)) { + didTimeOut = [giveUpDate timeIntervalSinceNow] < 0; + if (didTimeOut) break; + + GTMSessionFetcher *stoppedFetcher = _stoppedFetchersToWaitFor.firstObject; + if (stoppedFetcher) { + [_stoppedFetchersToWaitFor removeObject:stoppedFetcher]; + [stoppedFetcher waitForCompletionWithTimeout:10.0 * kSpinInterval]; + } + + if (shouldSpinRunLoop) { + NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:kSpinInterval]; + [[NSRunLoop currentRunLoop] runUntilDate:stopDate]; + } else { + [NSThread sleepForTimeInterval:kSpinInterval]; + } + } + _stoppedFetchersToWaitFor = nil; + + return !didTimeOut; +} + +@end + +@implementation GTMSessionFetcherService (BackwardsCompatibilityOnly) + +- (NSInteger)cookieStorageMethod { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _cookieStorageMethod; + } +} + +- (void)setCookieStorageMethod:(NSInteger)cookieStorageMethod { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _cookieStorageMethod = cookieStorageMethod; + } +} + +@end + +@implementation GTMSessionFetcherSessionDelegateDispatcher { + __weak GTMSessionFetcherService *_parentService; + NSURLSession *_session; + + // The task map maps NSURLSessionTasks to GTMSessionFetchers + NSMutableDictionary *_taskToFetcherMap; + // The discard timer will invalidate sessions after the session's last task completes. + NSTimer *_discardTimer; + NSTimeInterval _discardInterval; +} + +@synthesize discardInterval = _discardInterval, + session = _session; + +- (instancetype)init { + [self doesNotRecognizeSelector:_cmd]; + return nil; +} + +- (instancetype)initWithParentService:(GTMSessionFetcherService *)parentService + sessionDiscardInterval:(NSTimeInterval)discardInterval { + self = [super init]; + if (self) { + _discardInterval = discardInterval; + _parentService = parentService; + } + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"%@ %p %@ %@", + [self class], self, + _session ?: @"<no session>", + _taskToFetcherMap.count > 0 ? _taskToFetcherMap : @"<no tasks>"]; +} + +- (NSTimer *)discardTimer { + GTMSessionCheckNotSynchronized(self); + @synchronized(self) { + return _discardTimer; + } +} + +// This method should be called inside of a @synchronized(self) block. +- (void)startDiscardTimer { + GTMSessionCheckSynchronized(self); + [_discardTimer invalidate]; + _discardTimer = nil; + if (_discardInterval > 0) { + _discardTimer = [NSTimer timerWithTimeInterval:_discardInterval + target:self + selector:@selector(discardTimerFired:) + userInfo:nil + repeats:NO]; + [_discardTimer setTolerance:(_discardInterval / 10)]; + [[NSRunLoop mainRunLoop] addTimer:_discardTimer forMode:NSRunLoopCommonModes]; + } +} + +// This method should be called inside of a @synchronized(self) block. +- (void)destroyDiscardTimer { + GTMSessionCheckSynchronized(self); + [_discardTimer invalidate]; + _discardTimer = nil; +} + +- (void)discardTimerFired:(NSTimer *)timer { + GTMSessionFetcherService *service; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSUInteger numberOfTasks = _taskToFetcherMap.count; + if (numberOfTasks == 0) { + service = _parentService; + } + } + + // Inform the service that the discard timer has fired, and should check whether the + // service can abandon us. -resetSession cannot be called directly, as there is a + // race condition that must be guarded against with the NSURLSession being returned + // from sessionForFetcherCreation outside other locks. The service can take steps + // to prevent resetting the session if that has occurred. + // + // The service must be called from outside the @synchronized block. + [service resetSessionForDispatcherDiscardTimer:timer]; +} + +- (void)abandon { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [self destroySessionAndTimer]; + } +} + +- (void)startSessionUsage { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [self destroyDiscardTimer]; + } +} + +// This method should be called inside of a @synchronized(self) block. +- (void)destroySessionAndTimer { + GTMSessionCheckSynchronized(self); + [self destroyDiscardTimer]; + + // Break any retain cycle from the session holding the delegate. + [_session finishTasksAndInvalidate]; + + // Immediately clear the session so no new task may be issued with it. + // + // The _taskToFetcherMap needs to stay valid until the outstanding tasks finish. + _session = nil; +} + +- (void)setFetcher:(GTMSessionFetcher *)fetcher forTask:(NSURLSessionTask *)task { + GTMSESSION_ASSERT_DEBUG(fetcher != nil, @"missing fetcher"); + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_taskToFetcherMap == nil) { + _taskToFetcherMap = [[NSMutableDictionary alloc] init]; + } + + if (fetcher) { + [_taskToFetcherMap setObject:fetcher forKey:task]; + [self destroyDiscardTimer]; + } + } +} + +- (void)removeFetcher:(GTMSessionFetcher *)fetcher { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // Typically, a fetcher should be removed when its task invokes + // URLSession:task:didCompleteWithError:. + // + // When fetching with a testBlock, though, the task completed delegate + // method may not be invoked, requiring cleanup here. + NSArray *tasks = [_taskToFetcherMap allKeysForObject:fetcher]; + GTMSESSION_ASSERT_DEBUG(tasks.count <= 1, @"fetcher task not unmapped: %@", tasks); + [_taskToFetcherMap removeObjectsForKeys:tasks]; + + if (_taskToFetcherMap.count == 0) { + [self startDiscardTimer]; + } + } +} + +// This helper method provides synchronized access to the task map for the delegate +// methods below. +- (id)fetcherForTask:(NSURLSessionTask *)task { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return [_taskToFetcherMap objectForKey:task]; + } +} + +- (void)removeTaskFromMap:(NSURLSessionTask *)task { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [_taskToFetcherMap removeObjectForKey:task]; + } +} + +- (void)setSession:(NSURLSession *)session { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _session = session; + } +} + +- (NSURLSession *)session { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _session; + } +} + +- (NSTimeInterval)discardInterval { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _discardInterval; + } +} + +- (void)setDiscardInterval:(NSTimeInterval)interval { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _discardInterval = interval; + } +} + +// NSURLSessionDelegate protocol methods. + +// - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session; +// +// TODO(seh): How do we route this to an appropriate fetcher? + + +- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error { + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ didBecomeInvalidWithError:%@", + [self class], self, session, error); + NSDictionary *localTaskToFetcherMap; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _session = nil; + + localTaskToFetcherMap = [_taskToFetcherMap copy]; + } + + // Any "suspended" tasks may not have received callbacks from NSURLSession when the session + // completes; we'll call them now. + [localTaskToFetcherMap enumerateKeysAndObjectsUsingBlock:^(NSURLSessionTask *task, + GTMSessionFetcher *fetcher, + BOOL *stop) { + if (fetcher.session == session) { + // Our delegate method URLSession:task:didCompleteWithError: will rely on + // _taskToFetcherMap so that should still contain this fetcher. + NSError *canceledError = [NSError errorWithDomain:NSURLErrorDomain + code:NSURLErrorCancelled + userInfo:nil]; + [self URLSession:session task:task didCompleteWithError:canceledError]; + } else { + GTMSESSION_ASSERT_DEBUG(0, @"Unexpected session in fetcher: %@ has %@ (expected %@)", + fetcher, fetcher.session, session); + } + }]; + + // Our tests rely on this notification to know the session discard timer fired. + NSDictionary *userInfo = @{ kGTMSessionFetcherServiceSessionKey : session }; + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc postNotificationName:kGTMSessionFetcherServiceSessionBecameInvalidNotification + object:_parentService + userInfo:userInfo]; +} + + +#pragma mark - NSURLSessionTaskDelegate + +// NSURLSessionTaskDelegate protocol methods. +// +// We won't test here if the fetcher responds to these since we only want this +// class to implement the same delegate methods the fetcher does (so NSURLSession's +// tests for respondsToSelector: will have the same result whether the session +// delegate is the fetcher or this dispatcher.) + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +willPerformHTTPRedirection:(NSHTTPURLResponse *)response + newRequest:(NSURLRequest *)request + completionHandler:(void (^)(NSURLRequest *))completionHandler { + id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task]; + [fetcher URLSession:session + task:task +willPerformHTTPRedirection:response + newRequest:request + completionHandler:completionHandler]; +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))handler { + id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task]; + [fetcher URLSession:session + task:task + didReceiveChallenge:challenge + completionHandler:handler]; +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + needNewBodyStream:(void (^)(NSInputStream *bodyStream))handler { + id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task]; + [fetcher URLSession:session + task:task + needNewBodyStream:handler]; +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didSendBodyData:(int64_t)bytesSent + totalBytesSent:(int64_t)totalBytesSent +totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { + id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task]; + [fetcher URLSession:session + task:task + didSendBodyData:bytesSent + totalBytesSent:totalBytesSent +totalBytesExpectedToSend:totalBytesExpectedToSend]; +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +didCompleteWithError:(NSError *)error { + id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task]; + + // This is the usual way tasks are removed from the task map. + [self removeTaskFromMap:task]; + + [fetcher URLSession:session + task:task + didCompleteWithError:error]; +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics + API_AVAILABLE(ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0)) { + id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task]; + [fetcher URLSession:session task:task didFinishCollectingMetrics:metrics]; +} + +// NSURLSessionDataDelegate protocol methods. + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask +didReceiveResponse:(NSURLResponse *)response + completionHandler:(void (^)(NSURLSessionResponseDisposition))handler { + id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask]; + [fetcher URLSession:session + dataTask:dataTask + didReceiveResponse:response + completionHandler:handler]; +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask +didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask { + id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask]; + GTMSESSION_ASSERT_DEBUG(fetcher != nil, @"Missing fetcher for %@", dataTask); + [self removeTaskFromMap:dataTask]; + if (fetcher) { + GTMSESSION_ASSERT_DEBUG([fetcher isKindOfClass:[GTMSessionFetcher class]], + @"Expecting GTMSessionFetcher"); + [self setFetcher:(GTMSessionFetcher *)fetcher forTask:downloadTask]; + } + + [fetcher URLSession:session + dataTask:dataTask +didBecomeDownloadTask:downloadTask]; +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + didReceiveData:(NSData *)data { + id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask]; + [fetcher URLSession:session + dataTask:dataTask + didReceiveData:data]; +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + willCacheResponse:(NSCachedURLResponse *)proposedResponse + completionHandler:(void (^)(NSCachedURLResponse *))handler { + id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask]; + [fetcher URLSession:session + dataTask:dataTask + willCacheResponse:proposedResponse + completionHandler:handler]; +} + +// NSURLSessionDownloadDelegate protocol methods. + +- (void)URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask +didFinishDownloadingToURL:(NSURL *)location { + id<NSURLSessionDownloadDelegate> fetcher = [self fetcherForTask:downloadTask]; + [fetcher URLSession:session + downloadTask:downloadTask +didFinishDownloadingToURL:location]; +} + +- (void)URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask + didWriteData:(int64_t)bytesWritten + totalBytesWritten:(int64_t)totalWritten +totalBytesExpectedToWrite:(int64_t)totalExpected { + id<NSURLSessionDownloadDelegate> fetcher = [self fetcherForTask:downloadTask]; + [fetcher URLSession:session + downloadTask:downloadTask + didWriteData:bytesWritten + totalBytesWritten:totalWritten +totalBytesExpectedToWrite:totalExpected]; +} + +- (void)URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask + didResumeAtOffset:(int64_t)fileOffset +expectedTotalBytes:(int64_t)expectedTotalBytes { + id<NSURLSessionDownloadDelegate> fetcher = [self fetcherForTask:downloadTask]; + [fetcher URLSession:session + downloadTask:downloadTask + didResumeAtOffset:fileOffset + expectedTotalBytes:expectedTotalBytes]; +} + +@end |
