修改pods

This commit is contained in:
2025-09-20 17:13:38 +08:00
parent 7787b3ee30
commit 28ff2b0264
5251 changed files with 345029 additions and 285168 deletions

View File

@@ -24,5 +24,5 @@ Features include:
- Fully independent of other projects
**To get started** please read
[USING.md](https://github.com/google/google-api-objectivec-client-for-rest/blob/main/USING.md)
for detailed information.
[USING.md](USING.md)
for detailed information.

View File

@@ -97,41 +97,25 @@ NS_ASSUME_NONNULL_END
#define GTM_TARGET_SUPPORTS_APP_TRANSPORT_SECURITY 1
#endif
#if ((defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST) || \
(TARGET_OS_OSX && defined(__MAC_10_15) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_15) || \
(TARGET_OS_IOS && defined(__IPHONE_13_0) && \
__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_13_0) || \
(TARGET_OS_WATCH && defined(__WATCHOS_6_0) && \
__WATCH_OS_VERSION_MIN_REQUIRED >= __WATCHOS_6_0) || \
(TARGET_OS_TV && defined(__TVOS_13_0) && __TVOS_VERSION_MIN_REQUIRED >= __TVOS_13_0) || \
(defined(TARGET_OS_VISION) && TARGET_OS_VISION))
#if TARGET_OS_IOS
#if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_13_0
#define GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 1
#define GTM_SDK_SUPPORTS_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 1
#elif ((TARGET_OS_OSX && defined(__MAC_10_15) && __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_10_15) || \
(TARGET_OS_IOS && defined(__IPHONE_13_0) && \
__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0) || \
(TARGET_OS_WATCH && defined(__WATCHOS_6_0) && \
__WATCH_OS_VERSION_MAX_ALLOWED >= __WATCHOS_6_0) || \
(TARGET_OS_TV && defined(__TVOS_13_0) && __TVOS_VERSION_MAX_ALLOWED >= __TVOS_13_0))
#define GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 0
#define GTM_SDK_SUPPORTS_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 1
#else
#define GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 0
#define GTM_SDK_SUPPORTS_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 0
#endif
#else // Not iOS
#define GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 1
#endif
#if ((defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST) || \
(TARGET_OS_OSX && defined(__MAC_10_15) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_15) || \
(TARGET_OS_IOS && defined(__IPHONE_13_0) && \
__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_13_0) || \
(TARGET_OS_WATCH && defined(__WATCHOS_6_0) && \
__WATCH_OS_VERSION_MIN_REQUIRED >= __WATCHOS_6_0) || \
(TARGET_OS_TV && defined(__TVOS_13_0) && __TVOS_VERSION_MIN_REQUIRED >= __TVOS_13_0) || \
(defined(TARGET_OS_VISION) && TARGET_OS_VISION))
#if TARGET_OS_IOS
#if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_13_0
#define GTM_SDK_REQUIRES_SECTRUSTEVALUATEWITHERROR 1
#else
#define GTM_SDK_REQUIRES_SECTRUSTEVALUATEWITHERROR 0
#endif
#else // Not iOS
#define GTM_SDK_REQUIRES_SECTRUSTEVALUATEWITHERROR 1
#endif
#if __has_attribute(swift_async)
// Once Clang 13/Xcode 13 can be assumed, can switch to NS_SWIFT_DISABLE_ASYNC.
@@ -140,6 +124,21 @@ NS_ASSUME_NONNULL_END
#define GTM_SWIFT_DISABLE_ASYNC
#endif
// Internal tracking of the state within the `-beginFetch...` flow.
typedef NS_ENUM(NSUInteger, GTMSessionFetcherStartingState) {
// Not in any explcit part of the startup flow or about the re-enter the flow.
kStartingStateNone = 0,
// Parts of startup that can result in the fetcher completing startup at some later time.
kStartingStateServiceDelayed,
kStartingStateCalculatingUA,
kStartingStateAuthorizing,
kStartingStateApplyingDecorators,
// State while actively in `-beginFetchMayDelay:mayAuthorize:mayDecorate:`.
kStartingStateStartingUp,
};
@interface GTMSessionFetcher ()
@property(atomic, strong, readwrite, nullable) NSData *downloadedData;
@@ -256,6 +255,9 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
NSDate *_initialRequestDate; // date of first request to the target server (ignoring auth)
BOOL _hasAttemptedAuthRefresh; // accessed only in shouldRetryNowForStatus:
GTMSessionFetcherStartingState _startingState;
NSUInteger _pendingNotifications;
NSString *_comment; // comment for log
NSString *_log;
#if !STRIP_GTM_FETCH_LOGGING
@@ -497,14 +499,68 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
[self beginFetchWithCompletionHandler:handler];
}
// Helper to enter a new StartingState. If the fetch has already been stopped, then it will trigger
// a needed callback instead of setting the state. It returns YES/NO based on if starting up of the
// fetch should continue.
- (BOOL)startingState:(GTMSessionFetcherStartingState)newState __attribute__((objc_direct)) {
BOOL stopped = NO;
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
GTMSESSION_ASSERT_DEBUG(
(_startingState == kStartingStateNone || _startingState == kStartingStateStartingUp),
@"Unexpected starting state: %lu", (unsigned long)_startingState);
if (_userStoppedFetching) {
stopped = YES;
} else {
_startingState = newState;
}
}
if (stopped) {
// We end up here if someone calls `stopFetching` from another thread/queue while
// the fetch was being started up, so while `stopFetching` did the needed shutdown
// we have to ensure the requested callback was triggered.
if (self.stopFetchingTriggersCompletionHandler) {
NSError *error = [NSError errorWithDomain:kGTMSessionFetcherErrorDomain
code:GTMSessionFetcherErrorUserCancelled
userInfo:nil];
[self failToBeginFetchWithError:error];
} else {
// In the edge case where the fetch was stopped before it even began, the call backs still
// need to get cleared as at least the completion will have been just set in calling
// beginFetch...
[self stopFetchReleasingCallbacks:YES];
}
return NO; // Caller to stop.
}
return YES;
}
// This is a private callback from the service to restart the fetcher after it was delayed
// due to the per host throttling.
- (void)serviceRestartingFetcher {
// Reset the starting state since the fetch is getting restarted.
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
_startingState = kStartingStateNone;
}
[self beginFetchMayDelay:NO mayAuthorize:YES mayDecorate:YES];
}
- (void)beginFetchMayDelay:(BOOL)mayDelay
mayAuthorize:(BOOL)mayAuthorize
mayDecorate:(BOOL)mayDecorate {
// This is the internal entry point for re-starting fetches.
GTMSessionCheckNotSynchronized(self);
NSMutableURLRequest *fetchRequest =
_request; // The request property is now externally immutable.
if (![self startingState:kStartingStateStartingUp]) {
return;
}
// The request property is now externally immutable.
NSMutableURLRequest *fetchRequest = _request;
NSURL *fetchRequestURL = fetchRequest.URL;
NSString *priorSessionIdentifier = self.sessionIdentifier;
@@ -643,7 +699,7 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
return;
}
} // !isSecure
} // (requestURL != nil) && !isDataRequest
} // (requestURL != nil) && !isDataRequest
if (self.cookieStorage == nil) {
self.cookieStorage = [[self class] staticCookieStorage];
@@ -702,17 +758,43 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
mayDelay = NO;
}
if (mayDelay && _service) {
// Set the delayed state so there can't be a race between it getting queued in the
// `fetcherShouldBeginFetching:` call and some other thread completing a different fetch and
// thus starting this one. If we were trying to set the state based on the return result, there
// would be a small window for that race.
if (![self startingState:kStartingStateServiceDelayed]) {
return;
}
BOOL shouldFetchNow = [_service fetcherShouldBeginFetching:self];
if (!shouldFetchNow) {
// The fetch is deferred, but will happen later.
//
// If this session is held by the fetcher service, clear the session now so that we don't
// assume it's still valid after the fetcher is restarted.
//
// NOTE: In hindsight, this could be a race, some other thread could be starting it while
// doing these checks/work, but it also doesn't seem safe to put the whole block in a
// since `@synchronized(self)` block.
if (self.canShareSession) {
self.session = nil;
}
return;
}
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
// If a `-stopFetching` came in while the service check was made, then the side effect of the
// state setting caused the handler (if needed) to already be made, so there we want to just
// exit and not continue the fetch.
if (_userStoppedFetching) {
return;
}
// Per comment above, correct state since it wasn't delayed, go back to startup state.
_startingState = kStartingStateStartingUp;
}
}
if ([fetchRequest valueForHTTPHeaderField:@"User-Agent"] == nil) {
@@ -725,7 +807,6 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
// The User-Agent is not cached in memory. Fetch it asynchronously.
[self updateUserAgentAsynchronouslyForRequest:fetchRequest
userAgentProvider:userAgentProvider
mayDelay:mayDelay
mayAuthorize:mayAuthorize
mayDecorate:mayDecorate];
// This method can't continue until the User-Agent header is fetched. The above
@@ -808,7 +889,16 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
}
}
// Even though the StartingUp state was entered at the start of the function, we re-enter it here
// in case a call to `-stopFetcher` comes in on another thread while the above code was running,
// this lets
// `-startingState:` do the needed checks/callbacks/cleanups without repeating them here.
if (![self startingState:kStartingStateStartingUp]) {
return;
}
// finally, start the connection
NSURLSessionTask *newSessionTask;
BOOL needsDataAccumulator = NO;
if (_downloadResumeData) {
@@ -906,10 +996,12 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
// UIApplication on the main thread.
UIBackgroundTaskIdentifier localTaskID;
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
localTaskID = guardedTaskID;
}
if (localTaskID != UIBackgroundTaskInvalid) {
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
if (localTaskID == self.backgroundTaskIdentifier) {
self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
}
@@ -918,6 +1010,7 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
}
}];
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
guardedTaskID = returnedTaskID;
self.backgroundTaskIdentifier = returnedTaskID;
}
@@ -937,9 +1030,7 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
[self setStopNotificationNeeded:YES];
[self postNotificationOnMainThreadWithName:kGTMSessionFetcherStartedNotification
userInfo:nil
requireAsync:NO];
[self postNotificationOnMainThreadWithName:kGTMSessionFetcherStartedNotification userInfo:nil];
// The service needs to know our task if it is serving as NSURLSession delegate.
[_service fetcherDidBeginFetching:self];
@@ -956,6 +1047,30 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
// of the session task.
[newSessionTask resume];
}
BOOL stopped;
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
stopped = _userStoppedFetching;
if (!stopped) {
_startingState = kStartingStateNone;
}
}
// If a `-stopFetching` came in between where the connection was finally started (see above), then
// `-stopFetching` itself didn't do anything because it would be a race, so we have to trigger
// the cleanup work here manually.
//
if (stopped) {
if (self.stopFetchingTriggersCompletionHandler) {
NSError *error = [NSError errorWithDomain:kGTMSessionFetcherErrorDomain
code:GTMSessionFetcherErrorUserCancelled
userInfo:nil];
// Fetch started, can't use `-failToBeginFetchWithError:`.
[self finishWithError:error shouldRetry:NO];
} else {
[self stopFetchReleasingCallbacks:YES];
}
}
}
// Helper method to create a new NSURLSession for this fetcher. Because the original
@@ -984,14 +1099,12 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
#if !GTM_ALLOW_INSECURE_REQUESTS
#if GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION
_configuration.TLSMinimumSupportedProtocolVersion = tls_protocol_version_TLSv12;
#elif GTM_SDK_SUPPORTS_TLSMINIMUMSUPPORTEDPROTOCOLVERSION
if (@available(iOS 13, tvOS 13, macOS 10.15, *)) {
#else
if (@available(iOS 13, *)) {
_configuration.TLSMinimumSupportedProtocolVersion = tls_protocol_version_TLSv12;
} else {
_configuration.TLSMinimumSupportedProtocol = kTLSProtocol12;
}
#else
_configuration.TLSMinimumSupportedProtocol = kTLSProtocol12;
#endif // GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION
#endif
} // !_configuration
@@ -1029,11 +1142,15 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
// sets it in |fetchRequest| and continues the request.
- (void)updateUserAgentAsynchronouslyForRequest:(NSMutableURLRequest *)fetchRequest
userAgentProvider:(id<GTMUserAgentProvider>)userAgentProvider
mayDelay:(BOOL)mayDelay
mayAuthorize:(BOOL)mayAuthorize
mayDecorate:(BOOL)mayDecorate {
GTMSESSION_LOG_DEBUG_VERBOSE(
@"GTMSessionFetcher fetching User-Agent from GTMUserAgentProvider %@...", _userAgentProvider);
if (![self startingState:kStartingStateCalculatingUA]) {
return;
}
__weak __typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong __typeof__(self) strongSelf = weakSelf;
@@ -1048,23 +1165,17 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
@"GTMUserAgentProvider %@ should have cached user agent now that it's "
@"calculated, but returned nil",
userAgentProvider);
BOOL shouldStop;
@synchronized(strongSelf) {
GTMSessionMonitorSynchronized(strongSelf);
// If `stopFetching` was called, do nothing, since the fetch was in a delay state
// any needed callback already happened.
if (strongSelf->_userStoppedFetching) {
return;
}
[strongSelf->_request setValue:userAgent forHTTPHeaderField:@"User-Agent"];
shouldStop = strongSelf->_userStoppedFetching;
}
if (shouldStop) {
NSError *error = [NSError errorWithDomain:kGTMSessionFetcherErrorDomain
code:GTMSessionFetcherErrorUserCancelled
userInfo:nil];
[strongSelf invokeFetchCallbacksOnCallbackQueueWithData:nil
error:error
mayDecorate:NO
shouldReleaseCallbacks:YES];
} else {
[strongSelf beginFetchMayDelay:mayDelay mayAuthorize:mayAuthorize mayDecorate:mayDecorate];
strongSelf->_startingState = kStartingStateNone;
}
[strongSelf beginFetchMayDelay:NO mayAuthorize:mayAuthorize mayDecorate:mayDecorate];
});
}
@@ -1553,18 +1664,39 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
} // @synchronized(self)
}
- (nullable NSDictionary *)sessionUserInfo {
- (nullable NSDictionary<NSString *, NSString *> *)sessionUserInfo {
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
if (_sessionUserInfo == nil) {
// We'll return the metadata dictionary with internal keys removed. This avoids the user
// re-using the userInfo dictionary later and accidentally including the internal keys.
// Just incase something got corrupted in storage and parsed back out differently, ensure
// the api contract on types is still valid.
NSMutableDictionary *metadata = [[self sessionIdentifierMetadataUnsynchronized] mutableCopy];
NSSet *keysToRemove = [metadata keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) {
return [key hasPrefix:@"_"];
return ![key isKindOfClass:[NSString class]] || ![obj isKindOfClass:[NSString class]] ||
[key hasPrefix:@"_"];
}];
#if DEBUG
// If we're pruning, give warnings about the things that were invalid as some bug has slipped
// through.
if (keysToRemove.count) {
[metadata enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj,
BOOL *_Nonnull stop) {
if (![key isKindOfClass:[NSString class]]) {
GTMSESSION_LOG_DEBUG(
@"InternalError: restoring sessionUserInfo is pruning a non String key: %@", key);
} else if (![obj isKindOfClass:[NSString class]]) {
GTMSESSION_LOG_DEBUG(
@"InternalError: restoring sessionUserInfo is pruning a non String value: %@: %@",
key, obj);
}
}];
}
#endif // DEBUG
[metadata removeObjectsForKeys:[keysToRemove allObjects]];
if (metadata.count > 0) {
_sessionUserInfo = metadata;
}
@@ -1573,7 +1705,26 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
} // @synchronized(self)
}
- (void)setSessionUserInfo:(nullable NSDictionary *)dictionary {
- (void)setSessionUserInfo:(nullable NSDictionary<NSString *, NSString *> *)dictionary {
[dictionary enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj,
BOOL *_Nonnull stop) {
if (![key isKindOfClass:[NSString class]]) {
[NSException raise:NSInvalidArgumentException
format:@"sessionUserInfo keys must be NSStrings: %@", key];
}
if ([key hasPrefix:@"_"]) {
[NSException
raise:NSInvalidArgumentException
format:
@"sessionUserInfo keys starting with an underscore are reserved for the library: %@",
key];
}
if (![obj isKindOfClass:[NSString class]]) {
[NSException raise:NSInvalidArgumentException
format:@"Values in sessionUserInfo must be NSStrings: %@: %@", key, obj];
}
}];
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
@@ -1668,15 +1819,61 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
_sessionIdentifierUUID = [[NSUUID UUID] UUIDString];
_sessionIdentifier =
[NSString stringWithFormat:@"%@_%@", kGTMSessionIdentifierPrefix, _sessionIdentifierUUID];
// Start with user-supplied keys so they cannot accidentally override the fetcher's keys.
NSMutableDictionary *metadataDict =
[NSMutableDictionary dictionaryWithDictionary:(NSDictionary *_Nonnull)_sessionUserInfo];
// sessionUserInfo was declared as `strong` (not `copy`), so it could have been modifed after
// having been set. So remove anything that breaks the contract.
NSSet *keysToRemove = [metadataDict keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) {
return ![key isKindOfClass:[NSString class]] || ![obj isKindOfClass:[NSString class]] ||
[key hasPrefix:@"_"];
}];
#if DEBUG
// If we're pruning, give warnings about the things that were invalid as some bug has slipped
// through.
if (keysToRemove.count) {
[metadataDict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj,
BOOL *_Nonnull stop) {
if (![key isKindOfClass:[NSString class]]) {
GTMSESSION_LOG_DEBUG(
@"Warning: sessionUserInfo has been modifed to include a non String key: %@", key);
} else if (![obj isKindOfClass:[NSString class]]) {
GTMSESSION_LOG_DEBUG(
@"Warning: sessionUserInfo has been modifed to include a non String value: %@: %@",
key, obj);
}
}];
}
#endif // DEBUG
[metadataDict removeObjectsForKeys:[keysToRemove allObjects]];
if (metadataToInclude) {
#if DEBUG
[metadataToInclude enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj,
BOOL *_Nonnull stop) {
GTMSESSION_ASSERT_DEBUG([key isKindOfClass:[NSString class]],
@"metadataToInclude keys must be NSStrings: %@", key);
GTMSESSION_ASSERT_DEBUG([key hasPrefix:@"_"],
@"metadataToInclude should only have prefixed keys: %@ - %@", key,
obj);
}];
#endif
[metadataDict addEntriesFromDictionary:(NSDictionary *)metadataToInclude];
}
NSDictionary *defaultMetadataDict = [self sessionIdentifierDefaultMetadata];
if (defaultMetadataDict) {
#if DEBUG
[defaultMetadataDict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj,
BOOL *_Nonnull stop) {
GTMSESSION_ASSERT_DEBUG([key isKindOfClass:[NSString class]],
@"defaultMetadataDict keys must be NSStrings: %@", key);
GTMSESSION_ASSERT_DEBUG([key hasPrefix:@"_"],
@"defaultMetadataDict should only have prefixed keys: %@ - %@", key,
obj);
}];
#endif
[metadataDict addEntriesFromDictionary:defaultMetadataDict];
}
if (metadataDict.count > 0) {
@@ -1737,6 +1934,8 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
// we need to tell UIApplication we're done.
UIBackgroundTaskIdentifier bgTaskID;
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
bgTaskID = self.backgroundTaskIdentifier;
if (bgTaskID != UIBackgroundTaskInvalid) {
self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
@@ -1754,6 +1953,10 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
- (void)authorizeRequest {
GTMSessionCheckNotSynchronized(self);
if (![self startingState:kStartingStateAuthorizing]) {
return;
}
id authorizer = self.authorizer;
// Prefer the block-based implementation. This *is* a change in behavior, but if authorizers
// previously provided this method they would presumably assume they can be used for the same
@@ -1774,9 +1977,12 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
[authorizer authorizeRequest:mutableRequest delegate:self didFinishSelector:callbackSel];
} else {
GTMSESSION_ASSERT_DEBUG(authorizer == nil, @"invalid authorizer for fetch");
// No authorizing possible, and authorizing happens only after any delay;
// just begin fetching
// Should really never get here the main flow shouldn't have called here if there
// wasn't an authorizer, but for safety sake, continue on through the starting process.
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
_startingState = kStartingStateNone;
}
[self beginFetchMayDelay:NO mayAuthorize:NO mayDecorate:YES];
}
}
@@ -1789,13 +1995,28 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
finishedWithError:(nullable NSError *)error {
GTMSessionCheckNotSynchronized(self);
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
// If `stopFetching` was called, do nothing, since the fetch was in a delay state
// any needed callback already happened.
if (_userStoppedFetching) {
return;
}
if (error == nil) {
_request = authorizedRequest;
// Clear the delay state only if things aren't about to fail. Don't want to have a race
// between clearing the state and when the error is posted because some other thread happened
// to get in a call to
// `-stopFetching`.
_startingState = kStartingStateNone;
}
} // @synchronized(self)
if (error != nil) {
// We can't fetch without authorization
[self failToBeginFetchWithError:(NSError *_Nonnull)error];
} else {
@synchronized(self) {
_request = authorizedRequest;
}
[self beginFetchMayDelay:NO mayAuthorize:NO mayDecorate:YES];
}
}
@@ -1806,40 +2027,79 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
if (index >= decorators.count) {
GTMSESSION_LOG_DEBUG_VERBOSE(
@"GTMSessionFetcher decorate requestWillStart %zu decorators complete", decorators.count);
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
_startingState = kStartingStateNone;
}
[self beginFetchMayDelay:NO mayAuthorize:NO mayDecorate:NO];
return;
}
if (index == 0) {
if (![self startingState:kStartingStateApplyingDecorators]) {
return;
}
}
__weak __typeof__(self) weakSelf = self;
id<GTMFetcherDecoratorProtocol> decorator = decorators[index];
GTMSESSION_LOG_DEBUG_VERBOSE(
@"GTMSessionFetcher decorate requestWillStart %zu decorators, index %zu, "
@"retry count %zu, decorator %@",
decorators.count, index, self.retryCount, decorator);
[decorator fetcherWillStart:self
completionHandler:^(NSURLRequest *_Nullable newRequest, NSError *_Nullable error) {
GTMSESSION_LOG_DEBUG_VERBOSE(
@"GTMSessionFetcher decorator requestWillStart index %zu "
@"complete, newRequest %@, error %@",
index, newRequest, error);
__strong __typeof__(self) strongSelf = weakSelf;
if (!strongSelf) {
GTMSESSION_LOG_DEBUG(@"GTMSessionFetcher destroyed before requestWillStart "
@"decorators completed, ignoring.");
return;
}
if (error) {
[self failToBeginFetchWithError:(NSError *_Nonnull)error];
return;
}
if (newRequest) {
// Copying `NSURLRequest` should be cheap, but in case profiling shows this
// operation is prohibitively expensive, this API might need to be changed to allow
// clients to manipulate `self.request` directly.
[strongSelf updateMutableRequest:[newRequest mutableCopy]];
}
[strongSelf applyDecoratorsAtRequestWillStart:decorators startingAtIndex:index + 1];
}];
[decorator
fetcherWillStart:self
completionHandler:^(NSURLRequest *_Nullable newRequest, NSError *_Nullable error) {
GTMSESSION_LOG_DEBUG_VERBOSE(@"GTMSessionFetcher decorator requestWillStart index %zu "
@"complete, newRequest %@, error %@",
index, newRequest, error);
__strong __typeof__(self) strongSelf = weakSelf;
if (!strongSelf) {
GTMSESSION_LOG_DEBUG(@"GTMSessionFetcher destroyed before requestWillStart "
@"decorators completed, ignoring.");
return;
}
BOOL shouldStop;
@synchronized(strongSelf) {
GTMSessionMonitorSynchronized(strongSelf);
shouldStop = strongSelf->_userStoppedFetching;
if (shouldStop) {
if (strongSelf->_stopFetchingTriggersCompletionHandler) {
// Override any error with the cancel error and then trigger the callbacks below.
//
// The callback for a cancel is sent from here instead of from within `-stopFetching`
// like is done for all the other pending states because if `-stopFetching` directly
// triggered the callbacks, the decorators would also get triggered and in a
// multithreaded case it would be possible for this current decorartor to have
// didFinish invoked at the same time but on a different thread; so triggering the
// failure completion here will avoid that.
error = [NSError errorWithDomain:kGTMSessionFetcherErrorDomain
code:GTMSessionFetcherErrorUserCancelled
userInfo:nil];
} else {
// Suppress any error from being sent due to the `-stopFetching`.
error = nil;
}
}
}
if (error) {
[strongSelf failToBeginFetchWithError:(NSError *_Nonnull)error];
return;
}
if (shouldStop) {
return;
}
if (newRequest) {
// Copying `NSURLRequest` should be cheap, but in case profiling shows this
// operation is prohibitively expensive, this API might need to be changed to allow
// clients to manipulate `self.request` directly.
[strongSelf updateMutableRequest:[newRequest mutableCopy]];
}
[strongSelf applyDecoratorsAtRequestWillStart:decorators startingAtIndex:index + 1];
}];
}
- (void)applyDecoratorsAtRequestDidFinish:(NSArray<id<GTMFetcherDecoratorProtocol>> *)decorators
@@ -1848,6 +2108,11 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
startingAtIndex:(NSUInteger)index
shouldReleaseCallbacks:(BOOL)shouldReleaseCallbacks {
GTMSessionCheckNotSynchronized(self);
// NOTE: At this point, the fetch is done, so if `-stopFetching` comes in now, it does not stop
// these from getting called as the completion is currently being invoked with the results of
// the fetch.
if (index >= decorators.count) {
GTMSESSION_LOG_DEBUG_VERBOSE(
@"GTMSessionFetcher decorate requestDidFinish %zu decorators complete", decorators.count);
@@ -2011,9 +2276,7 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
self.retryBlock = nil;
self.testBlock = nil;
self.resumeDataBlock = nil;
if (@available(iOS 10.0, *)) {
self.metricsCollectionBlock = nil;
}
self.metricsCollectionBlock = nil;
}
- (void)forgetSessionIdentifierForFetcher {
@@ -2033,14 +2296,38 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
// External stop method
- (void)stopFetching {
BOOL triggerCallback;
BOOL inBeginFetch;
BOOL stopTriggersHandler;
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
stopTriggersHandler = self.stopFetchingTriggersCompletionHandler;
// Prevent enqueued callbacks from executing. The completion handler will still execute if
// the property `stopFetchingTriggersCompletionHandler` is `YES`.
_userStoppedFetching = YES;
// Some of the delayed states want the complition to be triggered from here so they don't have
// to do it during their flows for async work.
triggerCallback = (_startingState == kStartingStateServiceDelayed ||
_startingState == kStartingStateAuthorizing ||
_startingState == kStartingStateCalculatingUA) &&
stopTriggersHandler;
// If literally in `-beginFetchMayDelay:mayAuthorize:mayDecorate:`, it will handle the work to
// stop.
inBeginFetch = _startingState == kStartingStateStartingUp;
} // @synchronized(self)
[self stopFetchReleasingCallbacks:!self.stopFetchingTriggersCompletionHandler];
if (triggerCallback) {
NSError *error = [NSError errorWithDomain:kGTMSessionFetcherErrorDomain
code:GTMSessionFetcherErrorUserCancelled
userInfo:nil];
[self failToBeginFetchWithError:error];
} else if (!inBeginFetch) {
[self stopFetchReleasingCallbacks:!stopTriggersHandler];
}
}
// Cancel the fetch of the URL that's currently in progress.
@@ -2052,7 +2339,7 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
[self removePersistedBackgroundSessionFromDefaults];
GTMSessionFetcherService *service;
NSMutableURLRequest *request;
NSMutableURLRequest *requestForStopAuth;
// If the task or the retry timer is all that's retaining the fetcher,
// we want to be sure this instance survives stopping at least long enough for
@@ -2071,7 +2358,8 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
_hasStoppedFetching = YES;
service = _service;
request = _request;
// Only will need to stop authorization if in an authorizing state.
requestForStopAuth = (_startingState == kStartingStateAuthorizing) ? _request : nil;
if (_sessionTask) {
// In case cancelling the task or session calls this recursively, we want
@@ -2146,7 +2434,9 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
[self sendStopNotificationIfNeeded];
}
[_authorizer stopAuthorizationForRequest:request];
if (requestForStopAuth) {
[_authorizer stopAuthorizationForRequest:requestForStopAuth];
}
if (shouldReleaseCallbacks) {
[self releaseCallbacks];
@@ -2181,9 +2471,7 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
} // @synchronized(self)
if (sendNow) {
[self postNotificationOnMainThreadWithName:kGTMSessionFetcherStoppedNotification
userInfo:nil
requireAsync:NO];
[self postNotificationOnMainThreadWithName:kGTMSessionFetcherStoppedNotification userInfo:nil];
}
}
@@ -2550,8 +2838,7 @@ static _Nullable id<GTMUIApplicationProtocol> gSubstituteUIApp;
NSString *redirectScheme = redirectRequestURL.scheme;
// If no change in scheme with redirect, just return the redirect.
if (originalScheme != nil &&
redirectScheme != nil &&
if (originalScheme != nil && redirectScheme != nil &&
[originalScheme caseInsensitiveCompare:redirectScheme] == NSOrderedSame) {
return redirectRequestURL;
}
@@ -2765,31 +3052,56 @@ static _Nullable id<GTMUIApplicationProtocol> gSubstituteUIApp;
}
[self postNotificationOnMainThreadWithName:
kGTMSessionFetcherCompletionInvokedNotification
userInfo:userInfo
requireAsync:NO];
userInfo:userInfo];
}];
}
}
- (void)postNotificationOnMainThreadWithName:(NSString *)noteName
userInfo:(nullable NSDictionary *)userInfo
requireAsync:(BOOL)requireAsync {
dispatch_block_t postBlock = ^{
userInfo:(nullable NSDictionary *)userInfo {
// Historically, the notification has been posted immediately when on the main thread already, so
// that is continued to not break any existing code. However, if a notification gets deferred to
// be posted on the main thread, then even if some other code is one the main thread, it can't
// post it's notification before the deferred one is posted, otherwise you could get a stop and/or
// callback notification *before* the start notification. So if anything gets deferred to the main
// thread, an immediated post on the main thread also has to get delayed to maintain the order.
//
// This could get revisited to defer all notifications, but that should probably go out in a
// major version bump as it could be breaking to some usages who were dependent on the ordering.
GTMSessionCheckNotSynchronized(self);
BOOL canPostNow = NO;
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
if (_pendingNotifications == 0 && [NSThread isMainThread]) {
canPostNow = YES;
} else {
// This one will get deferred, so increase the counter for deferred notifications.
_pendingNotifications++;
}
}
if (canPostNow) {
[[NSNotificationCenter defaultCenter] postNotificationName:noteName
object:self
userInfo:userInfo];
};
if ([NSThread isMainThread] && !requireAsync) {
// Post synchronously for compatibility with older code using the fetcher.
// Avoid calling out to other code from inside a sync block to avoid risk
// of a deadlock or of recursive sync.
GTMSessionCheckNotSynchronized(self);
postBlock();
} else {
dispatch_async(dispatch_get_main_queue(), postBlock);
dispatch_async(dispatch_get_main_queue(), ^{
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
GTMSESSION_ASSERT_DEBUG(
self->_pendingNotifications > 0,
@"Internal error: firing pending notification when wasn't tracked: %lu",
(unsigned long)self->_pendingNotifications);
self->_pendingNotifications--;
}
[[NSNotificationCenter defaultCenter] postNotificationName:noteName
object:self
userInfo:userInfo];
});
}
}
@@ -3132,8 +3444,7 @@ static _Nullable id<GTMUIApplicationProtocol> gSubstituteUIApp;
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
API_AVAILABLE(ios(10.0), macosx(10.12), tvos(10.0), watchos(6.0)) {
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics {
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
GTMSessionFetcherMetricsCollectionBlock metricsCollectionBlock = _metricsCollectionBlock;
@@ -3518,8 +3829,7 @@ static _Nullable id<GTMUIApplicationProtocol> gSubstituteUIApp;
} // @synchronized(self)
[self postNotificationOnMainThreadWithName:kGTMSessionFetcherRetryDelayStartedNotification
userInfo:nil
requireAsync:NO];
userInfo:nil];
}
- (void)retryTimerFired:(NSTimer *)timer {
@@ -3552,8 +3862,7 @@ static _Nullable id<GTMUIApplicationProtocol> gSubstituteUIApp;
if (shouldNotify) {
[self postNotificationOnMainThreadWithName:kGTMSessionFetcherRetryDelayStoppedNotification
userInfo:nil
requireAsync:NO];
userInfo:nil];
}
}

View File

@@ -66,7 +66,9 @@
@implementation GTMSessionFetcher (GTMSessionFetcherLogging)
// fetchers come and fetchers go, but statics are forever
// This should only be accessed within @synchronized([GTMSessionFetcher class]) blocks
// to ensure thread safety. Even if not manually toggled, multiple fetches can lazy
// initialize things in parallel causing issues.
static BOOL gIsLoggingEnabled = NO;
static BOOL gIsLoggingToFile = YES;
static NSString *gLoggingDirectoryPath = nil;
@@ -75,141 +77,165 @@ static NSString *gLoggingDateStamp = nil;
static NSString *gLoggingProcessName = nil;
+ (void)setLoggingDirectory:(NSString *)path {
gLoggingDirectoryPath = [path copy];
@synchronized([GTMSessionFetcher class]) {
gLoggingDirectoryPath = [path copy];
}
}
+ (NSString *)loggingDirectory {
if (!gLoggingDirectoryPath) {
NSArray *paths = nil;
@synchronized([GTMSessionFetcher class]) {
if (!gLoggingDirectoryPath) {
NSArray *paths = nil;
#if TARGET_IPHONE_SIMULATOR
// default to a directory called GTMHTTPDebugLogs into a sandbox-safe
// directory that a developer can find easily, the application home
paths = @[ NSHomeDirectory() ];
// default to a directory called GTMHTTPDebugLogs into a sandbox-safe
// directory that a developer can find easily, the application home
paths = @[ NSHomeDirectory() ];
#elif TARGET_OS_IPHONE
// Neither ~/Desktop nor ~/Home is writable on an actual iOS, watchOS, or tvOS device.
// Put it in ~/Documents.
paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
// Neither ~/Desktop nor ~/Home is writable on an actual iOS, watchOS, or tvOS device.
// Put it in ~/Documents.
paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
#else
// default to a directory called GTMHTTPDebugLogs in the desktop folder
paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES);
// default to a directory called GTMHTTPDebugLogs in the desktop folder
paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES);
#endif
NSString *desktopPath = paths.firstObject;
if (desktopPath) {
NSString *const kGTMLogFolderName = @"GTMHTTPDebugLogs";
NSString *logsFolderPath = [desktopPath stringByAppendingPathComponent:kGTMLogFolderName];
NSString *desktopPath = paths.firstObject;
if (desktopPath) {
NSString *const kGTMLogFolderName = @"GTMHTTPDebugLogs";
NSString *logsFolderPath = [desktopPath stringByAppendingPathComponent:kGTMLogFolderName];
NSFileManager *fileMgr = [NSFileManager defaultManager];
BOOL isDir;
BOOL doesFolderExist = [fileMgr fileExistsAtPath:logsFolderPath isDirectory:&isDir];
if (!doesFolderExist) {
// make the directory
doesFolderExist = [fileMgr createDirectoryAtPath:logsFolderPath
withIntermediateDirectories:YES
attributes:nil
error:NULL];
if (doesFolderExist) {
// The directory has been created. Exclude it from backups.
NSURL *pathURL = [NSURL fileURLWithPath:logsFolderPath isDirectory:YES];
[pathURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:NULL];
}
}
NSFileManager *fileMgr = [NSFileManager defaultManager];
BOOL isDir;
BOOL doesFolderExist = [fileMgr fileExistsAtPath:logsFolderPath isDirectory:&isDir];
if (!doesFolderExist) {
// make the directory
doesFolderExist = [fileMgr createDirectoryAtPath:logsFolderPath
withIntermediateDirectories:YES
attributes:nil
error:NULL];
if (doesFolderExist) {
// The directory has been created. Exclude it from backups.
NSURL *pathURL = [NSURL fileURLWithPath:logsFolderPath isDirectory:YES];
[pathURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:NULL];
// it's there; store it in the global
gLoggingDirectoryPath = [logsFolderPath copy];
}
}
if (doesFolderExist) {
// it's there; store it in the global
gLoggingDirectoryPath = [logsFolderPath copy];
}
}
return gLoggingDirectoryPath;
}
return gLoggingDirectoryPath;
}
+ (void)setLogDirectoryForCurrentRun:(NSString *)logDirectoryForCurrentRun {
// Set the path for this run's logs.
gLogDirectoryForCurrentRun = [logDirectoryForCurrentRun copy];
@synchronized([GTMSessionFetcher class]) {
// Set the path for this run's logs.
gLogDirectoryForCurrentRun = [logDirectoryForCurrentRun copy];
}
}
+ (NSString *)logDirectoryForCurrentRun {
// make a directory for this run's logs, like SyncProto_logs_10-16_01-56-58PM
if (gLogDirectoryForCurrentRun) return gLogDirectoryForCurrentRun;
@synchronized([GTMSessionFetcher class]) {
// make a directory for this run's logs, like SyncProto_logs_10-16_01-56-58PM
if (gLogDirectoryForCurrentRun) return gLogDirectoryForCurrentRun;
NSString *parentDir = [self loggingDirectory];
NSString *logNamePrefix = [self processNameLogPrefix];
NSString *dateStamp = [self loggingDateStamp];
NSString *dirName = [NSString stringWithFormat:@"%@%@", logNamePrefix, dateStamp];
NSString *logDirectory = [parentDir stringByAppendingPathComponent:dirName];
NSString *parentDir = [self loggingDirectory];
NSString *logNamePrefix = [self processNameLogPrefix];
NSString *dateStamp = [self loggingDateStamp];
NSString *dirName = [NSString stringWithFormat:@"%@%@", logNamePrefix, dateStamp];
NSString *logDirectory = [parentDir stringByAppendingPathComponent:dirName];
if (gIsLoggingToFile) {
NSFileManager *fileMgr = [NSFileManager defaultManager];
// Be sure that the first time this app runs, it's not writing to a preexisting folder
static BOOL gShouldReuseFolder = NO;
if (!gShouldReuseFolder) {
gShouldReuseFolder = YES;
NSString *origLogDir = logDirectory;
for (int ctr = 2; ctr < 20; ++ctr) {
if (![fileMgr fileExistsAtPath:logDirectory]) break;
if (gIsLoggingToFile) {
NSFileManager *fileMgr = [NSFileManager defaultManager];
// Be sure that the first time this app runs, it's not writing to a preexisting folder
static BOOL gShouldReuseFolder = NO;
if (!gShouldReuseFolder) {
gShouldReuseFolder = YES;
NSString *origLogDir = logDirectory;
for (int ctr = 2; ctr < 20; ++ctr) {
if (![fileMgr fileExistsAtPath:logDirectory]) break;
// append a digit
logDirectory = [origLogDir stringByAppendingFormat:@"_%d", ctr];
// append a digit
logDirectory = [origLogDir stringByAppendingFormat:@"_%d", ctr];
}
}
if (![fileMgr createDirectoryAtPath:logDirectory
withIntermediateDirectories:YES
attributes:nil
error:NULL])
return nil;
}
if (![fileMgr createDirectoryAtPath:logDirectory
withIntermediateDirectories:YES
attributes:nil
error:NULL])
return nil;
}
gLogDirectoryForCurrentRun = logDirectory;
gLogDirectoryForCurrentRun = logDirectory;
return gLogDirectoryForCurrentRun;
return gLogDirectoryForCurrentRun;
}
}
+ (void)setLoggingEnabled:(BOOL)isLoggingEnabled {
gIsLoggingEnabled = isLoggingEnabled;
@synchronized([GTMSessionFetcher class]) {
gIsLoggingEnabled = isLoggingEnabled;
}
}
+ (BOOL)isLoggingEnabled {
return gIsLoggingEnabled;
@synchronized([GTMSessionFetcher class]) {
return gIsLoggingEnabled;
}
}
+ (void)setLoggingToFileEnabled:(BOOL)isLoggingToFileEnabled {
gIsLoggingToFile = isLoggingToFileEnabled;
@synchronized([GTMSessionFetcher class]) {
gIsLoggingToFile = isLoggingToFileEnabled;
}
}
+ (BOOL)isLoggingToFileEnabled {
return gIsLoggingToFile;
@synchronized([GTMSessionFetcher class]) {
return gIsLoggingToFile;
}
}
+ (void)setLoggingProcessName:(NSString *)processName {
gLoggingProcessName = [processName copy];
@synchronized([GTMSessionFetcher class]) {
gLoggingProcessName = [processName copy];
}
}
+ (NSString *)loggingProcessName {
// get the process name (once per run) replacing spaces with underscores
if (!gLoggingProcessName) {
NSString *procName = [[NSProcessInfo processInfo] processName];
gLoggingProcessName = [procName stringByReplacingOccurrencesOfString:@" " withString:@"_"];
@synchronized([GTMSessionFetcher class]) {
// get the process name (once per run) replacing spaces with underscores
if (!gLoggingProcessName) {
NSString *procName = [[NSProcessInfo processInfo] processName];
gLoggingProcessName = [procName stringByReplacingOccurrencesOfString:@" " withString:@"_"];
}
return gLoggingProcessName;
}
return gLoggingProcessName;
}
+ (void)setLoggingDateStamp:(NSString *)dateStamp {
gLoggingDateStamp = [dateStamp copy];
@synchronized([GTMSessionFetcher class]) {
gLoggingDateStamp = [dateStamp copy];
}
}
+ (NSString *)loggingDateStamp {
// We'll pick one date stamp per run, so a run that starts at a later second
// will get a unique results html file
if (!gLoggingDateStamp) {
// produce a string like 08-21_01-41-23PM
@synchronized([GTMSessionFetcher class]) {
// We'll pick one date stamp per run, so a run that starts at a later second
// will get a unique results html file
if (!gLoggingDateStamp) {
// produce a string like 08-21_01-41-23PM
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setFormatterBehavior:NSDateFormatterBehavior10_4];
[formatter setDateFormat:@"M-dd_hh-mm-ssa"];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setFormatterBehavior:NSDateFormatterBehavior10_4];
[formatter setDateFormat:@"M-dd_hh-mm-ssa"];
gLoggingDateStamp = [formatter stringFromDate:[NSDate date]];
gLoggingDateStamp = [formatter stringFromDate:[NSDate date]];
}
return gLoggingDateStamp;
}
return gLoggingDateStamp;
}
+ (NSString *)processNameLogPrefix {

View File

@@ -25,6 +25,6 @@
(nonnull NS_NOESCAPE GTMSessionFetcherSessionCreationBlock)creationBlock;
- (nullable id<NSURLSessionDelegate>)sessionDelegate;
- (nullable NSDate *)stoppedAllFetchersDate;
- (void)fetcherDidStop:(nonnull GTMSessionFetcher *)fetcher callbacksPending: (BOOL)callbacksPending;
- (void)fetcherDidStop:(nonnull GTMSessionFetcher *)fetcher callbacksPending:(BOOL)callbacksPending;
@end

View File

@@ -37,9 +37,7 @@ static id<GTMUserAgentProvider> SharedStandardUserAgentProvider(void) {
#if !GTMSESSION_BUILD_COMBINED_SOURCES
@interface GTMSessionFetcher (ServiceMethods)
- (BOOL)beginFetchMayDelay:(BOOL)mayDelay
mayAuthorize:(BOOL)mayAuthorize
mayDecorate:(BOOL)mayDecorate;
- (void)serviceRestartingFetcher;
@end
#endif // !GTMSESSION_BUILD_COMBINED_SOURCES
@@ -141,7 +139,8 @@ static id<GTMUserAgentProvider> SharedStandardUserAgentProvider(void) {
unusedSessionTimeout = _unusedSessionTimeout,
userAgentProvider = _userAgentProvider,
decoratorsPointerArray = _decoratorsPointerArray,
testBlock = _testBlock;
testBlock = _testBlock,
stopFetchingTriggersCompletionHandler = _stopFetchingTriggersCompletionHandler;
// clang-format on
#if GTM_BACKGROUND_TASK_FETCHING
@@ -218,9 +217,8 @@ static id<GTMUserAgentProvider> SharedStandardUserAgentProvider(void) {
fetcher.retryBlock = self.retryBlock;
fetcher.maxRetryInterval = self.maxRetryInterval;
fetcher.minRetryInterval = self.minRetryInterval;
if (@available(iOS 10.0, *)) {
fetcher.metricsCollectionBlock = self.metricsCollectionBlock;
}
fetcher.metricsCollectionBlock = self.metricsCollectionBlock;
fetcher.stopFetchingTriggersCompletionHandler = self.stopFetchingTriggersCompletionHandler;
fetcher.properties = self.properties;
fetcher.service = self;
@@ -338,6 +336,7 @@ static id<GTMUserAgentProvider> SharedStandardUserAgentProvider(void) {
#pragma mark Queue Management
- (void)addRunningFetcher:(GTMSessionFetcher *)fetcher forHost:(NSString *)host {
GTMSessionCheckSynchronized(self);
// Add to the array of running fetchers for this host, creating the array if needed.
NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host];
if (runningForHost == nil) {
@@ -349,6 +348,7 @@ static id<GTMUserAgentProvider> SharedStandardUserAgentProvider(void) {
}
- (void)addDelayedFetcher:(GTMSessionFetcher *)fetcher forHost:(NSString *)host {
GTMSessionCheckSynchronized(self);
// Add to the array of delayed fetchers for this host, creating the array if needed.
NSMutableArray *delayedForHost = [_delayedFetchersByHost objectForKey:host];
if (delayedForHost == nil) {
@@ -421,10 +421,6 @@ static id<GTMUserAgentProvider> SharedStandardUserAgentProvider(void) {
return shouldBeginResult;
}
- (void)startFetcher:(GTMSessionFetcher *)fetcher {
[fetcher beginFetchMayDelay:NO mayAuthorize:YES mayDecorate: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:
@@ -486,7 +482,7 @@ static id<GTMUserAgentProvider> SharedStandardUserAgentProvider(void) {
[self fetcherDidStop:fetcher callbacksPending:false];
}
- (void)fetcherDidStop:(GTMSessionFetcher *)fetcher callbacksPending:(BOOL) callbacksPending {
- (void)fetcherDidStop:(GTMSessionFetcher *)fetcher callbacksPending:(BOOL)callbacksPending {
// Entry point from the fetcher
NSString *host = fetcher.serviceHost;
if (!host) {
@@ -498,7 +494,7 @@ static id<GTMUserAgentProvider> SharedStandardUserAgentProvider(void) {
// map when the task completes.
if (!callbacksPending) {
GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher =
[self delegateDispatcherForFetcher:fetcher];
[self delegateDispatcherForFetcher:fetcher];
[delegateDispatcher removeFetcher:fetcher];
}
@@ -554,7 +550,7 @@ static id<GTMUserAgentProvider> SharedStandardUserAgentProvider(void) {
// Start fetchers outside of the synchronized block to avoid a deadlock.
for (GTMSessionFetcher *nextFetcher in fetchersToStart) {
[self startFetcher:nextFetcher];
[nextFetcher serviceRestartingFetcher];
}
// The fetcher is no longer in the running or the delayed array,
@@ -657,6 +653,8 @@ static id<GTMUserAgentProvider> SharedStandardUserAgentProvider(void) {
[_runningFetchersByHost removeAllObjects];
}
// Stop the delayed fetchers first so a delayed one doesn't get started when canceling
// a running one.
for (NSArray *delayedForHost in delayedFetchersByHost) {
for (GTMSessionFetcher *fetcher in delayedForHost) {
[self stopFetcher:fetcher];
@@ -1331,8 +1329,7 @@ static id<GTMUserAgentProvider> SharedStandardUserAgentProvider(void) {
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
API_AVAILABLE(ios(10.0), macosx(10.12), tvos(10.0), watchos(6.0)) {
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics {
id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
[fetcher URLSession:session task:task didFinishCollectingMetrics:metrics];
}

View File

@@ -300,8 +300,17 @@ NSString *const kGTMSessionFetcherUploadInitialBackoffStartedNotification =
// Set the upload file length before setting the upload file URL tries to determine the length.
[uploadFetcher setUploadFileLength:uploadFileLength];
// Remove the private keys (or any invalid keys) from the sessionUserInfo to maintain that api's
// type contract.
NSSet *keysToRemove = [metadata keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) {
return ![key isKindOfClass:[NSString class]] || ![obj isKindOfClass:[NSString class]] ||
[key hasPrefix:@"_"];
}];
NSMutableDictionary *sessionUserInfo = [metadata mutableCopy];
[sessionUserInfo removeObjectsForKeys:[keysToRemove allObjects]];
uploadFetcher.sessionUserInfo = sessionUserInfo;
uploadFetcher.uploadFileURL = uploadFileURL;
uploadFetcher.sessionUserInfo = metadata;
uploadFetcher.useBackgroundSession = YES;
uploadFetcher.currentOffset = currentOffset;
uploadFetcher.delegateCallbackQueue = uploadFetcher.callbackQueue;
@@ -332,6 +341,10 @@ NSString *const kGTMSessionFetcherUploadInitialBackoffStartedNotification =
return gUploadFetcherPointerArrayForBackgroundSessions;
}
+ (instancetype)fetcherWithSessionIdentifier:(NSString *)sessionIdentifier {
return [self uploadFetcherForSessionIdentifier:sessionIdentifier];
}
+ (instancetype)uploadFetcherForSessionIdentifier:(NSString *)sessionIdentifier {
GTMSESSION_ASSERT_DEBUG(sessionIdentifier != nil, @"Invalid session identifier");
NSArray *uploadFetchersForBackgroundSessions = [self uploadFetchersForBackgroundSessions];
@@ -790,7 +803,15 @@ NSString *const kGTMSessionFetcherUploadInitialBackoffStartedNotification =
}
if (offset > 0 || length < fullUploadLength) {
NSRange range = NSMakeRange((NSUInteger)offset, (NSUInteger)length);
resultData = [mappedData subdataWithRange:range];
@try {
resultData = [mappedData subdataWithRange:range];
} @catch (NSException *exception) {
NSString *errorMessage = exception.description;
GTMSESSION_ASSERT_DEBUG(NO, @"%@", errorMessage);
response(nil, kGTMSessionUploadFetcherUnknownFileSize,
[self uploadChunkUnavailableErrorWithDescription:errorMessage]);
return;
}
} else {
resultData = mappedData;
}

View File

@@ -503,7 +503,6 @@ typedef void (^GTMSessionFetcherRetryResponse)(BOOL shouldRetry);
typedef void (^GTMSessionFetcherRetryBlock)(BOOL suggestedWillRetry, NSError *_Nullable error,
GTMSessionFetcherRetryResponse response);
API_AVAILABLE(ios(10.0), macosx(10.12), tvos(10.0), watchos(6.0))
typedef void (^GTMSessionFetcherMetricsCollectionBlock)(NSURLSessionTaskMetrics *metrics);
typedef void (^GTMSessionFetcherTestResponse)(NSHTTPURLResponse *_Nullable response,
@@ -605,6 +604,13 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
#endif
// Completion handler passed to -[GTMFetcherDecoratorProtocol fetcherWillStart:completionHandler:].
// TODO(https://github.com/google/gtm-session-fetcher/issues/398): Uncomment this when the next
// major version bump happens, since this is an API breaking change for Swift clients.
//
// typedef void (^GTMFetcherDecoratorFetcherWillStartCompletionHandler)(NSURLRequest *_Nullable_result,
// NSError *_Nullable);
typedef void (^GTMFetcherDecoratorFetcherWillStartCompletionHandler)(NSURLRequest *_Nullable,
NSError *_Nullable);
@@ -867,6 +873,8 @@ __deprecated_msg("implement GTMSessionFetcherAuthorizer instead")
// The fetcher encodes information used to resume a session in the session identifier.
// This method, intended for internal use returns the encoded information. The sessionUserInfo
// dictionary is stored as identifier metadata.
// NOTE: This type is a lie and could be an issue for Swift. The values for private keys (prefixed
// with an underscore) aren't always strings; but changing the type is a breaking change.
- (nullable NSDictionary<NSString *, NSString *> *)sessionIdentifierMetadata;
#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
@@ -1070,9 +1078,7 @@ __deprecated_msg("implement GTMSessionFetcherAuthorizer instead")
// The optional block for collecting the metrics of the present session.
//
// This is called on the callback queue.
@property(atomic, copy, nullable)
GTMSessionFetcherMetricsCollectionBlock metricsCollectionBlock API_AVAILABLE(
ios(10.0), macosx(10.12), tvos(10.0), watchos(6.0));
@property(atomic, copy, nullable) GTMSessionFetcherMetricsCollectionBlock metricsCollectionBlock;
// Retry intervals must be strictly less than maxRetryInterval, else
// they will be limited to maxRetryInterval and no further retries will

View File

@@ -65,9 +65,8 @@ extern NSString *const kGTMSessionFetcherServiceSessionKey;
@property(atomic, assign) NSTimeInterval maxRetryInterval;
@property(atomic, assign) NSTimeInterval minRetryInterval;
@property(atomic, copy, nullable) NSDictionary<NSString *, id> *properties;
@property(atomic, copy, nullable)
GTMSessionFetcherMetricsCollectionBlock metricsCollectionBlock API_AVAILABLE(
ios(10.0), macosx(10.12), tvos(10.0), watchos(6.0));
@property(atomic, copy, nullable) GTMSessionFetcherMetricsCollectionBlock metricsCollectionBlock;
@property(atomic, assign) BOOL stopFetchingTriggersCompletionHandler;
#if GTM_BACKGROUND_TASK_FETCHING
@property(atomic, assign) BOOL skipBackgroundTask;

View File

@@ -161,7 +161,7 @@ typedef void (^GTMSessionUploadFetcherCancellationHandler)(GTMSessionFetcher *_N
//
// Unlike other callbacks, since this is related specifically to the stopFetching flow it is not
// cleared by stopFetching. It will instead clear itself after it is invoked or if the completion
// has occured before stopFetching is called.
// has occurred before stopFetching is called.
@property(atomic, copy, nullable) GTMSessionUploadFetcherCancellationHandler cancellationHandler;
// Exposed for testing only.