diff --git a/AWSCore/AWSCore.h b/AWSCore/AWSCore.h index 20d649a1486..4f64a314f9e 100644 --- a/AWSCore/AWSCore.h +++ b/AWSCore/AWSCore.h @@ -28,3 +28,5 @@ #import "STS.h" #import "CognitoIdentity.h" #import "MobileAnalytics.h" + +#import "Bolts.h" diff --git a/AWSCore/Authentication/AWSCredentialsProvider.h b/AWSCore/Authentication/AWSCredentialsProvider.h index 85de08e8e1b..d045aa8cf91 100644 --- a/AWSCore/Authentication/AWSCredentialsProvider.h +++ b/AWSCore/Authentication/AWSCredentialsProvider.h @@ -19,7 +19,8 @@ FOUNDATION_EXPORT NSString *const AWSCognitoCredentialsProviderErrorDomain; typedef NS_ENUM(NSInteger, AWSCognitoCredentialsProviderErrorType) { - AWSCognitoCredentialsProviderErrorUnknown + AWSCognitoCredentialsProviderErrorUnknown, + AWSCognitoCredentialsProviderIdentityIdIsNil, }; @class BFTask; diff --git a/AWSCore/Authentication/AWSCredentialsProvider.m b/AWSCore/Authentication/AWSCredentialsProvider.m index dc8c599c274..1522d5aaa86 100644 --- a/AWSCore/Authentication/AWSCredentialsProvider.m +++ b/AWSCore/Authentication/AWSCredentialsProvider.m @@ -17,9 +17,16 @@ #import "STS.h" #import "UICKeyChainStore.h" #import "AWSLogging.h" +#import "Bolts.h" NSString *const AWSCognitoCredentialsProviderErrorDomain = @"com.amazonaws.AWSCognitoCredentialsProviderErrorDomain"; +NSString *const AWSCredentialsProviderKeychainAccessKeyId = @"AccessKeyId"; +NSString *const AWSCredentialsProviderKeychainSecretAccessKey = @"SecretAccessKey"; +NSString *const AWSCredentialsProviderKeychainSessionToken = @"SessionToken"; +NSString *const AWSCredentialsProviderKeychainExpiration = @"Expiration"; +NSString *const AWSCredentialsProviderKeychainIdentityId = @"IdentityId"; + @interface AWSStaticCredentialsProvider() @property (nonatomic, strong) NSString *accessKey; @@ -120,22 +127,19 @@ - (BFTask *)refresh { if (task.result) { AWSSTSAssumeRoleWithWebIdentityResponse *wifResponse = task.result; @synchronized(self) { - [self.keychain setString:wifResponse.credentials.accessKeyId - forKey:@"accessKey"]; - [self.keychain setString:wifResponse.credentials.secretAccessKey - forKey:@"secretKey"]; - [self.keychain setString:wifResponse.credentials.sessionToken - forKey:@"sessionKey"]; - [self.keychain setString:[NSString stringWithFormat:@"%f", [wifResponse.credentials.expiration timeIntervalSince1970]] - forKey:@"expiration"]; + self.keychain[AWSCredentialsProviderKeychainAccessKeyId] = wifResponse.credentials.accessKeyId; + self.keychain[AWSCredentialsProviderKeychainSecretAccessKey] = wifResponse.credentials.secretAccessKey; + self.keychain[AWSCredentialsProviderKeychainSessionToken] = wifResponse.credentials.sessionToken; + self.keychain[AWSCredentialsProviderKeychainExpiration] = [NSString stringWithFormat:@"%f", [wifResponse.credentials.expiration timeIntervalSince1970]]; + [self.keychain synchronize]; } } else { // reset the values for the credentials @synchronized(self) { - [self.keychain removeItemForKey:@"accessKey"]; - [self.keychain removeItemForKey:@"secretKey"]; - [self.keychain removeItemForKey:@"sessionKey"]; - [self.keychain removeItemForKey:@"expiration"]; + self.keychain[AWSCredentialsProviderKeychainAccessKeyId] = nil; + self.keychain[AWSCredentialsProviderKeychainSecretAccessKey] = nil; + self.keychain[AWSCredentialsProviderKeychainSessionToken] = nil; + self.keychain[AWSCredentialsProviderKeychainExpiration] = nil; [self.keychain synchronize]; } } @@ -146,25 +150,25 @@ - (BFTask *)refresh { - (NSString *)accessKey { @synchronized(self) { - return [self.keychain stringForKey:@"accessKey"]; + return [self.keychain stringForKey:AWSCredentialsProviderKeychainAccessKeyId]; } } - (NSString *)secretKey { @synchronized(self) { - return [self.keychain stringForKey:@"secretKey"]; + return [self.keychain stringForKey:AWSCredentialsProviderKeychainSecretAccessKey]; } } - (NSString *)sessionKey { @synchronized(self) { - return [self.keychain stringForKey:@"sessionKey"]; + return [self.keychain stringForKey:AWSCredentialsProviderKeychainSessionToken]; } } - (NSDate *)expiration { @synchronized(self) { - NSString *expirationString = [self.keychain stringForKey:@"expiration"]; + NSString *expirationString = [self.keychain stringForKey:AWSCredentialsProviderKeychainExpiration]; if (expirationString) { return [NSDate dateWithTimeIntervalSince1970:[expirationString doubleValue]]; } else { @@ -181,6 +185,9 @@ @interface AWSCognitoCredentialsProvider() @property (nonatomic, strong) NSString *unAuthRoleArn; @property (nonatomic, strong) AWSSTS *sts; @property (nonatomic, strong) UICKeyChainStore *keychain; +@property (nonatomic, strong) BFExecutor *refreshExecutor; +@property (atomic, assign) int32_t count; +@property (nonatomic, strong) dispatch_semaphore_t semaphore; @end @@ -238,7 +245,7 @@ + (instancetype)credentialsWithRegionType:(AWSRegionType)regionType identityProvider:(id)identityProvider unauthRoleArn:(NSString *)unauthRoleArn authRoleArn:(NSString *)authRoleArn { - + AWSCognitoCredentialsProvider *credentials = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:regionType identityProvider:identityProvider unauthRoleArn:unauthRoleArn @@ -254,23 +261,23 @@ - (instancetype)initWithRegionType:(AWSRegionType)regionType unauthRoleArn:(NSString *)unauthRoleArn authRoleArn:(NSString *)authRoleArn logins:(NSDictionary *)logins { - + // check for a stored identity if one isn't explicitly set if (!identityId) { UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:[NSString stringWithFormat:@"%@.%@.%@", [NSBundle mainBundle].bundleIdentifier, [AWSCognitoCredentialsProvider class], identityPoolId]]; - identityId = keychain[@"identityId"]; + identityId = keychain[AWSCredentialsProviderKeychainIdentityId]; } - + AWSBasicCognitoIdentityProvider *identityProvider = [[AWSBasicCognitoIdentityProvider alloc] - initWithRegionType:regionType - identityId:identityId - accountId:accountId - identityPoolId:identityPoolId - logins:logins]; + initWithRegionType:regionType + identityId:identityId + accountId:accountId + identityPoolId:identityPoolId + logins:logins]; + - AWSCognitoCredentialsProvider *credentials = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:regionType - identityProvider:identityProvider + identityProvider:identityProvider unauthRoleArn:unauthRoleArn authRoleArn:authRoleArn]; @@ -278,137 +285,157 @@ - (instancetype)initWithRegionType:(AWSRegionType)regionType } - (instancetype)initWithRegionType:(AWSRegionType)regionType - identityProvider:(id) identityProvider + identityProvider:(id) identityProvider unauthRoleArn:(NSString *)unauthRoleArn authRoleArn:(NSString *)authRoleArn { if (self = [super init]) { + _refreshExecutor = [BFExecutor executorWithOperationQueue:[NSOperationQueue new]]; + _count = 0; + _semaphore = dispatch_semaphore_create(0); + _unAuthRoleArn = unauthRoleArn; _authRoleArn = authRoleArn; _identityProvider = identityProvider; - + // initialize keychain - name spaced by app bundle and identity pool id _keychain = [UICKeyChainStore keyChainStoreWithService:[NSString stringWithFormat:@"%@.%@.%@", [NSBundle mainBundle].bundleIdentifier, [AWSCognitoCredentialsProvider class], identityProvider.identityPoolId]]; if (identityProvider.identityId) { - _keychain[@"identityId"] = identityProvider.identityId; + _keychain[AWSCredentialsProviderKeychainIdentityId] = identityProvider.identityId; [_keychain synchronize]; } - + AWSAnonymousCredentialsProvider *credentialsProvider = [AWSAnonymousCredentialsProvider new]; AWSServiceConfiguration *configuration = [AWSServiceConfiguration configurationWithRegion:regionType credentialsProvider:credentialsProvider]; - + _sts = [[AWSSTS new] initWithConfiguration:configuration]; } - + return self; } - (BFTask *)refresh { - dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); - - return [[[[BFTask taskWithResult:nil] continueWithSuccessBlock:^id(BFTask *task) { - dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); - return [self.identityProvider refresh]; - }] continueWithSuccessBlock:^id(BFTask *task) { - self.keychain[@"identityId"] = self.identityProvider.identityId; - [self.keychain synchronize]; - - NSString *roleArn = self.unAuthRoleArn; - if ([self.identityProvider isAuthenticated]) { - roleArn = self.authRoleArn; - } + return [[[BFTask taskWithResult:nil] continueWithExecutor:self.refreshExecutor withSuccessBlock:^id(BFTask *task) { + self.count++; + if (self.count <= 1) { + return [[self.identityProvider refresh] continueWithSuccessBlock:^id(BFTask *task) { + // This should never happen, but just in case + if (!self.identityProvider.identityId) { + AWSLogError(@"In refresh, but identityId is nil."); + return [BFTask taskWithError:[NSError errorWithDomain:AWSCognitoCredentialsProviderErrorDomain + code:AWSCognitoCredentialsProviderIdentityIdIsNil + userInfo:@{NSLocalizedDescriptionKey: @"identityId shouldn't be nil"}] + ]; + } + + self.keychain[AWSCredentialsProviderKeychainIdentityId] = self.identityProvider.identityId; + [self.keychain synchronize]; - AWSSTSAssumeRoleWithWebIdentityRequest *webIdentityRequest = [AWSSTSAssumeRoleWithWebIdentityRequest new]; - webIdentityRequest.roleArn = roleArn; - webIdentityRequest.webIdentityToken = self.identityProvider.token; - webIdentityRequest.roleSessionName = @"iOS-Provider"; - return [[self.sts assumeRoleWithWebIdentity:webIdentityRequest] continueWithBlock:^id(BFTask *task) { - if (task.result) { - AWSSTSAssumeRoleWithWebIdentityResponse *webIdentityResponse = task.result; - @synchronized(self) { - self.keychain[@"accessKey"] = webIdentityResponse.credentials.accessKeyId; - self.keychain[@"secretKey"] = webIdentityResponse.credentials.secretAccessKey; - self.keychain[@"sessionKey"] = webIdentityResponse.credentials.sessionToken; - self.keychain[@"expiration"] = [NSString stringWithFormat:@"%f", [webIdentityResponse.credentials.expiration timeIntervalSince1970]]; - [self.keychain synchronize]; + NSString *roleArn = self.unAuthRoleArn; + if ([self.identityProvider isAuthenticated]) { + roleArn = self.authRoleArn; } - } else { - // reset the values for the credentials - [self clearCredentials]; - } - return task; - }]; + AWSSTSAssumeRoleWithWebIdentityRequest *webIdentityRequest = [AWSSTSAssumeRoleWithWebIdentityRequest new]; + webIdentityRequest.roleArn = roleArn; + webIdentityRequest.webIdentityToken = self.identityProvider.token; + webIdentityRequest.roleSessionName = @"iOS-Provider"; + return [[self.sts assumeRoleWithWebIdentity:webIdentityRequest] continueWithBlock:^id(BFTask *task) { + if (task.result) { + AWSSTSAssumeRoleWithWebIdentityResponse *webIdentityResponse = task.result; + @synchronized(self) { + self.keychain[AWSCredentialsProviderKeychainAccessKeyId] = webIdentityResponse.credentials.accessKeyId; + self.keychain[AWSCredentialsProviderKeychainSecretAccessKey] = webIdentityResponse.credentials.secretAccessKey; + self.keychain[AWSCredentialsProviderKeychainSessionToken] = webIdentityResponse.credentials.sessionToken; + self.keychain[AWSCredentialsProviderKeychainExpiration] = [NSString stringWithFormat:@"%f", [webIdentityResponse.credentials.expiration timeIntervalSince1970]]; + [self.keychain synchronize]; + } + } else { + // reset the values for the credentials + [self clearCredentials]; + } + + return task; + }]; + }]; + } else { + dispatch_semaphore_wait(self.semaphore, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)); + return [BFTask taskWithResult:nil]; + } }] continueWithBlock:^id(BFTask *task) { if (task.error) { AWSLogError(@"Unable to refresh. Error is [%@]", task.error); } - dispatch_semaphore_signal(semaphore); + self.count--; + dispatch_semaphore_signal(self.semaphore); return task; }]; } - (BFTask *)getIdentityId { - dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); - - return [[[BFTask taskWithResult:nil] continueWithSuccessBlock:^id(BFTask *task) { - dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); - return [self.identityProvider getIdentityId]; - }] continueWithBlock:^id(BFTask *task) { - self.keychain[@"identityId"] = self.identityProvider.identityId; + return [[self.identityProvider getIdentityId] continueWithSuccessBlock:^id(BFTask *task) { + // This should never happen, but just in case + if (!self.identityProvider.identityId) { + AWSLogError(@"In refresh, but identityId is nil."); + AWSLogError(@"Result from getIdentityId is %@", task.result); + return [BFTask taskWithError:[NSError errorWithDomain:AWSCognitoCredentialsProviderErrorDomain + code:AWSCognitoCredentialsProviderIdentityIdIsNil + userInfo:@{NSLocalizedDescriptionKey: @"identityId shouldn't be nil"}] + ]; + } + self.keychain[AWSCredentialsProviderKeychainIdentityId] = self.identityProvider.identityId; [self.keychain synchronize]; - dispatch_semaphore_signal(semaphore); - return nil; + return task; }]; } - (void)clearKeychain { @synchronized(self) { [self.identityProvider clear]; - [self.keychain removeItemForKey:@"identityId"]; + self.keychain[AWSCredentialsProviderKeychainIdentityId] = nil; [self clearCredentials]; } } - (void)clearCredentials { @synchronized(self) { - [self.keychain removeItemForKey:@"accessKey"]; - [self.keychain removeItemForKey:@"secretKey"]; - [self.keychain removeItemForKey:@"sessionKey"]; - [self.keychain removeItemForKey:@"expiration"]; + self.keychain[AWSCredentialsProviderKeychainAccessKeyId] = nil; + self.keychain[AWSCredentialsProviderKeychainSecretAccessKey] = nil; + self.keychain[AWSCredentialsProviderKeychainSessionToken] = nil; + self.keychain[AWSCredentialsProviderKeychainExpiration] = nil; [self.keychain synchronize]; } } - (NSString *)identityId { @synchronized(self) { - return [self.keychain stringForKey:@"identityId"]; + return [self.keychain stringForKey:AWSCredentialsProviderKeychainIdentityId]; } } - (NSString *)accessKey { @synchronized(self) { - return self.keychain[@"accessKey"]; + return self.keychain[AWSCredentialsProviderKeychainAccessKeyId]; } } - (NSString *)secretKey { @synchronized(self) { - return self.keychain[@"secretKey"]; + return self.keychain[AWSCredentialsProviderKeychainSecretAccessKey]; } } - (NSString *)sessionKey { @synchronized(self) { - return self.keychain[@"sessionKey"]; + return self.keychain[AWSCredentialsProviderKeychainSessionToken]; } } - (NSDate *)expiration { @synchronized(self) { - NSString *expirationString = self.keychain[@"expiration"]; + NSString *expirationString = self.keychain[AWSCredentialsProviderKeychainExpiration]; if (expirationString) { return [NSDate dateWithTimeIntervalSince1970:[expirationString doubleValue]]; } else { diff --git a/AWSCore/Authentication/AWSIdentityProvider.h b/AWSCore/Authentication/AWSIdentityProvider.h index 7ce6c214489..b3adec54f71 100644 --- a/AWSCore/Authentication/AWSIdentityProvider.h +++ b/AWSCore/Authentication/AWSIdentityProvider.h @@ -26,6 +26,11 @@ typedef NS_ENUM(NSInteger, AWSCognitoLoginProviderKey) { AWSCognitoLoginProviderKeyLoginWithAmazon, }; +FOUNDATION_EXPORT NSString *const AWSCognitoIdentityProviderErrorDomain; +typedef NS_ENUM(NSInteger, AWSCognitoIdentityProviderErrorType) { + AWSCognitoIdentityProviderErrorIdentityIsNil, +}; + @class BFTask; @protocol AWSIdentityProvider diff --git a/AWSCore/Authentication/AWSIdentityProvider.m b/AWSCore/Authentication/AWSIdentityProvider.m index 6f543f7daed..fe72beb2faa 100644 --- a/AWSCore/Authentication/AWSIdentityProvider.m +++ b/AWSCore/Authentication/AWSIdentityProvider.m @@ -15,8 +15,10 @@ #import "AWSCore.h" #import "AWSIdentityProvider.h" +#import "Bolts.h" NSString *const AWSCognitoIdentityIdChangedNotification = @"com.amazonaws.services.cognitoidentity.AWSCognitoIdentityIdChangedNotification"; +NSString *const AWSCognitoIdentityProviderErrorDomain = @"com.amazonaws.service.cognitoidentity.AWSCognitoIdentityProvider"; NSString *const AWSCognitoNotificationPreviousId = @"PREVID"; NSString *const AWSCognitoNotificationNewId = @"NEWID"; @@ -49,7 +51,7 @@ - (void)clear { } - (BOOL)isAuthenticated { - return [self.logins count] > 0; + return self.logins != nil && [self.logins count] > 0; } - (void)setLogins:(NSDictionary *)logins { @@ -93,6 +95,10 @@ - (NSDictionary *)updateKeysForLogins:(NSDictionary *)logins { mutableLogin[updatedKey] = logins[key]; } + if ([mutableLogin count] == 0) { + return nil; + } + return mutableLogin; } @@ -115,6 +121,9 @@ - (void)postIdentityIdChangedNotification:(NSString *)newId { @interface AWSBasicCognitoIdentityProvider() @property (nonatomic, strong) NSString *accountId; @property (nonatomic, strong) AWSCognitoIdentity *cib; +@property (nonatomic, strong) BFExecutor *executor; +@property (atomic, assign) int32_t count; +@property (nonatomic, strong) dispatch_semaphore_t semaphore; @end @implementation AWSBasicCognitoIdentityProvider @@ -128,6 +137,9 @@ - (instancetype)initWithRegionType:(AWSRegionType)regionType if (self = [super init]) { _accountId = accountId; + _executor = [BFExecutor executorWithOperationQueue:[NSOperationQueue new]]; + _count = 0; + _semaphore = dispatch_semaphore_create(0); self.identityPoolId = identityPoolId; self.identityId = identityId; self.logins = [self updateKeysForLogins:logins]; @@ -144,51 +156,79 @@ - (instancetype)initWithRegionType:(AWSRegionType)regionType - (BFTask *)getIdentityId { if (self.identityId) { - return [BFTask taskWithResult:nil]; + return [BFTask taskWithResult:self.identityId]; } else { - return [[BFTask taskWithResult:nil] continueWithBlock:^id(BFTask *task) { - - if (!self.identityId) { + return [[[BFTask taskWithResult:nil] continueWithExecutor:self.executor withBlock:^id(BFTask *task) { + self.count++; + if (self.count <= 1) { AWSCognitoIdentityGetIdInput *getIdInput = [AWSCognitoIdentityGetIdInput new]; getIdInput.accountId = self.accountId; getIdInput.identityPoolId = self.identityPoolId; getIdInput.logins = self.logins; - return [[self.cib getId:getIdInput] continueWithBlock:^id(BFTask *task) { - if (task.error) { - AWSLogError(@"GetId failed. Error is [%@]", task.error); - } else { - AWSCognitoIdentityGetIdResponse *getIdResponse = task.result; - self.identityId = getIdResponse.identityId; - } - return nil; - }]; + return [self.cib getId:getIdInput]; } - return nil; + else { + dispatch_semaphore_wait(self.semaphore, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)); + return nil; + } + }] continueWithBlock:^id(BFTask *task) { + self.count--; + dispatch_semaphore_signal(self.semaphore); + if (task.error) { + AWSLogError(@"GetId failed. Error is [%@]", task.error); + return task; + } else if (task.result) { + AWSCognitoIdentityGetIdResponse *getIdResponse = task.result; + self.identityId = getIdResponse.identityId; + } + return [BFTask taskWithResult:self.identityId]; }]; } } - (BFTask *)refresh { return [[[self getIdentityId] continueWithSuccessBlock:^id(BFTask *task) { + // This should never happen, but just in case + if (!self.identityId) { + AWSLogError(@"In refresh, but identitId is nil."); + AWSLogError(@"Result from getIdentityId is %@", task.result); + return [BFTask taskWithError:[NSError errorWithDomain:AWSCognitoIdentityProviderErrorDomain + code:AWSCognitoIdentityProviderErrorIdentityIsNil + userInfo:@{NSLocalizedDescriptionKey: @"identityId shouldn't be nil"}] + ]; + } + AWSCognitoIdentityGetOpenIdTokenInput *getTokenInput = [AWSCognitoIdentityGetOpenIdTokenInput new]; getTokenInput.identityId = self.identityId; getTokenInput.logins = self.logins; + return [[self.cib getOpenIdToken:getTokenInput] continueWithBlock:^id(BFTask *task) { // When an invalid identityId is cached in the keychain for auth, // we will refresh the identityId and try to get OpenID token again. if (task.error) { + AWSLogError(@"GetOpenIdToken failed. Error is [%@]", task.error); + // if it's unauth, just fail out - if (!self.logins) { + if (![self isAuthenticated]) { return task; } - AWSLogError(@"GetOpenIdToken failed. Error is [%@]", task.error); - AWSLogVerbose(@"Calling GetId"); + AWSLogVerbose(@"Resetting identity Id and calling getIdentityId"); // if it's auth, reset id and refetch self.identityId = nil; return [[self getIdentityId] continueWithSuccessBlock:^id(BFTask *task) { + // This should never happen, but just in case + if (!self.identityId) { + AWSLogError(@"In refresh, but identitId is nil."); + AWSLogError(@"Result from getIdentityId is %@", task.result); + return [BFTask taskWithError:[NSError errorWithDomain:AWSCognitoIdentityProviderErrorDomain + code:AWSCognitoIdentityProviderErrorIdentityIsNil + userInfo:@{NSLocalizedDescriptionKey: @"identityId shouldn't be nil"}] + ]; + } + AWSLogVerbose(@"Retrying GetOpenIdToken"); // retry get token @@ -206,6 +246,15 @@ - (BFTask *)refresh { self.token = getTokenResponse.token; NSString *identityIdFromToken = getTokenResponse.identityId; + // This should never happen, but just in case + if (!identityIdFromToken) { + AWSLogError(@"identityId from getOpenIdToken is nil"); + return [BFTask taskWithError:[NSError errorWithDomain:AWSCognitoIdentityProviderErrorDomain + code:AWSCognitoIdentityProviderErrorIdentityIsNil + userInfo:@{NSLocalizedDescriptionKey: @"identityId shouldn't be nil"}] + ]; + } + if (![self.identityId isEqualToString:identityIdFromToken]) { self.identityId = identityIdFromToken; } diff --git a/AWSCore/Authentication/AWSSignature.m b/AWSCore/Authentication/AWSSignature.m index 5cfe7bfb9b1..98415b30dde 100644 --- a/AWSCore/Authentication/AWSSignature.m +++ b/AWSCore/Authentication/AWSSignature.m @@ -22,6 +22,7 @@ #import "AWSService.h" #import "AWSCredentialsProvider.h" #import "AWSLogging.h" +#import "Bolts.h" NSString *const AWSSigV4Marker = @"AWS4"; NSString *const AWSSigV4Algorithm = @"AWS4-HMAC-SHA256"; diff --git a/AWSCore/MobileAnalytics/core/http/AWSMobileAnalyticsDefaultHttpClient.m b/AWSCore/MobileAnalytics/core/http/AWSMobileAnalyticsDefaultHttpClient.m index 6b80c80b343..11c8283e5e8 100644 --- a/AWSCore/MobileAnalytics/core/http/AWSMobileAnalyticsDefaultHttpClient.m +++ b/AWSCore/MobileAnalytics/core/http/AWSMobileAnalyticsDefaultHttpClient.m @@ -13,6 +13,7 @@ * permissions and limitations under the License. */ +#import "Bolts.h" #import "AWSMobileAnalyticsDefaultHttpClient.h" #import "AWSCore.h" #import "AWSMobileAnalyticsInstanceIdInterceptor.h" @@ -147,17 +148,17 @@ -(void) addInterceptor: (id) theInterceptor putEventInput.events = parsedEventsArray; } + //Attach the request to the response + id request = [[AWSMobileAnalyticsDefaultRequest alloc] init]; + [request setUrl:ers.configuration.URL]; + response.originatingRequest = request; NSDate* requestStartDate = [NSDate date]; [[[ers putEvents:putEventInput] continueWithBlock:^id(BFTask *task) { NSDictionary *resultDictionary = nil; if (task.error) { - if (task.error.domain != AWSMobileAnalyticsERSErrorDomain || task.error.domain != AWSGeneralErrorDomain) { - //It is client side error, assign the error and return immediately - response.error = task.error; - return nil; - } + response.error = task.error; resultDictionary = task.error.userInfo; } else { if ([task.result isKindOfClass:[NSDictionary class]]) { diff --git a/AWSCore/MobileAnalytics/core/http/AWSMobileAnalyticsRequestTimingInterceptor.m b/AWSCore/MobileAnalytics/core/http/AWSMobileAnalyticsRequestTimingInterceptor.m index 53a73cc39e3..43adc8e0450 100644 --- a/AWSCore/MobileAnalytics/core/http/AWSMobileAnalyticsRequestTimingInterceptor.m +++ b/AWSCore/MobileAnalytics/core/http/AWSMobileAnalyticsRequestTimingInterceptor.m @@ -110,7 +110,7 @@ -(void) recordRequestTimeEventOnResponse:(id) theRes [recordEvent addAttribute:serverInfo forKey:@"serverInfo"]; } - [self.eventClient recordEvent:recordEvent andApplyGlobalAttributes:NO]; + [self.eventClient recordEvent:recordEvent andApplyGlobalAttributes:YES]; } } diff --git a/AWSCore/MobileAnalytics/delivery/AWSMobileAnalyticsDefaultDeliveryClient.m b/AWSCore/MobileAnalytics/delivery/AWSMobileAnalyticsDefaultDeliveryClient.m index ca3729311d9..0abd341744d 100644 --- a/AWSCore/MobileAnalytics/delivery/AWSMobileAnalyticsDefaultDeliveryClient.m +++ b/AWSCore/MobileAnalytics/delivery/AWSMobileAnalyticsDefaultDeliveryClient.m @@ -23,6 +23,7 @@ #import "AWSMobileAnalyticsSerializerFactory.h" #import "AWSMobileAnalyticsStringUtils.h" #import "AWSLogging.h" +#import "AWSMObileAnalyticsDefaultSessionClient.h" #import static NSSet* RETRY_REQUEST_CODES = nil; @@ -130,10 +131,30 @@ -(void) notify:(id) theEvent [self enqueueEventForDelivery:theEvent]; } +-(BOOL) validateEvent:(id) theEvent { + + if (![theEvent attributeForKey:AWSSessionIDAttributeKey]) { + AWSLogError(@"Event: '%@' Validation Error: %@ is nil",theEvent.eventType,AWSSessionIDAttributeKey); + return NO; + } + + if (![theEvent attributeForKey:AWSSessionStartTimeAttributeKey]) { + AWSLogError(@"Event '%@' Validation Error: %@ is nil",theEvent.eventType,AWSSessionStartTimeAttributeKey); + return NO; + } + + return YES; +} + -(void) enqueueEventForDelivery:(id) theEvent { if(self.operationQueue.operationCount >= MAX_OPERATIONS) { - AWSLogWarn(@"The event is being dropped because too many operations enqueued"); + AWSLogError(@"The event: '%@' is being dropped because too many operations enqueued.",theEvent.eventType); + return; + } + + if (![self validateEvent:theEvent]) { + AWSLogError(@"The event '%@'is being dropped because internal validation failed.",theEvent.eventType); return; } diff --git a/AWSCore/MobileAnalytics/session/AWSMobileAnalyticsInactiveSessionState.m b/AWSCore/MobileAnalytics/session/AWSMobileAnalyticsInactiveSessionState.m index 14724958e79..503ac59df72 100644 --- a/AWSCore/MobileAnalytics/session/AWSMobileAnalyticsInactiveSessionState.m +++ b/AWSCore/MobileAnalytics/session/AWSMobileAnalyticsInactiveSessionState.m @@ -26,9 +26,6 @@ -(void)resumeWithSessionClient:(AWSMobileAnalyticsDefaultSessionClient *)session } -(void)pauseWithSessionClient:(AWSMobileAnalyticsDefaultSessionClient *)sessionClient{ - id pauseEvent = [sessionClient.eventClient createInternalEvent:AWSSessionPauseEventType]; - [sessionClient.eventClient recordEvent:pauseEvent andApplyGlobalAttributes:YES]; - AWSLogVerbose( @"Session Pause Failed: No session is running."); } diff --git a/AWSCore/MobileAnalyticsERS/AWSMobileAnalyticsERS.m b/AWSCore/MobileAnalyticsERS/AWSMobileAnalyticsERS.m index 7416ecdcadb..bffb3e57386 100644 --- a/AWSCore/MobileAnalyticsERS/AWSMobileAnalyticsERS.m +++ b/AWSCore/MobileAnalyticsERS/AWSMobileAnalyticsERS.m @@ -81,40 +81,52 @@ - (id)responseObjectForResponse:(NSHTTPURLResponse *)response } if (!*error && [responseObject isKindOfClass:[NSDictionary class]]) { - NSString *errorTypeStr = [[response allHeaderFields] objectForKey:@"x-amzn-ErrorType"]; - NSString *errorTypeHeader = [[errorTypeStr componentsSeparatedByString:@":"] firstObject]; - - if ([errorTypeStr length] > 0 && errorTypeHeader) { - if (errorCodeDictionary[[[errorTypeHeader componentsSeparatedByString:@"#"] lastObject]]) { - if (error) { - NSMutableDictionary *userInfo = [@{ - NSLocalizedFailureReasonErrorKey : errorTypeStr, - @"responseStatusCode" : @([response statusCode]), - @"responseHeaders" : [response allHeaderFields], - @"responseDataSize" : @(data?[data length]:0), - } mutableCopy]; - [userInfo addEntriesFromDictionary:responseObject]; - *error = [NSError errorWithDomain:AWSMobileAnalyticsERSErrorDomain - code:[[errorCodeDictionary objectForKey:[[errorTypeHeader componentsSeparatedByString:@"#"] lastObject]] integerValue] - userInfo:userInfo]; - } - return responseObject; - } else if (errorTypeHeader) { - if (error) { - NSMutableDictionary *userInfo = [@{ - NSLocalizedFailureReasonErrorKey : errorTypeStr, - @"responseStatusCode" : @([response statusCode]), - @"responseHeaders" : [response allHeaderFields], - @"responseDataSize" : @(data?[data length]:0), - } mutableCopy]; - [userInfo addEntriesFromDictionary:responseObject]; - *error = [NSError errorWithDomain:AWSMobileAnalyticsERSErrorDomain - code:AWSMobileAnalyticsERSErrorUnknown - userInfo:userInfo]; - } - return responseObject; + NSString *errorTypeHeader = [[[[response allHeaderFields] objectForKey:@"x-amzn-ErrorType"] componentsSeparatedByString:@":"] firstObject]; + + //server may also return error message in the body, need to catch it. + if (errorTypeHeader == nil) { + errorTypeHeader = [responseObject objectForKey:@"__type"]; + } + + + if (errorCodeDictionary[[[errorTypeHeader componentsSeparatedByString:@"#"] lastObject]]) { + if (error) { + NSMutableDictionary *userInfo = [@{ + NSLocalizedFailureReasonErrorKey : errorTypeHeader, + @"responseStatusCode" : @([response statusCode]), + @"responseHeaders" : [response allHeaderFields], + @"responseDataSize" : @(data?[data length]:0), + } mutableCopy]; + [userInfo addEntriesFromDictionary:responseObject]; + *error = [NSError errorWithDomain:AWSMobileAnalyticsERSErrorDomain + code:[[errorCodeDictionary objectForKey:[[errorTypeHeader componentsSeparatedByString:@"#"] lastObject]] integerValue] + userInfo:userInfo]; + } + return responseObject; + } else if ([[errorTypeHeader componentsSeparatedByString:@"#"] lastObject]) { + if (error) { + NSMutableDictionary *userInfo = [@{ + NSLocalizedFailureReasonErrorKey : errorTypeHeader, + @"responseStatusCode" : @([response statusCode]), + @"responseHeaders" : [response allHeaderFields], + @"responseDataSize" : @(data?[data length]:0), + } mutableCopy]; + [userInfo addEntriesFromDictionary:responseObject]; + *error = [NSError errorWithDomain:AWSMobileAnalyticsERSErrorDomain + code:AWSMobileAnalyticsERSErrorUnknown + userInfo:userInfo]; + } + return responseObject; + } else if (response.statusCode/100 != 2) { + //should be an error if not a 2xx response. + if (error) { + *error = [NSError errorWithDomain:AWSMobileAnalyticsERSErrorDomain + code:AWSMobileAnalyticsERSErrorUnknown + userInfo:responseObject]; } + return responseObject; } + if (self.outputClass) { responseObject = [MTLJSONAdapter modelOfClass:self.outputClass diff --git a/AWSCore/Networking/AWSNetworking.h b/AWSCore/Networking/AWSNetworking.h index f1961ff5024..32d42235e3f 100644 --- a/AWSCore/Networking/AWSNetworking.h +++ b/AWSCore/Networking/AWSNetworking.h @@ -14,7 +14,6 @@ */ #import -#import "Bolts.h" #import "AWSModel.h" FOUNDATION_EXPORT NSString *const AWSNetworkingErrorDomain; @@ -33,6 +32,7 @@ typedef NS_ENUM(NSInteger, AWSNetworkingRetryType) { @class AWSNetworkingConfiguration; @class AWSNetworkingRequest; +@class BFTask; typedef void (^AWSNetworkingUploadProgressBlock) (int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend); typedef void (^AWSNetworkingDownloadProgressBlock) (int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite); diff --git a/AWSCore/Networking/AWSNetworking.m b/AWSCore/Networking/AWSNetworking.m index 3f87f92cd80..80ad219af6c 100644 --- a/AWSCore/Networking/AWSNetworking.m +++ b/AWSCore/Networking/AWSNetworking.m @@ -15,12 +15,13 @@ #import "AWSNetworking.h" #import +#import "Bolts.h" #import "AWSCategory.h" #import "AWSModel.h" #import "AWSURLSessionManager.h" NSString *const AWSNetworkingErrorDomain = @"com.amazonaws.AWSNetworkingErrorDomain"; -NSString *const AWSiOSSDKVersion = @"2.0.8"; +NSString *const AWSiOSSDKVersion = @"2.0.9"; #pragma mark - AWSHTTPMethod diff --git a/AWSCore/Networking/AWSURLSessionManager.h b/AWSCore/Networking/AWSURLSessionManager.h index 3343c308239..2d0a3cd2618 100644 --- a/AWSCore/Networking/AWSURLSessionManager.h +++ b/AWSCore/Networking/AWSURLSessionManager.h @@ -14,7 +14,6 @@ */ #import - #import "AWSNetworking.h" @interface AWSURLSessionManager : NSObject diff --git a/AWSCore/Networking/AWSURLSessionManager.m b/AWSCore/Networking/AWSURLSessionManager.m index b10676704b4..8fbd8aad07b 100644 --- a/AWSCore/Networking/AWSURLSessionManager.m +++ b/AWSCore/Networking/AWSURLSessionManager.m @@ -19,9 +19,12 @@ #import "AWSLogging.h" #import "AWSCategory.h" #import "AWSSignature.h" +#import "Bolts.h" #pragma mark - AWSURLSessionManagerDelegate +static NSString* const AWSMobileURLSessionManagerCacheDomain = @"com.amazonaws.AWSURLSessionManager"; + typedef NS_ENUM(NSInteger, AWSURLSessionTaskType) { AWSURLSessionTaskTypeUnknown, AWSURLSessionTaskTypeData, @@ -44,6 +47,7 @@ @interface AWSURLSessionManagerDelegate : NSObject @property (nonatomic, strong) NSFileHandle *responseFilehandle; @property (nonatomic, strong) NSURL *tempDownloadedFileURL; @property (nonatomic, assign) BOOL shouldWriteDirectly; +@property (nonatomic, assign) BOOL shouldWriteToFile; @property (atomic, assign) int64_t lastTotalLengthOfChunkSignatureSent; @property (atomic, assign) int64_t payloadTotalBytesWritten; @@ -56,7 +60,7 @@ - (instancetype)init { if (self = [super init]) { _taskType = AWSURLSessionTaskTypeUnknown; } - + return self; } @@ -87,21 +91,21 @@ - (instancetype)init { if (self = [super init]) { NSOperationQueue *operationQueue = [NSOperationQueue new]; operationQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount; - + NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; _session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:operationQueue]; _sessionManagerDelegates = [AWSSynchronizedMutableDictionary new]; } - + return self; } - (void)dataTaskWithRequest:(AWSNetworkingRequest *)request completionHandler:(AWSNetworkingCompletionHandlerBlock)completionHandler { [request assignProperties:self.configuration]; - + AWSURLSessionManagerDelegate *delegate = [AWSURLSessionManagerDelegate new]; delegate.dataTaskCompletionHandler = completionHandler; delegate.request = request; @@ -109,14 +113,14 @@ - (void)dataTaskWithRequest:(AWSNetworkingRequest *)request delegate.downloadingFileURL = request.downloadingFileURL; delegate.uploadingFileURL = request.uploadingFileURL; delegate.shouldWriteDirectly = request.shouldWriteDirectly; - + [self taskWithDelegate:delegate]; } - (void)downloadTaskWithRequest:(AWSNetworkingRequest *)request completionHandler:(AWSNetworkingCompletionHandlerBlock)completionHandler { [request assignProperties:self.configuration]; - + AWSURLSessionManagerDelegate *delegate = [AWSURLSessionManagerDelegate new]; delegate.dataTaskCompletionHandler = completionHandler; delegate.request = request; @@ -128,7 +132,7 @@ - (void)downloadTaskWithRequest:(AWSNetworkingRequest *)request - (void)uploadTaskWithRequest:(AWSNetworkingRequest *)request completionHandler:(AWSNetworkingCompletionHandlerBlock)completionHandler { [request assignProperties:self.configuration]; - + AWSURLSessionManagerDelegate *delegate = [AWSURLSessionManagerDelegate new]; delegate.dataTaskCompletionHandler = completionHandler; delegate.request = request; @@ -137,11 +141,12 @@ - (void)uploadTaskWithRequest:(AWSNetworkingRequest *)request } - (void)taskWithDelegate:(AWSURLSessionManagerDelegate *)delegate { + if (delegate.downloadingFileURL) delegate.shouldWriteToFile = YES; delegate.responseData = nil; delegate.responseObject = nil; delegate.error = nil; NSMutableURLRequest *mutableRequest = [NSMutableURLRequest requestWithURL:delegate.request.URL]; - + [[[[[[BFTask taskWithResult:nil] continueWithBlock:^id(BFTask *task) { id signer = [delegate.request.requestInterceptors lastObject]; if (signer) { @@ -149,23 +154,23 @@ - (void)taskWithDelegate:(AWSURLSessionManagerDelegate *)delegate { #pragma clang diagnostic ignored "-Wundeclared-selector" if ([signer respondsToSelector:@selector(credentialsProvider)]) { id credentialsProvider = [signer performSelector:@selector(credentialsProvider)]; - + if ([credentialsProvider respondsToSelector:@selector(refresh)]) { NSString *accessKey = nil; if ([credentialsProvider respondsToSelector:@selector(accessKey)]) { accessKey = [credentialsProvider performSelector:@selector(accessKey)]; } - + NSString *secretKey = nil; if ([credentialsProvider respondsToSelector:@selector(secretKey)]) { secretKey = [credentialsProvider performSelector:@selector(secretKey)]; } - + NSDate *expiration = nil; if ([credentialsProvider respondsToSelector:@selector(expiration)]) { expiration = [credentialsProvider performSelector:@selector(expiration)]; } - + /** Preemptively refresh credentials if any of the following is true: 1. accessKey or secretKey is nil. @@ -179,7 +184,7 @@ - (void)taskWithDelegate:(AWSURLSessionManagerDelegate *)delegate { } #pragma clang diagnostic pop } - + return nil; }] continueWithSuccessBlock:^id(BFTask *task) { AWSNetworkingRequest *request = delegate.request; @@ -192,19 +197,19 @@ - (void)taskWithDelegate:(AWSURLSessionManagerDelegate *)delegate { } return nil; } - + mutableRequest.HTTPMethod = [NSString aws_stringWithHTTPMethod:delegate.request.HTTPMethod]; - + if ([request.requestSerializer respondsToSelector:@selector(serializeRequest:headers:parameters:)]) { BFTask *resultTask = [request.requestSerializer serializeRequest:mutableRequest - headers:request.headers - parameters:request.parameters]; + headers:request.headers + parameters:request.parameters]; //if serialization has error, abort task. if (resultTask.error) { return resultTask; } } - + BFTask *sequencialTask = [BFTask taskWithResult:nil]; for(idinterceptor in request.requestInterceptors) { if ([interceptor respondsToSelector:@selector(interceptRequest:)]) { @@ -213,7 +218,7 @@ - (void)taskWithDelegate:(AWSURLSessionManagerDelegate *)delegate { }]; } } - + return task; }] continueWithSuccessBlock:^id(BFTask *task) { AWSNetworkingRequest *request = delegate.request; @@ -227,20 +232,20 @@ - (void)taskWithDelegate:(AWSURLSessionManagerDelegate *)delegate { case AWSURLSessionTaskTypeData: delegate.request.task = [self.session dataTaskWithRequest:mutableRequest]; break; - + case AWSURLSessionTaskTypeDownload: delegate.request.task = [self.session downloadTaskWithRequest:mutableRequest]; break; - + case AWSURLSessionTaskTypeUpload: delegate.request.task = [self.session uploadTaskWithRequest:mutableRequest fromFile:delegate.uploadingFileURL]; break; - + default: break; } - + if (delegate.request.task) { [self.sessionManagerDelegates setObject:delegate forKey:@(((NSURLSessionTask *)delegate.request.task).taskIdentifier)]; @@ -248,7 +253,7 @@ - (void)taskWithDelegate:(AWSURLSessionManagerDelegate *)delegate { } else { AWSLogError(@"Invalid AWSURLSessionTaskType."); } - + return nil; }] continueWithBlock:^id(BFTask *task) { if (task.error) { @@ -266,80 +271,86 @@ - (void)taskWithDelegate:(AWSURLSessionManagerDelegate *)delegate { - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)sessionTask didCompleteWithError:(NSError *)error { [[[BFTask taskWithResult:nil] continueWithSuccessBlock:^id(BFTask *task) { AWSURLSessionManagerDelegate *delegate = [self.sessionManagerDelegates objectForKey:@(sessionTask.taskIdentifier)]; - - if (delegate.downloadingFileURL) { + + if (delegate.responseFilehandle) { [delegate.responseFilehandle closeFile]; } - + if (!delegate.error) { delegate.error = error; } - - if (!delegate.error - && [sessionTask.response isKindOfClass:[NSHTTPURLResponse class]]) { - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)sessionTask.response; - if (delegate.downloadingFileURL) { - NSError *error = nil; - //move the downloaded file to user specified location if tempDownloadFileURL and downloadFileURL are different. - if ([delegate.tempDownloadedFileURL isEqual:delegate.downloadingFileURL] == NO) { - - if ([[NSFileManager defaultManager] fileExistsAtPath:delegate.downloadingFileURL.path]) { - AWSLogWarn(@"Warning: target file already exists, will be overwritten at the file path: %@",delegate.downloadingFileURL); - [[NSFileManager defaultManager] removeItemAtPath:delegate.downloadingFileURL.path error:&error]; + //delete temporary file if the task contains error (e.g. has been canceled) + if (error && delegate.tempDownloadedFileURL) { + [[NSFileManager defaultManager] removeItemAtPath:delegate.tempDownloadedFileURL.path error:nil]; + } + + + if (!delegate.error + && [sessionTask.response isKindOfClass:[NSHTTPURLResponse class]]) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)sessionTask.response; + + if (delegate.shouldWriteToFile) { + NSError *error = nil; + //move the downloaded file to user specified location if tempDownloadFileURL and downloadFileURL are different. + if (delegate.tempDownloadedFileURL && delegate.downloadingFileURL && [delegate.tempDownloadedFileURL isEqual:delegate.downloadingFileURL] == NO) { + + if ([[NSFileManager defaultManager] fileExistsAtPath:delegate.downloadingFileURL.path]) { + AWSLogWarn(@"Warning: target file already exists, will be overwritten at the file path: %@",delegate.downloadingFileURL); + [[NSFileManager defaultManager] removeItemAtPath:delegate.downloadingFileURL.path error:&error]; + } + if (error) { + AWSLogError(@"Delete File Error: [%@]",error); + } + error = nil; + [[NSFileManager defaultManager] moveItemAtURL:delegate.tempDownloadedFileURL + toURL:delegate.downloadingFileURL + error:&error]; } if (error) { - AWSLogError(@"Delete File Error: [%@]",error); + delegate.error = error; + } else { + if ([delegate.request.responseSerializer respondsToSelector:@selector(responseObjectForResponse:originalRequest:currentRequest:data:error:)]) { + NSError *error = nil; + delegate.responseObject = [delegate.request.responseSerializer responseObjectForResponse:httpResponse + originalRequest:sessionTask.originalRequest + currentRequest:sessionTask.currentRequest + data:delegate.downloadingFileURL + error:&error]; + if (error) { + delegate.error = error; + } + } + else { + delegate.responseObject = delegate.downloadingFileURL; + } } - error = nil; - [[NSFileManager defaultManager] moveItemAtURL:delegate.tempDownloadedFileURL - toURL:delegate.downloadingFileURL - error:&error]; - } - if (error) { - delegate.error = error; - } else { + } else if (!delegate.error) { + // need to call responseSerializer if there is no client-side error. if ([delegate.request.responseSerializer respondsToSelector:@selector(responseObjectForResponse:originalRequest:currentRequest:data:error:)]) { NSError *error = nil; delegate.responseObject = [delegate.request.responseSerializer responseObjectForResponse:httpResponse originalRequest:sessionTask.originalRequest currentRequest:sessionTask.currentRequest - data:delegate.downloadingFileURL + data:delegate.responseData error:&error]; if (error) { delegate.error = error; } } else { - delegate.responseObject = delegate.downloadingFileURL; - } - } - } else if (!delegate.error) { - // need to call responseSerializer if there is no client-side error. - if ([delegate.request.responseSerializer respondsToSelector:@selector(responseObjectForResponse:originalRequest:currentRequest:data:error:)]) { - NSError *error = nil; - delegate.responseObject = [delegate.request.responseSerializer responseObjectForResponse:httpResponse - originalRequest:sessionTask.originalRequest - currentRequest:sessionTask.currentRequest - data:delegate.responseData - error:&error]; - if (error) { - delegate.error = error; + delegate.responseObject = delegate.responseData; } } - else { - delegate.responseObject = delegate.responseData; - } } - } - + if (delegate.error && ([sessionTask.response isKindOfClass:[NSHTTPURLResponse class]] || sessionTask.response == nil) && delegate.request.retryHandler) { AWSNetworkingRetryType retryType = [delegate.request.retryHandler shouldRetry:delegate.currentRetryCount - response:(NSHTTPURLResponse *)sessionTask.response - data:delegate.responseData - error:delegate.error]; + response:(NSHTTPURLResponse *)sessionTask.response + data:delegate.responseData + error:delegate.error]; switch (retryType) { case AWSNetworkingRetryTypeShouldCorrectClockSkewAndRetry: { //Correct Clock Skew @@ -359,16 +370,16 @@ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)sessionTask } } } - + if (serverTime) { NSDate *deviceTime = [NSDate date]; NSTimeInterval skewTime = [deviceTime timeIntervalSinceDate:serverTime]; [NSDate aws_setRuntimeClockSkew:skewTime]; } - + } } - + case AWSNetworkingRetryTypeShouldRefreshCredentialsAndRetry: { id signer = [delegate.request.requestInterceptors lastObject]; #pragma clang diagnostic push @@ -381,7 +392,7 @@ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)sessionTask } #pragma clang diagnostic pop } - + case AWSNetworkingRetryTypeShouldRetry: { NSTimeInterval timeIntervalToSleep = [delegate.request.retryHandler timeIntervalForRetry:delegate.currentRetryCount response:(NSHTTPURLResponse *)sessionTask.response @@ -392,7 +403,7 @@ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)sessionTask [self taskWithDelegate:delegate]; } break; - + case AWSNetworkingRetryTypeShouldNotRetry: { if (delegate.dataTaskCompletionHandler) { AWSNetworkingCompletionHandlerBlock completionHandler = delegate.dataTaskCompletionHandler; @@ -400,7 +411,7 @@ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)sessionTask } } break; - + default: AWSLogError(@"Unknown retry type. This should not happen."); NSAssert(NO, @"Unknown retry type. This should not happen."); @@ -412,7 +423,7 @@ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)sessionTask if ([[retryHandler valueForKey:@"isClockSkewRetried"] boolValue]) { [retryHandler setValue:@NO forKey:@"isClockSkewRetried"]; } - + if (delegate.dataTaskCompletionHandler) { AWSNetworkingCompletionHandlerBlock completionHandler = delegate.dataTaskCompletionHandler; completionHandler(delegate.responseObject, delegate.error); @@ -453,39 +464,57 @@ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSend - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { AWSURLSessionManagerDelegate *delegate = [self.sessionManagerDelegates objectForKey:@(dataTask.taskIdentifier)]; - if (delegate.downloadingFileURL) { - + + //If the response code is not 2xx, avoid write data to disk + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + if (httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 ) { + // status is good, we can keep value of shouldWriteToFile + } else { + // got error status code, avoid write data to disk + delegate.shouldWriteToFile = NO; + } + } + if (delegate.shouldWriteToFile) { + if (delegate.shouldWriteDirectly) { //If set (e..g by S3 Transfer Manager), downloaded data will be wrote to the downloadingFileURL directly, if the file already exists, it will appended to the end. AWSLogDebug(@"DirectWrite is On, downloaded data will be wrote to the downloadingFileURL directly, if the file already exists, it will appended to the end.\ - Original file may be modified even the downloading task has been paused/cancelled later."); - delegate.tempDownloadedFileURL = delegate.downloadingFileURL; + Original file may be modified even the downloading task has been paused/cancelled later."); + NSError *error = nil; - if ([[NSFileManager defaultManager] fileExistsAtPath:delegate.tempDownloadedFileURL.path]) { - AWSLogDebug(@"target file already exists, will be appended at the file path: %@",delegate.tempDownloadedFileURL); - delegate.responseFilehandle = [NSFileHandle fileHandleForUpdatingURL:delegate.tempDownloadedFileURL error:&error]; + if ([[NSFileManager defaultManager] fileExistsAtPath:delegate.downloadingFileURL.path]) { + AWSLogDebug(@"target file already exists, will be appended at the file path: %@",delegate.downloadingFileURL); + delegate.responseFilehandle = [NSFileHandle fileHandleForUpdatingURL:delegate.downloadingFileURL error:&error]; if (error) { AWSLogError(@"Error: [%@]", error); } [delegate.responseFilehandle seekToEndOfFile]; - + } else { //Create the file - if (![[NSFileManager defaultManager] createFileAtPath:delegate.tempDownloadedFileURL.path contents:nil attributes:nil]) { - AWSLogError(@"Error: Can not create file with file path:%@",delegate.tempDownloadedFileURL.path); + if (![[NSFileManager defaultManager] createFileAtPath:delegate.downloadingFileURL.path contents:nil attributes:nil]) { + AWSLogError(@"Error: Can not create file with file path:%@",delegate.downloadingFileURL.path); } error = nil; - delegate.responseFilehandle = [NSFileHandle fileHandleForWritingToURL:delegate.tempDownloadedFileURL error:&error]; + delegate.responseFilehandle = [NSFileHandle fileHandleForWritingToURL:delegate.downloadingFileURL error:&error]; if (error) { AWSLogError(@"Error: [%@]", error); } } - + } else { - //This is the normal case. downloaded data will be saved in a temporay folder and then moved to downloadingFileURL after downloading complete. - NSString *tempFileName = [[NSProcessInfo processInfo] globallyUniqueString]; - delegate.tempDownloadedFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:tempFileName]]; NSError *error = nil; + //This is the normal case. downloaded data will be saved in a temporay folder and then moved to downloadingFileURL after downloading complete. + NSString *tempFileName = [NSString stringWithFormat:@"%@.%@",AWSMobileURLSessionManagerCacheDomain,[[NSProcessInfo processInfo] globallyUniqueString]]; + NSString *tempDirPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.fileCache",AWSMobileURLSessionManagerCacheDomain]]; + + //Create temp folder if not exist + [[NSFileManager defaultManager] createDirectoryAtPath:tempDirPath withIntermediateDirectories:NO attributes:nil error:nil]; + + delegate.tempDownloadedFileURL = [NSURL fileURLWithPath:[tempDirPath stringByAppendingPathComponent:tempFileName]]; + + //Remove temp file if it has already exists if ([[NSFileManager defaultManager] fileExistsAtPath:delegate.tempDownloadedFileURL.path]) { AWSLogWarn(@"Warning: target file already exists, will be overwritten at the file path: %@",delegate.tempDownloadedFileURL); [[NSFileManager defaultManager] removeItemAtPath:delegate.tempDownloadedFileURL.path error:&error]; @@ -493,6 +522,8 @@ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)data if (error) { AWSLogError(@"Error: [%@]", error); } + + //Create new temp file if (![[NSFileManager defaultManager] createFileAtPath:delegate.tempDownloadedFileURL.path contents:nil attributes:nil]) { AWSLogError(@"Error: Can not create file with file path:%@",delegate.tempDownloadedFileURL.path); } @@ -502,9 +533,9 @@ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)data AWSLogError(@"Error: [%@]", error); } } - + } - + // if([response isKindOfClass:[NSHTTPURLResponse class]]) { // NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; // if ([[[httpResponse allHeaderFields] objectForKey:@"Content-Length"] longLongValue] >= AWSMinimumDownloadTaskSize) { @@ -512,7 +543,7 @@ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)data // return; // } // } - + completionHandler(NSURLSessionResponseAllow); } @@ -524,7 +555,7 @@ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)data - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { AWSURLSessionManagerDelegate *delegate = [self.sessionManagerDelegates objectForKey:@(dataTask.taskIdentifier)]; - if (delegate.downloadingFileURL) { + if (delegate.responseFilehandle) { [delegate.responseFilehandle writeData:data]; } else { if (!delegate.responseData) { @@ -552,7 +583,7 @@ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)data } downloadProgress(bytesWritten,delegate.payloadTotalBytesWritten + byteRangeStartPosition,totalBytesExpectedToWrite); } - + } //- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler { diff --git a/AWSCore/STS/AWSSTS.m b/AWSCore/STS/AWSSTS.m index 3c40799183d..528eb1c6a92 100644 --- a/AWSCore/STS/AWSSTS.m +++ b/AWSCore/STS/AWSSTS.m @@ -113,25 +113,35 @@ @interface AWSSTSRequestRetryHandler : AWSURLRequestRetryHandler @implementation AWSSTSRequestRetryHandler - (AWSNetworkingRetryType)shouldRetry:(uint32_t)currentRetryCount - response:(NSHTTPURLResponse *)response - data:(NSData *)data - error:(NSError *)error { + response:(NSHTTPURLResponse *)response + data:(NSData *)data + error:(NSError *)error { AWSNetworkingRetryType retryType = [super shouldRetry:currentRetryCount - response:response - data:data - error:error]; + response:response + data:data + error:error]; if(retryType == AWSNetworkingRetryTypeShouldNotRetry - && [error.domain isEqualToString:AWSSTSErrorDomain] && currentRetryCount < self.maxRetryCount) { - switch (error.code) { - case AWSSTSErrorIncompleteSignature: - case AWSSTSErrorInvalidClientTokenId: - case AWSSTSErrorMissingAuthenticationToken: - retryType = AWSNetworkingRetryTypeShouldRefreshCredentialsAndRetry; - break; - - default: - break; + if ([error.domain isEqualToString:AWSSTSErrorDomain]) { + switch (error.code) { + case AWSSTSErrorIncompleteSignature: + case AWSSTSErrorInvalidClientTokenId: + case AWSSTSErrorMissingAuthenticationToken: + retryType = AWSNetworkingRetryTypeShouldRefreshCredentialsAndRetry; + break; + + default: + break; + } + } else if ([error.domain isEqualToString:AWSGeneralErrorDomain]) { + switch (error.code) { + case AWSGeneralErrorSignatureDoesNotMatch: + retryType = AWSNetworkingRetryTypeShouldCorrectClockSkewAndRetry; + break; + + default: + break; + } } } diff --git a/AWSCore/Serialization/AWSSerialization.m b/AWSCore/Serialization/AWSSerialization.m index f1637dc9183..7cd02f129b1 100644 --- a/AWSCore/Serialization/AWSSerialization.m +++ b/AWSCore/Serialization/AWSSerialization.m @@ -461,7 +461,9 @@ - (NSMutableDictionary *)dictionaryForXMLData:(NSData *)data NSMutableDictionary *rootXmlDictionary = nil; if ([data isKindOfClass:[NSData class]]) { - rootXmlDictionary = [[self.xmlDictionaryParser dictionaryWithData:data] mutableCopy]; //TODO: need error parameters for parsing + @synchronized (self) { + rootXmlDictionary = [[self.xmlDictionaryParser dictionaryWithData:data] mutableCopy]; //TODO: need error parameters for parsing + } } NSString *rootNodeName = [[rootXmlDictionary allKeys] firstObject]; @@ -606,12 +608,14 @@ + (NSMutableDictionary *)parseStructure:(NSDictionary *)structure rules:(AWSJSON __block NSError *blockErr = nil; [structure enumerateKeysAndObjectsUsingBlock:^(NSString *xmlName, id value, BOOL *stop) { if ([xmlName isEqualToString:@"$"]) { - //TODO: do something } else { NSString *keyName = [self findKeyNameByXMLName:xmlName rules:rules]; if (!keyName) { - if (![xmlName isEqualToString:@"_xmlns"] && ![xmlName isEqualToString:@"requestId"]) { - AWSLogWarn(@"Can not find the xmlName:%@ in definition to serialize xml data: %@", xmlName, [value description]); + if (![xmlName isEqualToString:@"_xmlns"] && + ![xmlName isEqualToString:@"requestId"] && + ![xmlName isEqualToString:@"ResponseMetadata"] && + ![xmlName isEqualToString:@"__text"]) { + AWSLogWarn(@"Response element ignored: no rule for %@ - %@", xmlName, [value description]); } /*[self failWithCode:AWSXMLParserXMLNameNotFoundInDefinition diff --git a/AWSCore/Serialization/AWSURLRequestRetryHandler.m b/AWSCore/Serialization/AWSURLRequestRetryHandler.m index a0f4187489a..b50068124a6 100644 --- a/AWSCore/Serialization/AWSURLRequestRetryHandler.m +++ b/AWSCore/Serialization/AWSURLRequestRetryHandler.m @@ -36,7 +36,6 @@ - (BOOL)isClockSkewError:(NSError *)error { if (error.code == AWSGeneralErrorRequestTimeTooSkewed || error.code == AWSGeneralErrorInvalidSignatureException || error.code == AWSGeneralErrorRequestExpired - || error.code == AWSGeneralErrorSignatureDoesNotMatch || error.code == AWSGeneralErrorAuthFailure) { return YES; } @@ -59,7 +58,18 @@ - (AWSNetworkingRetryType)shouldRetry:(uint32_t)currentRetryCount if ([error.domain isEqualToString:NSURLErrorDomain]) { switch (error.code) { - case kCFURLErrorNotConnectedToInternet: + case NSURLErrorCancelled: + case NSURLErrorBadURL: + case NSURLErrorNotConnectedToInternet: + + case NSURLErrorSecureConnectionFailed: + case NSURLErrorServerCertificateHasBadDate: + case NSURLErrorServerCertificateUntrusted: + case NSURLErrorServerCertificateHasUnknownRoot: + case NSURLErrorServerCertificateNotYetValid: + case NSURLErrorClientCertificateRejected: + case NSURLErrorClientCertificateRequired: + case NSURLErrorCannotLoadFromNetwork: return AWSNetworkingRetryTypeShouldNotRetry; default: diff --git a/AWSCore/Serialization/AWSURLRequestSerialization.m b/AWSCore/Serialization/AWSURLRequestSerialization.m index 705d1c07d2f..be2efd3d7b4 100644 --- a/AWSCore/Serialization/AWSURLRequestSerialization.m +++ b/AWSCore/Serialization/AWSURLRequestSerialization.m @@ -21,6 +21,7 @@ #import "AWSCategory.h" #import "AWSLogging.h" #import "GZIP.h" +#import "Bolts.h" @interface AWSJSONRequestSerializer() diff --git a/AWSCore/Service/AWSService.m b/AWSCore/Service/AWSService.m index cb33819906b..65fb9c17aee 100644 --- a/AWSCore/Service/AWSService.m +++ b/AWSCore/Service/AWSService.m @@ -86,6 +86,7 @@ @implementation AWSServiceConfiguration - (instancetype)init { if(self = [super init]) { _regionType = AWSRegionUnknown; + _maxRetryCount = 3; } return self; @@ -95,6 +96,7 @@ - (instancetype)initWithRegion:(AWSRegionType)regionType credentialsProvider:(id if (self = [super init]) { _regionType = regionType; _credentialsProvider = credentialsProvider; + _maxRetryCount = 3; } return self; @@ -177,11 +179,11 @@ - (BOOL)isServiceAvailable:(AWSServiceType)serviceType { NSString *const AWSRegionNameUSWest2 = @"us-west-2"; NSString *const AWSRegionNameUSWest1 = @"us-west-1"; NSString *const AWSRegionNameEUWest1 = @"eu-west-1"; +NSString *const AWSRegionNameEUCentral1 = @"eu-central-1"; NSString *const AWSRegionNameAPSoutheast1 = @"ap-southeast-1"; -NSString *const AWSRegionNameAPSoutheast2 = @"ap-southeast-2"; NSString *const AWSRegionNameAPNortheast1 = @"ap-northeast-1"; +NSString *const AWSRegionNameAPSoutheast2 = @"ap-southeast-2"; NSString *const AWSRegionNameSAEast1 = @"sa-east-1"; - NSString *const AWSRegionNameCNNorth1 = @"cn-north-1"; NSString *const AWSServiceNameAppStream = @"appstream"; @@ -226,7 +228,7 @@ - (instancetype)init { } - (instancetype)initWithRegion:(AWSRegionType)regionType - service:(AWSServiceType)serviceType + service:(AWSServiceType)serviceType useUnsafeURL:(BOOL)useUnsafeURL { if (self = [super init]) { _regionType = regionType; @@ -246,6 +248,9 @@ - (instancetype)initWithRegion:(AWSRegionType)regionType case AWSRegionEUWest1: _regionName = AWSRegionNameEUWest1; break; + case AWSRegionEUCentral1: + _regionName = AWSRegionNameEUCentral1; + break; case AWSRegionAPSoutheast1: _regionName = AWSRegionNameAPSoutheast1; break; @@ -317,9 +322,17 @@ - (instancetype)initWithRegion:(AWSRegionType)regionType } NSString *separator = @"."; - if (_serviceType == AWSServiceS3 && _regionType != AWSRegionCNNorth1) { - separator = @"-"; - } + if (_serviceType == AWSServiceS3 + && (_regionType == AWSRegionUSEast1 + || _regionType == AWSRegionUSWest1 + || _regionType == AWSRegionUSWest2 + || _regionType == AWSRegionEUWest1 + || _regionType == AWSRegionAPSoutheast1 + || _regionType == AWSRegionAPNortheast1 + || _regionType == AWSRegionAPSoutheast2 + || _regionType == AWSRegionSAEast1)) { + separator = @"-"; + } NSString *HTTP_Type = @"https"; if (_useUnsafeURL) { @@ -334,13 +347,13 @@ - (instancetype)initWithRegion:(AWSRegionType)regionType } else { _URL = [NSURL URLWithString:@"https://sts.amazonaws.com"]; } - + } else if (_serviceType == AWSServiceSimpleDB && _regionType == AWSRegionUSEast1) { _URL = [NSURL URLWithString:[NSString stringWithFormat:@"%@://sdb.amazonaws.com", HTTP_Type]]; } else { _URL = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@%@%@.amazonaws.com", HTTP_Type, _serviceName, separator, _regionName]]; } - + //need to add ".cn" at end of URL if it is in China Region if ([_regionName hasPrefix:@"cn"]) { NSString *urlString = [_URL absoluteString]; @@ -352,12 +365,15 @@ - (instancetype)initWithRegion:(AWSRegionType)regionType return self; } -+ (instancetype)endpointWithRegion:(AWSRegionType)regionType service:(AWSServiceType)serviceType { ++ (instancetype)endpointWithRegion:(AWSRegionType)regionType + service:(AWSServiceType)serviceType { AWSEndpoint *endpoint = [[AWSEndpoint alloc] initWithRegion:regionType service:serviceType useUnsafeURL:NO]; return endpoint; } -+ (instancetype)endpointWithRegion:(AWSRegionType)regionType service:(AWSServiceType)serviceType useUnsafeURL:(BOOL)useUnsafeURL { ++ (instancetype)endpointWithRegion:(AWSRegionType)regionType + service:(AWSServiceType)serviceType + useUnsafeURL:(BOOL)useUnsafeURL { AWSEndpoint *endpoint = [[AWSEndpoint alloc] initWithRegion:regionType service:serviceType useUnsafeURL:useUnsafeURL]; return endpoint; } diff --git a/AWSCore/Service/AWSServiceEnum.h b/AWSCore/Service/AWSServiceEnum.h index 57d9cd97f8e..b011c875987 100644 --- a/AWSCore/Service/AWSServiceEnum.h +++ b/AWSCore/Service/AWSServiceEnum.h @@ -20,12 +20,13 @@ typedef NS_ENUM(NSInteger, AWSRegionType) { AWSRegionUnknown, AWSRegionUSEast1, AWSRegionUSWest1, + AWSRegionUSWest2, AWSRegionEUWest1, + AWSRegionEUCentral1, AWSRegionAPSoutheast1, AWSRegionAPNortheast1, - AWSRegionUSWest2, - AWSRegionSAEast1, AWSRegionAPSoutheast2, + AWSRegionSAEast1, AWSRegionCNNorth1, }; diff --git a/AWSiOSSDKAnalyticsTests/AIDelayedBlockTests.m b/AWSiOSSDKAnalyticsTests/AIDelayedBlockTests.m index ebc12c4e144..d83ae95ea98 100644 --- a/AWSiOSSDKAnalyticsTests/AIDelayedBlockTests.m +++ b/AWSiOSSDKAnalyticsTests/AIDelayedBlockTests.m @@ -65,7 +65,7 @@ -(void)testAllowBlockToExpire __block BOOL wasExecutedReturnValue; - AWSMobileAnalyticsDelayedBlock* delayedBlock = [AWSMobileAnalyticsDelayedBlock delayedBlockWithDelay:1 withBlock:^(void) { + AWSMobileAnalyticsDelayedBlock* delayedBlock = [AWSMobileAnalyticsDelayedBlock delayedBlockWithDelay:delay withBlock:^(void) { blockWasCalled = YES; executionDate = [NSDate date]; }]; @@ -84,9 +84,9 @@ -(void)testAllowBlockToExpire assertThatBool(wasExecutedReturnValue, is(equalToBool(YES))); assertThatBool(blockWasCalled, is(equalToBool(YES))); - // check that the delay is within 10% - assertThatBool([executionDate timeIntervalSinceDate:startDate] < delay * 1.10 , is(equalToBool(YES))); - assertThatBool([executionDate timeIntervalSinceDate:startDate] > delay * .90 , is(equalToBool(YES))); + // check that the delay is within 20% + XCTAssertLessThan([executionDate timeIntervalSinceDate:startDate], delay * 1.20); + XCTAssertGreaterThan([executionDate timeIntervalSinceDate:startDate], delay * .80); } @end diff --git a/AWSiOSSDKAnalyticsTests/AIDeliveryIntegrationTests.m b/AWSiOSSDKAnalyticsTests/AIDeliveryIntegrationTests.m index 31644891f14..2f69843817a 100644 --- a/AWSiOSSDKAnalyticsTests/AIDeliveryIntegrationTests.m +++ b/AWSiOSSDKAnalyticsTests/AIDeliveryIntegrationTests.m @@ -130,6 +130,9 @@ - (void)test_submitEvents_eventsFileMissing_eventsNotSubmitted withDeliveryClient:deliveryClient allowsEventCollection:YES]; + [eventClient addGlobalAttribute:@"test-seesion-id-value" forKey:AWSSessionIDAttributeKey]; + [eventClient addGlobalAttribute:@"test-start-timstamp-value" forKey:AWSSessionStartTimeAttributeKey]; + BlockingInterceptor* interceptor = [[BlockingInterceptor alloc] init]; [interceptor setExpectedRequestURL:[NSURL URLWithString:[BASE_URL stringByAppendingFormat:@"%@/events", APP_KEY]]]; [[_context httpClient] addInterceptor:interceptor]; @@ -163,6 +166,9 @@ - (void)test_submitEvents_WANNotAllowed_eventsNotSubmittedButKept withDeliveryClient:deliveryClient allowsEventCollection:YES]; + [eventClient addGlobalAttribute:@"test-seesion-id-value" forKey:AWSSessionIDAttributeKey]; + [eventClient addGlobalAttribute:@"test-start-timstamp-value" forKey:AWSSessionStartTimeAttributeKey]; + BlockingInterceptor* interceptor = [[BlockingInterceptor alloc] init]; [interceptor setExpectedRequestURL:[NSURL URLWithString:[BASE_URL stringByAppendingFormat:@"%@/events", APP_KEY]]]; [[_context httpClient] addInterceptor:interceptor]; @@ -205,6 +211,9 @@ - (void)test_submitEvents_noConnectivity_eventsNotSubmittedButKept withDeliveryClient:deliveryClient allowsEventCollection:YES]; + [eventClient addGlobalAttribute:@"test-seesion-id-value" forKey:AWSSessionIDAttributeKey]; + [eventClient addGlobalAttribute:@"test-start-timstamp-value" forKey:AWSSessionStartTimeAttributeKey]; + BlockingInterceptor* interceptor = [[BlockingInterceptor alloc] init]; [interceptor setExpectedRequestURL:[NSURL URLWithString:[BASE_URL stringByAppendingFormat:@"%@/events", APP_KEY]]]; [[_context httpClient] addInterceptor:interceptor]; @@ -245,6 +254,9 @@ - (void)test_submitEvents_internalServerError_eventsKept withDeliveryClient:deliveryClient allowsEventCollection:YES]; + [eventClient addGlobalAttribute:@"test-seesion-id-value" forKey:AWSSessionIDAttributeKey]; + [eventClient addGlobalAttribute:@"test-start-timstamp-value" forKey:AWSSessionStartTimeAttributeKey]; + BlockingInterceptor* interceptor = [[BlockingInterceptor alloc] init]; [interceptor setExpectedRequestURL:[NSURL URLWithString:[BASE_URL stringByAppendingFormat:@"%@/events", APP_KEY]]]; [interceptor setOverrideResponseCode:500]; @@ -287,6 +299,9 @@ - (void)test_submitEvents_hostNotReachable_eventsKept withDeliveryClient:deliveryClient allowsEventCollection:YES]; + [eventClient addGlobalAttribute:@"test-seesion-id-value" forKey:AWSSessionIDAttributeKey]; + [eventClient addGlobalAttribute:@"test-start-timstamp-value" forKey:AWSSessionStartTimeAttributeKey]; + BlockingInterceptor* interceptor = [[BlockingInterceptor alloc] init]; [interceptor setExpectedRequestURL:[NSURL URLWithString:[BASE_URL stringByAppendingFormat:@"%@/events", APP_KEY]]]; [interceptor setExpectedResponseCode:-1000]; @@ -325,6 +340,9 @@ - (void)test_submitEvents_forbiddenResponse_eventsDeleted withDeliveryClient:deliveryClient allowsEventCollection:YES]; + [eventClient addGlobalAttribute:@"test-seesion-id-value" forKey:AWSSessionIDAttributeKey]; + [eventClient addGlobalAttribute:@"test-start-timstamp-value" forKey:AWSSessionStartTimeAttributeKey]; + BlockingInterceptor* interceptor = [[BlockingInterceptor alloc] init]; [interceptor setExpectedRequestURL:[NSURL URLWithString:[BASE_URL stringByAppendingFormat:@"%@/events", APP_KEY]]]; [interceptor setOverrideResponseCode:403]; diff --git a/AWSiOSSDKAnalyticsTests/AIFileEventStoreTests.m b/AWSiOSSDKAnalyticsTests/AIFileEventStoreTests.m index 73d3e9d792d..b2e84bb4528 100644 --- a/AWSiOSSDKAnalyticsTests/AIFileEventStoreTests.m +++ b/AWSiOSSDKAnalyticsTests/AIFileEventStoreTests.m @@ -280,7 +280,7 @@ -(void) test_multithreads_finishesInTimeAndNothingLost NSTimeInterval duration = [end timeIntervalSinceDate:start]; NSLog(@"It took %f to write %d events", duration, operationCounts*eventCounts); - assertThatBool(duration > 3.0, is(equalToBool(NO))); + assertThatBool(duration > 20.0, is(equalToBool(NO))); id iter = eventStore.iterator; int linesRead = 0; diff --git a/AWSiOSSDKAnalyticsTests/AIIntegrationTestBase.m b/AWSiOSSDKAnalyticsTests/AIIntegrationTestBase.m index eeb30c9489b..d8de9b0afb9 100644 --- a/AWSiOSSDKAnalyticsTests/AIIntegrationTestBase.m +++ b/AWSiOSSDKAnalyticsTests/AIIntegrationTestBase.m @@ -90,22 +90,22 @@ - (NSUInteger)testCaseCount { return count; } -- (void)performTest:(XCTestRun *)testRun { - if ([[self class] isKindOfClass:[AIIntegrationTestBase class]] == NO) - { - NSArray *locales = [NSArray arrayWithObjects:@"en_US", @"ar_SA", @"ja_JP_JP", @"fr_FR", nil]; - for (NSString *locale in locales) { - [NSLocale ttt_overrideRuntimeLocale:[NSLocale localeWithLocaleIdentifier:locale]]; - NSString *locale = [[NSLocale currentLocale] localeIdentifier]; - NSLog(@"current locale set to: %@", locale); - - [super performTest:testRun]; - - [NSLocale ttt_resetRuntimeLocale]; - locale = [[NSLocale currentLocale] localeIdentifier]; - NSLog(@"current locale set back to: %@", locale); - } - } -} +//- (void)performTest:(XCTestRun *)testRun { +// if ([[self class] isKindOfClass:[AIIntegrationTestBase class]] == NO) +// { +// NSArray *locales = [NSArray arrayWithObjects:@"en_US", @"ar_SA", @"ja_JP_JP", @"fr_FR", nil]; +// for (NSString *locale in locales) { +// [NSLocale ttt_overrideRuntimeLocale:[NSLocale localeWithLocaleIdentifier:locale]]; +// NSString *locale = [[NSLocale currentLocale] localeIdentifier]; +// NSLog(@"current locale set to: %@", locale); +// +// [super performTest:testRun]; +// +// [NSLocale ttt_resetRuntimeLocale]; +// locale = [[NSLocale currentLocale] localeIdentifier]; +// NSLog(@"current locale set back to: %@", locale); +// } +// } +//} @end diff --git a/AWSiOSSDKAnalyticsTests/AISessionClientTests.m b/AWSiOSSDKAnalyticsTests/AISessionClientTests.m index 5b4fa62d2de..0a93540deee 100644 --- a/AWSiOSSDKAnalyticsTests/AISessionClientTests.m +++ b/AWSiOSSDKAnalyticsTests/AISessionClientTests.m @@ -654,22 +654,24 @@ -(void)test_PauseSession_SessionIsActive_StateChangedToPause{ assertThatInteger([target getSessionState], is(equalToInteger(SESSION_STATE_PAUSED))); } --(void)test_PauseSession_SessionIsInactive_FiresBlankPauseEvent{ +//Test has been permanently commented out since BlankPauseEvent is an invalid Event and won't be recorded in our SDK. - AWSMobileAnalytics* insights = [AWSMobileAnalytics mobileAnalyticsForAppId:TEST_APP_KEY]; - AWSMobileAnalyticsDefaultSessionClient* target = [insights sessionClient]; - TestEventObserver2* interceptor = [[TestEventObserver2 alloc] initObserver]; - id ec = (id) [insights eventClient]; - [ec addEventObserver:interceptor]; - - [target stopSession]; - assertThatInteger([target getSessionState], is(equalToInteger(SESSION_STATE_INACTIVE))); - - [target.state pauseWithSessionClient:target]; - [target cancelDelayedBlock]; - assertThat([interceptor lastEvent], is(notNilValue())); - assertThat([[interceptor lastEvent] eventType], is(equalTo(AWSSessionPauseEventType))); -} +//-(void)test_PauseSession_SessionIsInactive_FiresBlankPauseEvent{ +// +// AWSMobileAnalytics* insights = [AWSMobileAnalytics mobileAnalyticsForAppId:TEST_APP_KEY]; +// AWSMobileAnalyticsDefaultSessionClient* target = [insights sessionClient]; +// TestEventObserver2* interceptor = [[TestEventObserver2 alloc] initObserver]; +// id ec = (id) [insights eventClient]; +// [ec addEventObserver:interceptor]; +// +// [target stopSession]; +// assertThatInteger([target getSessionState], is(equalToInteger(SESSION_STATE_INACTIVE))); +// +// [target.state pauseWithSessionClient:target]; +// [target cancelDelayedBlock]; +// assertThat([interceptor lastEvent], is(notNilValue())); +// assertThat([[interceptor lastEvent] eventType], is(equalTo(AWSSessionPauseEventType))); +//} -(void)test_PauseSession_SessionIsInactive_StateIsNotChanged{ diff --git a/AWSiOSSDKAnalyticsTests/AZIOSClientContextTests.m b/AWSiOSSDKAnalyticsTests/AZIOSClientContextTests.m index be579eff4b3..5d2e9aeffeb 100644 --- a/AWSiOSSDKAnalyticsTests/AZIOSClientContextTests.m +++ b/AWSiOSSDKAnalyticsTests/AZIOSClientContextTests.m @@ -42,7 +42,8 @@ - (void)test_contextAttributesNoCustomAttributes //Device details UIDevice* currentDevice = [UIDevice currentDevice]; assertThat(clientContext.deviceManufacturer, is(equalTo(@"apple"))); - assertThat(clientContext.deviceModel, is(equalTo(@"iPhone Simulator"))); + XCTAssertTrue([clientContext.deviceModel isEqualToString:@"iPhone Simulator"] || + [clientContext.deviceModel isEqualToString:@"iPad Simulator"]); assertThat(clientContext.devicePlatform, is(equalTo(@"iPhone OS"))); assertThat(clientContext.deviceLocale, is(equalTo([[NSLocale autoupdatingCurrentLocale] localeIdentifier]))); assertThat(clientContext.deviceModelVersion, is(equalTo([clientContext deviceModelVersionCode]))); @@ -71,7 +72,8 @@ - (void)test_contextAttributesWithCustomAttributes //Device details UIDevice* currentDevice = [UIDevice currentDevice]; assertThat(clientContext.deviceManufacturer, is(equalTo(@"apple"))); - assertThat(clientContext.deviceModel, is(equalTo(@"iPhone Simulator"))); + XCTAssertTrue([clientContext.deviceModel isEqualToString:@"iPhone Simulator"] || + [clientContext.deviceModel isEqualToString:@"iPad Simulator"]); assertThat(clientContext.devicePlatform, is(equalTo(@"iPhone OS"))); assertThat(clientContext.deviceLocale, is(equalTo([[NSLocale autoupdatingCurrentLocale] localeIdentifier]))); assertThat(clientContext.deviceModelVersion, is(equalTo([clientContext deviceModelVersionCode]))); @@ -111,7 +113,8 @@ - (void)test_contextWithOverriddenAppProperties //Device details UIDevice* currentDevice = [UIDevice currentDevice]; assertThat(clientContext.deviceManufacturer, is(equalTo(@"apple"))); - assertThat(clientContext.deviceModel, is(equalTo(@"iPhone Simulator"))); + XCTAssertTrue([clientContext.deviceModel isEqualToString:@"iPhone Simulator"] || + [clientContext.deviceModel isEqualToString:@"iPad Simulator"]); assertThat(clientContext.devicePlatform, is(equalTo(@"iPhone OS"))); assertThat(clientContext.deviceLocale, is(equalTo([[NSLocale autoupdatingCurrentLocale] localeIdentifier]))); assertThat(clientContext.deviceModelVersion, is(equalTo([clientContext deviceModelVersionCode]))); diff --git a/AWSiOSSDKTests/AWSAnalyticsTests.m b/AWSiOSSDKTests/AWSAnalyticsTests.m index c1fdd82cadd..47225df6711 100644 --- a/AWSiOSSDKTests/AWSAnalyticsTests.m +++ b/AWSiOSSDKTests/AWSAnalyticsTests.m @@ -79,7 +79,7 @@ - (void)test_createAndSubmitEvent{ id deliveryClient = [insights valueForKey:@"deliveryClient"]; NSArray *batchedEvents = [deliveryClient batchedEvents]; //batchedEvents should be empty if all events has been sent successfully. - XCTAssertEqual(0, [batchedEvents count], @"batchedEvents is not empty,events delivery may have failed!"); + XCTAssertEqual(0, [batchedEvents count], @"batchedEvents is not empty,events delivery may have failed!, batchedEvent:\n%@",batchedEvents); //call sumbitEvent again without waiting for ValueForceSubmissionWaitTime(default 60sec) will result submission request been ignored. id level2Event = [eventClient createEventWithEventType:@"level2Complete"]; @@ -97,7 +97,7 @@ - (void)test_createAndSubmitEvent{ //submit it again, should be successful this time [eventClient submitEvents]; [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]]; - XCTAssertEqual(0, [[[insights valueForKey:@"deliveryClient"] batchedEvents] count], @"batchedEvents is not empty,events delivery may have failed!"); + XCTAssertEqual(0, [[[insights valueForKey:@"deliveryClient"] batchedEvents] count], @"batchedEvents is not empty,events delivery may have failed! , batchedEvent:\n%@",batchedEvents); } - (void)test_createAndSubmitMultipleEventsWithGlobalAttributes{ diff --git a/AWSiOSSDKTests/AWSCognitoCredentialsProviderTests.m b/AWSiOSSDKTests/AWSCognitoCredentialsProviderTests.m index b3c8b679348..abd4da1598f 100644 --- a/AWSiOSSDKTests/AWSCognitoCredentialsProviderTests.m +++ b/AWSiOSSDKTests/AWSCognitoCredentialsProviderTests.m @@ -196,6 +196,9 @@ - (void)testNotification { logins:@{ @(AWSCognitoLoginProviderKeyFacebook) : _facebookToken }]; + + [provider1 clearKeychain]; + __block AWSCognitoCredentialsProvider *provider2 = nil; __block NSString *provider1IdentityId = nil; diff --git a/AWSiOSSDKTests/AWSS3PreSignedURLBuilderTests.m b/AWSiOSSDKTests/AWSS3PreSignedURLBuilderTests.m index 4c6e4665abb..2a45dd03a23 100644 --- a/AWSiOSSDKTests/AWSS3PreSignedURLBuilderTests.m +++ b/AWSiOSSDKTests/AWSS3PreSignedURLBuilderTests.m @@ -91,6 +91,219 @@ - (void)testCustomServiceConfiguration { } +-(void)testObjectWithGreedyKey { + + NSArray *bucketNameArray = @[@"ios-v2-s3-tm-testdata",@"ios-v2-s3.periods"]; + + for (NSString *myBucketName in bucketNameArray) { + + + NSString *keyName = [NSString stringWithFormat:@"%lld/presignedURL-greedykey",(int64_t)[NSDate timeIntervalSinceReferenceDate]]; + //Put a object + AWSS3GetPreSignedURLRequest *getPreSignedURLRequest = [AWSS3GetPreSignedURLRequest new]; + getPreSignedURLRequest.bucket = myBucketName; + getPreSignedURLRequest.key = keyName; + getPreSignedURLRequest.HTTPMethod = AWSHTTPMethodPUT; + getPreSignedURLRequest.expires = [NSDate dateWithTimeIntervalSinceNow:3600]; + + [[[[AWSS3PreSignedURLBuilder defaultS3PreSignedURLBuilder] getPreSignedURL:getPreSignedURLRequest] continueWithBlock:^id(BFTask *task) { + + if (task.error) { + XCTAssertNil(task.error); + return nil; + } + + NSURL *presignedURL = task.result; + + XCTAssertNotNil(presignedURL); + + //NSLog(@"(PUT)presigned URL (Put)is: %@",presignedURL.absoluteString); + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:presignedURL]; + request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData; + [request setHTTPMethod:@"PUT"]; + + unsigned char *largeData = malloc(AWSS3PreSignedURLTest256KB) ; + memset(largeData, 5, AWSS3PreSignedURLTest256KB); + NSData *testObjectData = [[NSData alloc] initWithBytesNoCopy:largeData length:AWSS3PreSignedURLTest256KB]; + + [request setHTTPBody:testObjectData]; + + NSError *returnError = nil; + NSHTTPURLResponse *returnResponse = nil; + + NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&returnResponse error:&returnError]; + + XCTAssertNil(returnError, @"response contains error:%@ \n presigned URL is:%@",returnError,presignedURL.absoluteString); + + if (returnResponse.statusCode < 200 || returnResponse.statusCode >=300) XCTFail(@"response status Code is :%ld",(long)returnResponse.statusCode); + + if ([responseData length] != 0) { + //expected the got 0 size returnData, but got something else. + NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]; + XCTFail(@"Error response received:\n %@",responseString); + } + + return nil; + }] waitUntilFinished]; + + //wait a few seconds + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]]; + + //Head the Object + AWSS3GetPreSignedURLRequest *getPreSignedURLRequest2 = [AWSS3GetPreSignedURLRequest new]; + getPreSignedURLRequest2.bucket = myBucketName; + getPreSignedURLRequest2.key = keyName; + getPreSignedURLRequest2.HTTPMethod = AWSHTTPMethodHEAD; + getPreSignedURLRequest2.expires = [NSDate dateWithTimeIntervalSinceNow:3600]; + + + [[[[AWSS3PreSignedURLBuilder defaultS3PreSignedURLBuilder] getPreSignedURL:getPreSignedURLRequest2] continueWithBlock:^id(BFTask *task) { + + if (task.error) { + XCTAssertNil(task.error); + return nil; + } + + NSURL *presignedURL = task.result; + XCTAssertNotNil(presignedURL); + + //NSLog(@"(HEAD)presigned URL is: %@",presignedURL.absoluteString); + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:presignedURL]; + request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData; + [request setHTTPMethod:@"HEAD"]; + + NSError *returnError = nil; + NSHTTPURLResponse *returnResponse = nil; + + NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&returnResponse error:&returnError]; + + XCTAssertNil(returnError, @"response contains error:%@ \n presigned URL is:%@",returnError, presignedURL.absoluteString); + + if (returnResponse.statusCode < 200 || returnResponse.statusCode >=300) XCTFail(@"response status Code is :%ld",(long)returnResponse.statusCode); + + XCTAssertEqual((unsigned long)AWSS3PreSignedURLTest256KB, [[[returnResponse allHeaderFields] objectForKey:@"Content-Length"] integerValue],@"received object is different from sent object. expect file size:%lu, got:%lu",(unsigned long)AWSS3PreSignedURLTest256KB,(long)[[[returnResponse allHeaderFields] objectForKey:@"Content-Length"] integerValue]); + + if ([responseData length] != 0) { + //expected the got 0 size returnData, but got something else. + NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]; + XCTFail(@"Error response received:\n %@",responseString); + } + + return nil; + }] waitUntilFinished]; + + //Get Object + AWSS3GetPreSignedURLRequest *getPreSignedURLRequest3 = [AWSS3GetPreSignedURLRequest new]; + getPreSignedURLRequest3.bucket = myBucketName; + getPreSignedURLRequest3.key = keyName; + getPreSignedURLRequest3.HTTPMethod = AWSHTTPMethodGET; + getPreSignedURLRequest3.expires = [NSDate dateWithTimeIntervalSinceNow:3600]; + + + [[[[AWSS3PreSignedURLBuilder defaultS3PreSignedURLBuilder] getPreSignedURL:getPreSignedURLRequest3] continueWithBlock:^id(BFTask *task) { + + if (task.error) { + XCTAssertNil(task.error); + return nil; + } + + NSURL *presignedURL = task.result; + + XCTAssertNotNil(presignedURL); + + //NSLog(@"(GET)presigned URL is: %@",presignedURL.absoluteString); + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:presignedURL]; + request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData; + + NSError *returnError = nil; + NSHTTPURLResponse *returnResponse = nil; + + NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&returnResponse error:&returnError]; + + XCTAssertNil(returnError, @"response contains error:%@,\n presigned URL is:%@",returnError,presignedURL.absoluteString); + + if (returnResponse.statusCode < 200 || returnResponse.statusCode >=300) XCTFail(@"response status Code is :%ld",(long)returnResponse.statusCode); + + XCTAssertEqual((unsigned long)AWSS3PreSignedURLTest256KB, [responseData length],@"received object is different from sent object. expect file size:%lu, got:%lu",(unsigned long)AWSS3PreSignedURLTest256KB,(unsigned long)[responseData length]); + + return nil; + }] waitUntilFinished]; + + //Delete Object + + AWSS3GetPreSignedURLRequest *getPreSignedURLRequest4 = [AWSS3GetPreSignedURLRequest new]; + getPreSignedURLRequest4.bucket = myBucketName; + getPreSignedURLRequest4.key = keyName; + getPreSignedURLRequest4.HTTPMethod = AWSHTTPMethodDELETE; + getPreSignedURLRequest4.expires = [NSDate dateWithTimeIntervalSinceNow:3600]; + + [[[[AWSS3PreSignedURLBuilder defaultS3PreSignedURLBuilder] getPreSignedURL:getPreSignedURLRequest4] continueWithBlock:^id(BFTask *task) { + + if (task.error) { + XCTAssertNil(task.error); + return nil; + } + + NSURL *presignedURL = task.result; + + XCTAssertNotNil(presignedURL); + + //NSLog(@"(DELETE)presigned URL is: %@",presignedURL.absoluteString); + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:presignedURL]; + request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData; + [request setHTTPMethod:@"DELETE"]; + + NSError *returnError = nil; + NSHTTPURLResponse *returnResponse = nil; + + NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&returnResponse error:&returnError]; + + XCTAssertNil(returnError, @"response contains error:%@, \n presigned URL is:%@",returnError, presignedURL.absoluteString); + + if (returnResponse.statusCode < 200 || returnResponse.statusCode >=300) XCTFail(@"response status Code is :%ld",(long)returnResponse.statusCode); + + if ([responseData length] != 0) { + //expected the got 0 size returnData, but got something else. + NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]; + XCTFail(@"Error response received:\n %@",responseString); + } + + return nil; + }] waitUntilFinished]; + + //wait a few seconds + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]]; + + //confirm object has been deleted + AWSS3 *s3 = [AWSS3 defaultS3]; + + AWSS3GetObjectRequest *getObjectRequest = [AWSS3GetObjectRequest new]; + getObjectRequest.bucket = myBucketName; + getObjectRequest.key = keyName; + + [[[s3 getObject:getObjectRequest] continueWithBlock:^id(BFTask *task) { + XCTAssertNotNil(task.error); + XCTAssertEqual(AWSS3ErrorNoSuchKey, task.error.code, @"expected AWSS3ErrorNoSuchKey Error but got:%ld",(long)task.error.code); + return nil; + }] waitUntilFinished]; + + + //if failed to delete by presigned URL, delete it. + AWSS3DeleteObjectRequest *deleteObjectRequest = [AWSS3DeleteObjectRequest new]; + deleteObjectRequest.bucket = myBucketName; + deleteObjectRequest.key = keyName; + [[[s3 deleteObject:deleteObjectRequest] continueWithBlock:^id(BFTask *task) { + return nil; + }] waitUntilFinished]; + + } + +} + - (void)testGetObject { NSArray *bucketNameArray = @[@"ios-v2-s3-tm-testdata",@"ios-v2-s3.periods"]; @@ -107,6 +320,11 @@ - (void)testGetObject { [[[preSignedURLBuilder getPreSignedURL:getPreSignedURLRequest] continueWithBlock:^id(BFTask *task) { + if (task.error) { + XCTAssertNil(task.error); + return nil; + } + NSURL *presignedURL = task.result; XCTAssertNotNil(presignedURL); @@ -154,6 +372,11 @@ - (void)testPutObject { [[[preSignedURLBuilder getPreSignedURL:getPreSignedURLRequest] continueWithBlock:^id(BFTask *task) { + if (task.error) { + XCTAssertNil(task.error); + return nil; + } + NSURL *presignedURL = task.result; XCTAssertNotNil(presignedURL); @@ -246,6 +469,11 @@ - (void)testHEADObject { [[[preSignedURLBuilder getPreSignedURL:getPreSignedURLRequest] continueWithBlock:^id(BFTask *task) { + if (task.error) { + XCTAssertNil(task.error); + return nil; + } + NSURL *presignedURL = task.result; XCTAssertNotNil(presignedURL); @@ -324,6 +552,11 @@ - (void)testDeleteBucket { AWSS3PreSignedURLBuilder *preSignedURLBuilder = [AWSS3PreSignedURLBuilder defaultS3PreSignedURLBuilder]; [[[preSignedURLBuilder getPreSignedURL:getPreSignedURLRequest] continueWithBlock:^id(BFTask *task) { + if (task.error) { + XCTAssertNil(task.error); + return nil; + } + NSURL *presignedURL = task.result; XCTAssertNotNil(presignedURL); diff --git a/AWSiOSSDKTests/AWSS3Tests.m b/AWSiOSSDKTests/AWSS3Tests.m index c9695ea8675..ddfc86b7e72 100644 --- a/AWSiOSSDKTests/AWSS3Tests.m +++ b/AWSiOSSDKTests/AWSS3Tests.m @@ -401,6 +401,40 @@ -(void)testGetByFilePathFailed { return nil; }] waitUntilFinished]; +} + +-(void)testGetObjectByFilePathCanceled { + + AWSS3 *s3 = [AWSS3 defaultS3]; + + AWSS3GetObjectRequest *getObjectRequest = [AWSS3GetObjectRequest new]; + getObjectRequest.bucket = @"ios-v2-s3-tm-testdata"; + getObjectRequest.key = @"temp.txt"; + + //assign the file path to be written. + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsPath = [paths objectAtIndex:0]; //Get the docs directory + NSString *filePath = [documentsPath stringByAppendingPathComponent:@"s3TestCanceled.txt"]; + getObjectRequest.downloadingFileURL = [NSURL fileURLWithPath:filePath]; + + + BFTask *getObjTask = [[s3 getObject:getObjectRequest] continueWithBlock:^id(BFTask *task) { + //Should return Cancelled Task Error + XCTAssertNotNil(task.error,@"Expect got 'Cancelled' Error, but got nil"); + XCTAssertEqualObjects(NSURLErrorDomain, task.error.domain); + XCTAssertEqual(NSURLErrorCancelled, task.error.code); + return nil; + }]; + + + //wait few sec then cancel this job + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]]; + [getObjectRequest cancel]; + + + [getObjTask waitUntilFinished]; + + } - (void)testPutGetAndDeleteObjectByFilePathWithProgressFeedback { NSString *keyName = @"ios-test-put-get-and-delete-obj"; @@ -488,8 +522,8 @@ - (void)testPutGetAndDeleteObjectByFilePathWithProgressFeedback { }] waitUntilFinished]; } -- (void)testPutGetAndDeleteObject256KBWithProgressFeedback { - NSString *keyName = @"ios-test-put-and-delete-256KB"; +- (void)testPutGetAndDeleteObject256KBWithProgressFeedbackAndGreedyKey { + NSString *keyName = @"testfolder/ios-test-put-and-delete-256KB"; AWSS3 *s3 = [AWSS3 defaultS3]; XCTAssertNotNil(s3); @@ -590,6 +624,31 @@ - (void)testPutGetAndDeleteObject256KBWithProgressFeedback { XCTAssertTrue([task.result isKindOfClass:[AWSS3DeleteObjectOutput class]],@"The response object is not a class of [%@], got: %@", NSStringFromClass([AWSS3DeleteObjectOutput class]),[task.result description]); return nil; }] waitUntilFinished]; + + //confirm it has been deleted + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]]; + + AWSS3ListObjectsRequest *listObjectReq2 = [AWSS3ListObjectsRequest new]; + listObjectReq2.bucket = testBucketNameGeneral; + + [[[s3 listObjects:listObjectReq2] continueWithBlock:^id(BFTask *task) { + XCTAssertNil(task.error, @"The request failed. error: [%@]", task.error); + XCTAssertTrue([task.result isKindOfClass:[AWSS3ListObjectsOutput class]],@"The response object is not a class of [%@]", NSStringFromClass([AWSS3ListObjectsOutput class])); + AWSS3ListObjectsOutput *listObjectsOutput = task.result; + + XCTAssertEqualObjects(listObjectsOutput.name, testBucketNameGeneral); + + BOOL hasObject = NO; + for (AWSS3Object *s3Object in listObjectsOutput.contents) { + if ([s3Object.key isEqualToString:keyName]) { + hasObject = YES; + } + } + XCTAssertFalse(hasObject,@"object should be deleted but still existed."); + + return nil; + }] waitUntilFinished]; + } - (void)testMultipartUploadWithComplete { diff --git a/AWSiOSSDKTests/AWSS3TransferManagerTests.m b/AWSiOSSDKTests/AWSS3TransferManagerTests.m index 4097312356b..98b92711db9 100644 --- a/AWSiOSSDKTests/AWSS3TransferManagerTests.m +++ b/AWSiOSSDKTests/AWSS3TransferManagerTests.m @@ -39,7 +39,7 @@ + (void)setUp { [[self class] createBucketWithName:testBucketNameGeneral]; //Create a large temporary file for uploading & downloading test - tempLargeURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-s3tmTestTempLarge",testBucketNameGeneral]]]; + tempLargeURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-s3tmTestTempLarge",testBucketNameGeneral]]]; NSError *error = nil; if (![[NSFileManager defaultManager] createFileAtPath:tempLargeURL.path contents:nil attributes:nil]) { AWSLogError(@"Error: Can not create file with file path:%@",tempLargeURL.path); @@ -72,7 +72,7 @@ + (void)setUp { if (true) { //Create a smal temporary file for uploading & downloading test - tempSmallURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-s3tmTestTempSmall",testBucketNameGeneral]]]; + tempSmallURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-s3tmTestTempSmall",testBucketNameGeneral]]]; NSError *error = nil; if (![[NSFileManager defaultManager] createFileAtPath:tempSmallURL.path contents:nil attributes:nil]) { AWSLogError(@"Error: Can not create file with file path:%@",tempSmallURL.path); @@ -112,7 +112,7 @@ - (void)testTMPauseAllandResumeAllTasks { NSError *error = nil; XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:tempLargeURL.path]); NSString *fileName = [NSString stringWithFormat:@"%@-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - NSURL *testDataURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; + NSURL *testDataURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fileName]]; [[NSFileManager defaultManager] createSymbolicLinkAtURL:testDataURL withDestinationURL:tempLargeURL error:&error]; XCTAssertNil(error, @"The request failed. error: [%@]", error); NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:tempLargeURL.path @@ -159,7 +159,7 @@ - (void)testTMPauseAllandResumeAllTasks { downloadRequest1.key = @"temp.txt"; NSString *downloadFileName = [NSString stringWithFormat:@"%@-downloaded-%@-%d",NSStringFromSelector(_cmd),testBucketNameGeneral,1]; - downloadRequest1.downloadingFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:downloadFileName]]; + downloadRequest1.downloadingFileURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:downloadFileName]]; BFTask *downloadTask1 = [[transferManager download:downloadRequest1] continueWithBlock:^id(BFTask *task) { //Should return Cancelled Task Error XCTAssertNotNil(task.error,@"Expect got 'Cancelled' Error, but got nil"); @@ -174,7 +174,7 @@ - (void)testTMPauseAllandResumeAllTasks { NSString *downloadFileName2 = [NSString stringWithFormat:@"%@-downloaded-%@-%d",NSStringFromSelector(_cmd),testBucketNameGeneral,2]; - downloadRequest2.downloadingFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:downloadFileName2]]; + downloadRequest2.downloadingFileURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:downloadFileName2]]; BFTask *downloadTask2 = [[transferManager download:downloadRequest2] continueWithBlock:^id(BFTask *task) { //Should return Cancelled Task Error XCTAssertNotNil(task.error,@"Expect got 'Cancelled' Error, but got nil"); @@ -318,7 +318,7 @@ - (void)testCancelAllTasks { NSError *error = nil; XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:tempLargeURL.path]); NSString *fileName = [NSString stringWithFormat:@"%@-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - NSURL *testDataURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; + NSURL *testDataURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fileName]]; [[NSFileManager defaultManager] createSymbolicLinkAtURL:testDataURL withDestinationURL:tempLargeURL error:&error]; XCTAssertNil(error, @"The request failed. error: [%@]", error); @@ -363,7 +363,7 @@ - (void)testCancelAllTasks { downloadRequest1.key = @"temp.txt"; NSString *downloadFileName = [NSString stringWithFormat:@"%@-downloaded-%@-%d",NSStringFromSelector(_cmd),testBucketNameGeneral,1]; - downloadRequest1.downloadingFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:downloadFileName]]; + downloadRequest1.downloadingFileURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:downloadFileName]]; BFTask *downloadTask1 = [[transferManager download:downloadRequest1] continueWithBlock:^id(BFTask *task) { //Should return Cancelled Task Error XCTAssertNotNil(task.error,@"Expect got 'Cancelled' Error, but got nil"); @@ -378,7 +378,7 @@ - (void)testCancelAllTasks { NSString *downloadFileName2 = [NSString stringWithFormat:@"%@-downloaded-%@-%d",NSStringFromSelector(_cmd),testBucketNameGeneral,2]; - downloadRequest2.downloadingFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:downloadFileName2]]; + downloadRequest2.downloadingFileURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:downloadFileName2]]; BFTask *downloadTask2 = [[transferManager download:downloadRequest2] continueWithBlock:^id(BFTask *task) { //Should return Cancelled Task Error XCTAssertNotNil(task.error,@"Expect got 'Cancelled' Error, but got nil"); @@ -413,7 +413,7 @@ - (void)testCancelDownloadTask { NSError *error = nil; XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:tempLargeURL.path]); NSString *fileName = [NSString stringWithFormat:@"%@-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - NSURL *testDataURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; + NSURL *testDataURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fileName]]; [[NSFileManager defaultManager] createSymbolicLinkAtURL:testDataURL withDestinationURL:tempLargeURL error:&error]; XCTAssertNil(error, @"The request failed. error: [%@]", error); @@ -461,8 +461,10 @@ - (void)testCancelDownloadTask { downloadRequest.key = keyName; NSString *downloadFileName = [NSString stringWithFormat:@"%@-downloaded-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - downloadRequest.downloadingFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:downloadFileName]]; + downloadRequest.downloadingFileURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:downloadFileName]]; + XCTAssertEqual(downloadRequest.state, AWSS3TransferManagerRequestStateNotStarted); + [[transferManager download:downloadRequest] continueWithBlock:^id(BFTask *task) { //Should return Cancelled Task Error XCTAssertNotNil(task.error,@"Expect got 'Cancelled' Error, but got nil"); @@ -473,11 +475,16 @@ - (void)testCancelDownloadTask { return nil; }]; + + XCTAssertEqual(downloadRequest.state, AWSS3TransferManagerRequestStateRunning); //Wait a few second and then cancel the task [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]]; [condition lock]; [downloadRequest cancel]; + + XCTAssertEqual(downloadRequest.state, AWSS3TransferManagerRequestStateCanceling); + [condition waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:180]]; [condition unlock]; @@ -507,7 +514,7 @@ - (void)testCancelUploadTask { NSError *error = nil; XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:tempSmallURL.path]); NSString *fileName = [NSString stringWithFormat:@"%@-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - NSURL *testDataURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; + NSURL *testDataURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fileName]]; [[NSFileManager defaultManager] createSymbolicLinkAtURL:testDataURL withDestinationURL:tempSmallURL error:&error]; XCTAssertNil(error, @"The request failed. error: [%@]", error); @@ -518,7 +525,8 @@ - (void)testCancelUploadTask { uploadRequest.key = keyName; uploadRequest.body = testDataURL; - + XCTAssertEqual(uploadRequest.state, AWSS3TransferManagerRequestStateNotStarted); + [[transferManager upload:uploadRequest] continueWithBlock:^id(BFTask *task) { XCTAssertNotNil(task.error,@"Expect got 'Cancelled' Error, but got nil"); XCTAssertEqualObjects(AWSS3TransferManagerErrorDomain, task.error.domain); @@ -527,9 +535,13 @@ - (void)testCancelUploadTask { [condition signal]; return nil; }]; + + XCTAssertEqual(uploadRequest.state, AWSS3TransferManagerRequestStateRunning); //cancel the task [uploadRequest cancel]; + + XCTAssertEqual(uploadRequest.state, AWSS3TransferManagerRequestStateCanceling); [condition lock]; [condition waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:180]]; @@ -542,7 +554,7 @@ - (void)testCancelUploadTask { NSError *error = nil; XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:tempLargeURL.path]); NSString *fileName = [NSString stringWithFormat:@"%@-%@-large",NSStringFromSelector(_cmd),testBucketNameGeneral]; - NSURL *testDataURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; + NSURL *testDataURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fileName]]; [[NSFileManager defaultManager] createSymbolicLinkAtURL:testDataURL withDestinationURL:tempLargeURL error:&error]; XCTAssertNil(error, @"The request failed. error: [%@]", error); @@ -553,6 +565,7 @@ - (void)testCancelUploadTask { uploadRequest.key = keyNameSecond; uploadRequest.body = testDataURL; + XCTAssertEqual(uploadRequest.state, AWSS3TransferManagerRequestStateNotStarted); BFTask *uploadRequestLarge = [[transferManager upload:uploadRequest] continueWithBlock:^id(BFTask *task) { XCTAssertNotNil(task.error,@"Expect got 'Cancelled' Error, but got nil"); @@ -562,10 +575,15 @@ - (void)testCancelUploadTask { return nil; }]; + + XCTAssertEqual(uploadRequest.state, AWSS3TransferManagerRequestStateRunning); //wait a few moment and cancel the task [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]]; [uploadRequest cancel]; + + XCTAssertEqual(uploadRequest.state, AWSS3TransferManagerRequestStateCanceling); + [uploadRequestLarge waitUntilFinished]; @@ -605,7 +623,7 @@ - (void)testTMDownloadWithoutDownloadingFileURL { NSError *error = nil; XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:tempSmallURL.path]); NSString *fileName = [NSString stringWithFormat:@"%@-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - NSURL *testDataURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; + NSURL *testDataURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fileName]]; [[NSFileManager defaultManager] createSymbolicLinkAtURL:testDataURL withDestinationURL:tempSmallURL error:&error]; XCTAssertNil(error, @"The request failed. error: [%@]", error); @@ -677,7 +695,7 @@ - (void)testTMDownloadSmallSizeWithProgressFeedback { NSError *error = nil; XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:tempSmallURL.path]); NSString *fileName = [NSString stringWithFormat:@"%@-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - NSURL *testDataURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; + NSURL *testDataURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fileName]]; [[NSFileManager defaultManager] createSymbolicLinkAtURL:testDataURL withDestinationURL:tempSmallURL error:&error]; XCTAssertNil(error, @"The request failed. error: [%@]", error); NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:tempSmallURL.path @@ -722,7 +740,11 @@ - (void)testTMDownloadSmallSizeWithProgressFeedback { downloadRequest.key = keyName; NSString *downloadFileName = [NSString stringWithFormat:@"%@-downloaded-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - downloadRequest.downloadingFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:downloadFileName]]; + downloadRequest.downloadingFileURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:downloadFileName]]; + + //Create a situation that there is a file has already existed on that downloadingFileURL Path + NSString *getObjectFilePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"s3-2006-03-01" ofType:@"json"]; + [[NSFileManager defaultManager] copyItemAtPath:getObjectFilePath toPath:downloadRequest.downloadingFileURL.path error:nil]; __block int64_t accumulatedDownloadBytes = 0; __block int64_t totalDownloadedBytes = 0; @@ -779,7 +801,7 @@ - (void)testTMDownloadLargeSizeWithProgressFeedback { NSError *error = nil; XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:tempLargeURL.path]); NSString *fileName = [NSString stringWithFormat:@"%@-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - NSURL *testDataURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; + NSURL *testDataURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fileName]]; [[NSFileManager defaultManager] createSymbolicLinkAtURL:testDataURL withDestinationURL:tempLargeURL error:&error]; XCTAssertNil(error, @"The request failed. error: [%@]", error); NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:tempLargeURL.path @@ -807,7 +829,7 @@ - (void)testTMDownloadLargeSizeWithProgressFeedback { downloadRequest.key = keyName; NSString *downloadFileName = [NSString stringWithFormat:@"%@-downloaded-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - downloadRequest.downloadingFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:downloadFileName]]; + downloadRequest.downloadingFileURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:downloadFileName]]; __block int64_t accumulatedDownloadBytes = 0; __block int64_t totalDownloadedBytes = 0; @@ -865,7 +887,7 @@ - (void)testTMDownloadPauseAndResumeWithProgressFeedback { NSError *error = nil; XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:tempLargeURL.path]); NSString *fileName = [NSString stringWithFormat:@"%@-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - NSURL *testDataURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; + NSURL *testDataURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fileName]]; [[NSFileManager defaultManager] createSymbolicLinkAtURL:testDataURL withDestinationURL:tempLargeURL error:&error]; XCTAssertNil(error, @"The request failed. error: [%@]", error); NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:tempLargeURL.path @@ -894,7 +916,7 @@ - (void)testTMDownloadPauseAndResumeWithProgressFeedback { downloadRequest.key = keyName; NSString *downloadFileName = [NSString stringWithFormat:@"%@-downloaded-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - downloadRequest.downloadingFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:downloadFileName]]; + downloadRequest.downloadingFileURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:downloadFileName]]; __block int64_t accumulatedDownloadBytes = 0; __block int64_t totalDownloadedBytes = 0; @@ -905,6 +927,8 @@ - (void)testTMDownloadPauseAndResumeWithProgressFeedback { totalExpectedDownloadBytes = totalBytesExpectedToWrite; //NSLog(@"keyName:%@ bytesWritten: %lld, totalBytesWritten: %lld, totalBytesExpectedtoWrite: %lld",NSStringFromSelector(_cmd), bytesWritten,totalBytesWritten,totalBytesExpectedToWrite); }; + + XCTAssertEqual(downloadRequest.state, AWSS3TransferManagerRequestStateNotStarted); BFTask *pausedTaskOne = [[transferManager download:downloadRequest] continueWithBlock:^id(BFTask *task) { XCTAssertNotNil(task.error,@"Expect got 'Cancelled' Error, but got nil"); @@ -914,6 +938,8 @@ - (void)testTMDownloadPauseAndResumeWithProgressFeedback { return nil; }]; + XCTAssertEqual(downloadRequest.state, AWSS3TransferManagerRequestStateRunning); + //wait a few seconds and then pause it. [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]]; [[[downloadRequest pause] continueWithBlock:^id(BFTask *task) { @@ -921,6 +947,8 @@ - (void)testTMDownloadPauseAndResumeWithProgressFeedback { return nil; }] waitUntilFinished]; + XCTAssertEqual(downloadRequest.state, AWSS3TransferManagerRequestStatePaused); + AWSLogDebug(@"(S3 Transfer Manager) Download Task has been paused."); NSLog(@"(S3 Transfer Manager) Download Task has been paused."); [pausedTaskOne waitUntilFinished]; //make sure callback has been called. @@ -935,6 +963,8 @@ - (void)testTMDownloadPauseAndResumeWithProgressFeedback { XCTAssertEqual(AWSS3TransferManagerErrorPaused, task.error.code); return nil; }]; + + XCTAssertEqual(downloadRequest.state, AWSS3TransferManagerRequestStateRunning); //wait and pause again [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; @@ -943,6 +973,8 @@ - (void)testTMDownloadPauseAndResumeWithProgressFeedback { return nil; }] waitUntilFinished]; + XCTAssertEqual(downloadRequest.state, AWSS3TransferManagerRequestStatePaused); + [pausedTaskTwo waitUntilFinished]; //make sure callback has been called. [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]]; @@ -961,6 +993,8 @@ - (void)testTMDownloadPauseAndResumeWithProgressFeedback { return nil; }] waitUntilFinished]; + + XCTAssertEqual(downloadRequest.state, AWSS3TransferManagerRequestStateCompleted); NSLog(@"(S3 Transfer Manager) Download Task has been finished."); XCTAssertEqual(fileSize, accumulatedDownloadBytes, @"accumulatedDownloadBytes is not equal to total file size"); @@ -995,7 +1029,7 @@ - (void)testTMWithMissingParameters { NSError *error = nil; XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:tempSmallURL.path]); NSString *fileName = [NSString stringWithFormat:@"%@-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - NSURL *testDataURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; + NSURL *testDataURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fileName]]; [[NSFileManager defaultManager] createSymbolicLinkAtURL:testDataURL withDestinationURL:tempSmallURL error:&error]; XCTAssertNil(error, @"The request failed. error: [%@]", error); @@ -1022,7 +1056,7 @@ - (void)testTMWithMissingParameters { //downloadRequest.key = keyName; NSString *downloadFileName = [NSString stringWithFormat:@"%@-downloaded-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - downloadRequest.downloadingFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:downloadFileName]]; + downloadRequest.downloadingFileURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:downloadFileName]]; [[[transferManager download:downloadRequest] continueWithBlock:^id(BFTask *task) { @@ -1050,7 +1084,7 @@ - (void)testTMDownloadPauseAndResumeWithFailedTask { NSError *error = nil; XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:tempLargeURL.path]); NSString *fileName = [NSString stringWithFormat:@"%@-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - NSURL *testDataURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; + NSURL *testDataURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fileName]]; [[NSFileManager defaultManager] createSymbolicLinkAtURL:testDataURL withDestinationURL:tempLargeURL error:&error]; XCTAssertNil(error, @"The request failed. error: [%@]", error); @@ -1075,7 +1109,7 @@ - (void)testTMDownloadPauseAndResumeWithFailedTask { downloadRequest.key = keyName; NSString *downloadFileName = [NSString stringWithFormat:@"%@-downloaded-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - downloadRequest.downloadingFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:downloadFileName]]; + downloadRequest.downloadingFileURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:downloadFileName]]; BFTask *pausedTaskOne = [[transferManager download:downloadRequest] continueWithBlock:^id(BFTask *task) { XCTAssertNotNil(task.error,@"Expect got 'Cancelled' Error, but got nil"); @@ -1165,7 +1199,7 @@ - (void)testTMUploadPauseAndResumeSmallSizeWithProgressFeedback { NSError *error = nil; XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:tempSmallURL.path]); NSString *fileName = [NSString stringWithFormat:@"%@-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - NSURL *testDataURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; + NSURL *testDataURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fileName]]; [[NSFileManager defaultManager] createSymbolicLinkAtURL:testDataURL withDestinationURL:tempSmallURL error:&error]; XCTAssertNil(error, @"The request failed. error: [%@]", error); NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:tempSmallURL.path @@ -1184,12 +1218,14 @@ - (void)testTMUploadPauseAndResumeSmallSizeWithProgressFeedback { __block int64_t totalUploadedBytes = 0; __block int64_t totalExpectedUploadBytes = 0; uploadRequest.uploadProgress = ^(int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) { + //NSLog(@"keyName:%@ bytesSent: %lld, totalBytesSent: %lld, totalBytesExpectedToSend: %lld",keyName,bytesSent,totalBytesSent,totalBytesExpectedToSend); accumulatedUploadBytes += bytesSent; totalUploadedBytes = totalBytesSent; totalExpectedUploadBytes = totalBytesExpectedToSend; }; + XCTAssertEqual(uploadRequest.state, AWSS3TransferManagerRequestStateNotStarted); BFTask *uploadTaskSmall = [[transferManager upload:uploadRequest] continueWithBlock:^id(BFTask *task) { XCTAssertNotNil(task.error,@"Expect got 'Cancelled' Error, but got nil"); @@ -1197,6 +1233,8 @@ - (void)testTMUploadPauseAndResumeSmallSizeWithProgressFeedback { XCTAssertEqual(AWSS3TransferManagerErrorPaused, task.error.code); return nil; }]; + + XCTAssertEqual(uploadRequest.state, AWSS3TransferManagerRequestStateRunning); //wait a few moment and pause the task [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.3]]; @@ -1204,6 +1242,8 @@ - (void)testTMUploadPauseAndResumeSmallSizeWithProgressFeedback { XCTAssertNil(task.error, @"The request failed. error: [%@]", task.error); //should not return error if successfully paused. return nil; }] waitUntilFinished]; + + XCTAssertEqual(uploadRequest.state, AWSS3TransferManagerRequestStatePaused); [uploadTaskSmall waitUntilFinished]; @@ -1214,8 +1254,10 @@ - (void)testTMUploadPauseAndResumeSmallSizeWithProgressFeedback { return nil; }] waitUntilFinished]; + + XCTAssertEqual(uploadRequest.state, AWSS3TransferManagerRequestStateCompleted); + //XCTAssertEqual(fileSize, accumulatedUploadBytes, @"total of accumulatedUploadBytes is not equal to fileSize"); - XCTAssertEqual(fileSize, totalUploadedBytes, @"totalUploaded Bytes is not equal to fileSize"); XCTAssertEqual(fileSize, totalExpectedUploadBytes); @@ -1271,7 +1313,7 @@ - (void)testTMUploadPauseAndResumeLargeSizeWithProgressFeedback { NSError *error = nil; XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:tempLargeURL.path]); NSString *fileName = [NSString stringWithFormat:@"%@-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - NSURL *testDataURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; + NSURL *testDataURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fileName]]; [[NSFileManager defaultManager] createSymbolicLinkAtURL:testDataURL withDestinationURL:tempLargeURL error:&error]; XCTAssertNil(error, @"The request failed. error: [%@]", error); NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:tempLargeURL.path @@ -1290,11 +1332,14 @@ - (void)testTMUploadPauseAndResumeLargeSizeWithProgressFeedback { __block int64_t totalUploadedBytes = 0; __block int64_t totalExpectedUploadBytes = 0; uploadRequest.uploadProgress = ^(int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) { + //NSLog(@"keyName:%@ bytesSent: %lld, totalBytesSent: %lld, totalBytesExpectedToSend: %lld",keyName,bytesSent,totalBytesSent,totalBytesExpectedToSend); accumulatedUploadBytes += bytesSent; totalUploadedBytes = totalBytesSent; totalExpectedUploadBytes = totalBytesExpectedToSend; }; + + XCTAssertEqual(uploadRequest.state, AWSS3TransferManagerRequestStateNotStarted); [[transferManager upload:uploadRequest] continueWithBlock:^id(BFTask *task) { XCTAssertNotNil(task.error,@"Expect got 'Cancelled' Error, but got nil"); @@ -1305,6 +1350,8 @@ - (void)testTMUploadPauseAndResumeLargeSizeWithProgressFeedback { } return nil; }]; + + XCTAssertEqual(uploadRequest.state, AWSS3TransferManagerRequestStateRunning); //random pause and resume task until it finished BFTask *currentTask = nil; @@ -1321,7 +1368,9 @@ - (void)testTMUploadPauseAndResumeLargeSizeWithProgressFeedback { XCTAssertNil(task.error, @"The request failed. error: [%@]", task.error); //should not return error if successfully paused. return nil; }] waitUntilFinished]; - + + XCTAssertEqual(uploadRequest.state, AWSS3TransferManagerRequestStatePaused); + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]]; NSLog(@"-------- Resume the Task --------"); //resume the upload @@ -1342,11 +1391,15 @@ - (void)testTMUploadPauseAndResumeLargeSizeWithProgressFeedback { return nil; }]; + + XCTAssertEqual(uploadRequest.state, AWSS3TransferManagerRequestStateRunning); } [currentTask waitUntilFinished]; XCTAssertTrue(isFinished); + XCTAssertEqual(uploadRequest.state, AWSS3TransferManagerRequestStateCompleted); + //XCTAssertEqual(fileSize, accumulatedUploadBytes, @"total of accumulatedUploadBytes is not equal to fileSize"); XCTAssertEqual(fileSize, totalUploadedBytes, @"totalUploaded Bytes is not equal to fileSize"); XCTAssertEqual(fileSize, totalExpectedUploadBytes); @@ -1403,7 +1456,7 @@ - (void)testTMUploadLargeSizeWithProgressFeedback { NSError *error = nil; XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:tempLargeURL.path]); NSString *fileName = [NSString stringWithFormat:@"%@-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - NSURL *testDataURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; + NSURL *testDataURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fileName]]; [[NSFileManager defaultManager] createSymbolicLinkAtURL:testDataURL withDestinationURL:tempLargeURL error:&error]; XCTAssertNil(error, @"The request failed. error: [%@]", error); NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:tempLargeURL.path @@ -1421,17 +1474,22 @@ - (void)testTMUploadLargeSizeWithProgressFeedback { __block int64_t totalUploadedBytes = 0; __block int64_t totalExpectedUploadBytes = 0; uploadRequest.uploadProgress = ^(int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) { + //NSLog(@"keyName:%@ bytesSent: %lld, totalBytesSent: %lld, totalBytesExpectedToSend: %lld",keyName,bytesSent,totalBytesSent,totalBytesExpectedToSend); accumulatedUploadBytes += bytesSent; totalUploadedBytes = totalBytesSent; totalExpectedUploadBytes = totalBytesExpectedToSend; }; + XCTAssertEqual(uploadRequest.state, AWSS3TransferManagerRequestStateNotStarted); + [[[transferManager upload:uploadRequest] continueWithBlock:^id(BFTask *task) { XCTAssertNil(task.error, @"The request failed. error: [%@]", task.error); XCTAssertTrue([task.result isKindOfClass:[AWSS3TransferManagerUploadOutput class]], @"The response object is not a class of [%@], got: %@", NSStringFromClass([NSURL class]),NSStringFromClass([task.result class])); return nil; }] waitUntilFinished]; + + XCTAssertEqual(uploadRequest.state, AWSS3TransferManagerRequestStateCompleted); XCTAssertEqual(totalUploadedBytes, accumulatedUploadBytes, @"total of accumulatedUploadBytes is not equal to totalUploadedBytes"); XCTAssertEqual(fileSize, totalUploadedBytes, @"totalUploaded Bytes is not equal to fileSize"); @@ -1487,7 +1545,7 @@ - (void)testTMUploadSmallSizeWithProgressFeedback { NSError *error = nil; XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:tempSmallURL.path]); NSString *fileName = [NSString stringWithFormat:@"%@-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - NSURL *testDataURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; + NSURL *testDataURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fileName]]; [[NSFileManager defaultManager] createSymbolicLinkAtURL:testDataURL withDestinationURL:tempSmallURL error:&error]; XCTAssertNil(error, @"The request failed. error: [%@]", error); NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:tempSmallURL.path @@ -1507,17 +1565,22 @@ - (void)testTMUploadSmallSizeWithProgressFeedback { __block int64_t totalUploadedBytes = 0; __block int64_t totalExpectedUploadBytes = 0; uploadRequest.uploadProgress = ^(int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) { + //NSLog(@"keyName:%@ bytesSent: %lld, totalBytesSent: %lld, totalBytesExpectedToSend: %lld",keyName,bytesSent,totalBytesSent,totalBytesExpectedToSend); accumulatedUploadBytes += bytesSent; totalUploadedBytes = totalBytesSent; totalExpectedUploadBytes = totalBytesExpectedToSend; }; + + XCTAssertEqual(uploadRequest.state, AWSS3TransferManagerRequestStateNotStarted); [[[transferManager upload:uploadRequest] continueWithBlock:^id(BFTask *task) { XCTAssertNil(task.error, @"The request failed. error: [%@]", task.error); XCTAssertTrue([task.result isKindOfClass:[AWSS3TransferManagerUploadOutput class]], @"The response object is not a class of [%@], got: %@", NSStringFromClass([NSURL class]),NSStringFromClass([task.result class])); return nil; }] waitUntilFinished]; + + XCTAssertEqual(uploadRequest.state, AWSS3TransferManagerRequestStateCompleted); XCTAssertEqual(totalUploadedBytes, accumulatedUploadBytes, @"total of accumulatedUploadBytes is not equal to totalUploadedBytes"); XCTAssertEqual(fileSize, totalUploadedBytes, @"totalUploaded Bytes is not equal to fileSize"); @@ -1563,12 +1626,12 @@ - (void)testTMUploadSmallSizeWithProgressFeedback { } - (void)testArchiver { - NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"uploadRequest"]; + NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"uploadRequest"]; AWSS3TransferManagerUploadRequest *uploadRequest = [AWSS3TransferManagerUploadRequest new]; uploadRequest.bucket = @"test-bucket"; uploadRequest.key = @"test-key"; - uploadRequest.body = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"test-body"]]; + uploadRequest.body = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"test-body"]]; XCTAssertTrue([uploadRequest respondsToSelector:@selector(encodeWithCoder:)]); @@ -1581,279 +1644,6 @@ - (void)testArchiver { XCTAssertEqualObjects(unarchivedObject.body, uploadRequest.body); } -- (void)testCleanCacheTempFileAfterPause { - - AWSS3 *s3 = [AWSS3 defaultS3]; - XCTAssertNotNil(s3); - - AWSS3TransferManager *transferManager = [AWSS3TransferManager defaultS3TransferManager]; - XCTAssertNotNil(transferManager); - - - //Upload a file to the bucket - NSString *keyName = NSStringFromSelector(_cmd); - NSError *error = nil; - XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:tempLargeURL.path]); - NSString *fileName = [NSString stringWithFormat:@"%@-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - NSURL *testDataURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; - [[NSFileManager defaultManager] createSymbolicLinkAtURL:testDataURL withDestinationURL:tempLargeURL error:&error]; - XCTAssertNil(error, @"The request failed. error: [%@]", error); - - AWSS3TransferManagerUploadRequest *uploadRequest = [AWSS3TransferManagerUploadRequest new]; - uploadRequest.bucket = testBucketNameGeneral; - uploadRequest.key = keyName; - uploadRequest.body = testDataURL; - - - [[[transferManager upload:uploadRequest] continueWithBlock:^id(BFTask *task) { - XCTAssertNil(task.error, @"The request failed. error: [%@]", task.error); - XCTAssertTrue([task.result isKindOfClass:[AWSS3TransferManagerUploadOutput class]], @"The response object is not a class of [%@], got: %@", NSStringFromClass([NSURL class]),NSStringFromClass([task.result class])); - return nil; - }] waitUntilFinished]; - - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]]; - - //Download the same file from the bucket - AWSS3TransferManagerDownloadRequest *downloadRequest = [AWSS3TransferManagerDownloadRequest new]; - downloadRequest.bucket = testBucketNameGeneral; - downloadRequest.key = keyName; - - NSString *downloadFileName = [NSString stringWithFormat:@"%@-downloaded-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - downloadRequest.downloadingFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:downloadFileName]]; - - BFTask *pausedTaskOne = [[transferManager download:downloadRequest] continueWithBlock:^id(BFTask *task) { - XCTAssertNotNil(task.error,@"Expect got 'Cancelled' Error, but got nil"); - XCTAssertNil(task.result, @"task result should be nil since it has already been cancelled"); - XCTAssertEqualObjects(AWSS3TransferManagerErrorDomain, task.error.domain); - XCTAssertEqual(AWSS3TransferManagerErrorPaused, task.error.code); - return nil; - }]; - - //wait a few seconds and then pause it. - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; - [[[downloadRequest pause] continueWithBlock:^id(BFTask *task) { - XCTAssertNil(task.error, @"The request failed. error: [%@]", task.error); //should not return error if successfully paused. - return nil; - }] waitUntilFinished]; - - AWSLogDebug(@"(S3 Transfer Manager) Download Task has been paused."); - [pausedTaskOne waitUntilFinished]; //make sure callback has been called. - - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]]; - - - // clean the cache, make sure the temp file is deleted - NSURL *tempFileURL = [downloadRequest valueForKey:@"temporaryFileURL"]; - XCTAssertNotNil(tempFileURL,@"download request's temp file should not be nil"); - XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:[tempFileURL path]],@"temp file should exist if download is paused"); - - NSLog(@"%s:%d the object's temp file is %@",__FILE__,__LINE__,[tempFileURL path]); - - [transferManager clearCache]; - [NSThread sleepForTimeInterval:2.0f]; - - XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:[tempFileURL path]],@"download request's temp file should be deleted, but not"); - - - //Delete the object - AWSS3DeleteObjectRequest *deleteObjectRequest = [AWSS3DeleteObjectRequest new]; - deleteObjectRequest.bucket = testBucketNameGeneral; - deleteObjectRequest.key = keyName; - - [[[s3 deleteObject:deleteObjectRequest] continueWithBlock:^id(BFTask *task) { - XCTAssertNil(task.error, @"The request failed. error: [%@]", task.error); - XCTAssertTrue([task.result isKindOfClass:[AWSS3DeleteObjectOutput class]],@"The response object is not a class of [%@], got: %@", NSStringFromClass([AWSS3DeleteObjectOutput class]),NSStringFromClass([task.result class])); - return nil; - }] waitUntilFinished]; - -} - -- (void)testCleanCacheTempFileAfterCancel{ - NSCondition *condition = [NSCondition new]; - - AWSS3 *s3 = [AWSS3 defaultS3]; - XCTAssertNotNil(s3); - AWSS3TransferManager *transferManager = [AWSS3TransferManager defaultS3TransferManager]; - XCTAssertNotNil(transferManager); - - //Upload a large file to the bucket - NSString *keyName =[NSString stringWithFormat:@"%@-large",NSStringFromSelector(_cmd)]; - NSError *error = nil; - XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:tempLargeURL.path]); - NSString *fileName = [NSString stringWithFormat:@"%@-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - NSURL *testDataURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; - [[NSFileManager defaultManager] createSymbolicLinkAtURL:testDataURL withDestinationURL:tempLargeURL error:&error]; - XCTAssertNil(error, @"The request failed. error: [%@]", error); - - - - AWSS3TransferManagerUploadRequest *uploadRequest = [AWSS3TransferManagerUploadRequest new]; - uploadRequest.bucket = testBucketNameGeneral; - uploadRequest.key = keyName; - uploadRequest.body = testDataURL; - - - [[[transferManager upload:uploadRequest] continueWithBlock:^id(BFTask *task) { - XCTAssertNil(task.error, @"The request failed. error: [%@]", task.error); - XCTAssertTrue([task.result isKindOfClass:[AWSS3TransferManagerUploadOutput class]], @"The response object is not a class of [%@], got: %@", NSStringFromClass([NSURL class]),NSStringFromClass([task.result class])); - return nil; - }] waitUntilFinished]; - - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]]; - - //Check if uploaded file is on S3 - AWSS3ListObjectsRequest *listObjectReq = [AWSS3ListObjectsRequest new]; - listObjectReq.bucket = testBucketNameGeneral; - - [[[s3 listObjects:listObjectReq] continueWithBlock:^id(BFTask *task) { - XCTAssertNil(task.error, @"The request failed. error: [%@]", task.error); - XCTAssertTrue([task.result isKindOfClass:[AWSS3ListObjectsOutput class]],@"The response object is not a class of [%@]", NSStringFromClass([AWSS3ListObjectsOutput class])); - AWSS3ListObjectsOutput *listObjectsOutput = task.result; - XCTAssertEqualObjects(listObjectsOutput.name, testBucketNameGeneral); - - BOOL match = NO; - for (AWSS3Object *s3Object in listObjectsOutput.contents) { - if ([s3Object.key isEqualToString:keyName] && [s3Object.key isEqualToString:keyName]) { - match = YES; - } - } - - XCTAssertTrue(match, @"Didn't find the uploaded object in the bucket!"); - - return nil; - }] waitUntilFinished]; - - //Download the same file from the bucket - AWSS3TransferManagerDownloadRequest *downloadRequest = [AWSS3TransferManagerDownloadRequest new]; - downloadRequest.bucket = testBucketNameGeneral; - downloadRequest.key = keyName; - - NSString *downloadFileName = [NSString stringWithFormat:@"%@-downloaded-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - downloadRequest.downloadingFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:downloadFileName]]; - - [[transferManager download:downloadRequest] continueWithBlock:^id(BFTask *task) { - //Should return Cancelled Task Error - XCTAssertNotNil(task.error,@"Expect got 'Cancelled' Error, but got nil"); - XCTAssertEqualObjects(AWSS3TransferManagerErrorDomain, task.error.domain); - XCTAssertEqual(AWSS3TransferManagerErrorCancelled, task.error.code); - - [condition signal]; - return nil; - - }]; - - //Wait a few second and then cancel the task - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]]; - [condition lock]; - [downloadRequest cancel]; - [condition waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:180]]; - [condition unlock]; - - - // clean the cache, make sure the temp file is deleted - NSURL *tempFileURL = [downloadRequest valueForKey:@"temporaryFileURL"]; - NSLog(@"%s:%d temp file is %@",__FILE__,__LINE__,[tempFileURL path]); - XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:[tempFileURL path]],@"download request's temp file should be deleted, but not"); - - //Delete the object - AWSS3DeleteObjectRequest *deleteObjectRequest = [AWSS3DeleteObjectRequest new]; - deleteObjectRequest.bucket = testBucketNameGeneral; - deleteObjectRequest.key = keyName; - - [[[s3 deleteObject:deleteObjectRequest] continueWithBlock:^id(BFTask *task) { - XCTAssertNil(task.error, @"The request failed. error: [%@]", task.error); - XCTAssertTrue([task.result isKindOfClass:[AWSS3DeleteObjectOutput class]],@"The response object is not a class of [%@], got: %@", NSStringFromClass([AWSS3DeleteObjectOutput class]),NSStringFromClass([task.result class])); - return nil; - }] waitUntilFinished]; - -} - -- (void)testCleanCacheTempFileAgeLimit { - - AWSS3 *s3 = [AWSS3 defaultS3]; - XCTAssertNotNil(s3); - - AWSS3TransferManager *transferManager = [AWSS3TransferManager defaultS3TransferManager]; - XCTAssertNotNil(transferManager); - - - //Upload a file to the bucket - NSString *keyName = NSStringFromSelector(_cmd); - NSError *error = nil; - XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:tempLargeURL.path]); - NSString *fileName = [NSString stringWithFormat:@"%@-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - NSURL *testDataURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; - [[NSFileManager defaultManager] createSymbolicLinkAtURL:testDataURL withDestinationURL:tempLargeURL error:&error]; - XCTAssertNil(error, @"The request failed. error: [%@]", error); - - AWSS3TransferManagerUploadRequest *uploadRequest = [AWSS3TransferManagerUploadRequest new]; - uploadRequest.bucket = testBucketNameGeneral; - uploadRequest.key = keyName; - uploadRequest.body = testDataURL; - - - [[[transferManager upload:uploadRequest] continueWithBlock:^id(BFTask *task) { - XCTAssertNil(task.error, @"The request failed. error: [%@]", task.error); - XCTAssertTrue([task.result isKindOfClass:[AWSS3TransferManagerUploadOutput class]], @"The response object is not a class of [%@], got: %@", NSStringFromClass([NSURL class]),NSStringFromClass([task.result class])); - return nil; - }] waitUntilFinished]; - - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]]; - - //Download the same file from the bucket - AWSS3TransferManagerDownloadRequest *downloadRequest = [AWSS3TransferManagerDownloadRequest new]; - downloadRequest.bucket = testBucketNameGeneral; - downloadRequest.key = keyName; - - NSString *downloadFileName = [NSString stringWithFormat:@"%@-downloaded-%@",NSStringFromSelector(_cmd),testBucketNameGeneral]; - downloadRequest.downloadingFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:downloadFileName]]; - - BFTask *pausedTaskOne = [[transferManager download:downloadRequest] continueWithBlock:^id(BFTask *task) { - XCTAssertNotNil(task.error,@"Expect got 'Cancelled' Error, but got nil"); - XCTAssertNil(task.result, @"task result should be nil since it has already been cancelled"); - XCTAssertEqualObjects(AWSS3TransferManagerErrorDomain, task.error.domain); - XCTAssertEqual(AWSS3TransferManagerErrorPaused, task.error.code); - return nil; - }]; - - //wait a few seconds and then pause it. - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; - [[[downloadRequest pause] continueWithBlock:^id(BFTask *task) { - XCTAssertNil(task.error, @"The request failed. error: [%@]", task.error); //should not return error if successfully paused. - return nil; - }] waitUntilFinished]; - - AWSLogDebug(@"(S3 Transfer Manager) Download Task has been paused."); - [pausedTaskOne waitUntilFinished]; //make sure callback has been called. - - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]]; - - - // clean the cache, make sure the temp file is deleted - NSURL *tempFileURL = [downloadRequest valueForKey:@"temporaryFileURL"]; - XCTAssertNotNil(tempFileURL,@"download request's temp file should not be nil"); - XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:[tempFileURL path]],@"temp file should exist if download is paused"); - - transferManager.diskAgeLimit = 10; - - [NSThread sleepForTimeInterval:11]; - - XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:[tempFileURL path]],@"download request's temp file should be deleted, but not"); - - transferManager.diskAgeLimit = 0.0; - - //Delete the object - AWSS3DeleteObjectRequest *deleteObjectRequest = [AWSS3DeleteObjectRequest new]; - deleteObjectRequest.bucket = testBucketNameGeneral; - deleteObjectRequest.key = keyName; - - [[[s3 deleteObject:deleteObjectRequest] continueWithBlock:^id(BFTask *task) { - XCTAssertNil(task.error, @"The request failed. error: [%@]", task.error); - XCTAssertTrue([task.result isKindOfClass:[AWSS3DeleteObjectOutput class]],@"The response object is not a class of [%@], got: %@", NSStringFromClass([AWSS3DeleteObjectOutput class]),NSStringFromClass([task.result class])); - return nil; - }] waitUntilFinished]; -} - #pragma mark - + (BOOL)createBucketWithName:(NSString *)bucketName { diff --git a/AWSiOSSDKTests/AWSSimpleDBTests.m b/AWSiOSSDKTests/AWSSimpleDBTests.m index 6592a0377f8..59a46210e04 100644 --- a/AWSiOSSDKTests/AWSSimpleDBTests.m +++ b/AWSiOSSDKTests/AWSSimpleDBTests.m @@ -75,7 +75,6 @@ - (void)testListDomains { } - (void)testSelect { - [[AWSLogger defaultLogger] setLogLevel:AWSLogLevelVerbose]; AWSSimpleDB *sdb = [AWSSimpleDB defaultSimpleDB]; AWSSimpleDBSelectRequest *selectRequest = [AWSSimpleDBSelectRequest new]; @@ -95,7 +94,6 @@ - (void)testSelect { } - (void)testSelectWithNonEnglishTableName { - [[AWSLogger defaultLogger] setLogLevel:AWSLogLevelVerbose]; AWSSimpleDB *sdb = [AWSSimpleDB defaultSimpleDB]; AWSSimpleDBSelectRequest *selectRequest = [AWSSimpleDBSelectRequest new]; diff --git a/AWSiOSSDKv2.podspec b/AWSiOSSDKv2.podspec index fb61ffe69f2..5ebbaaec58a 100644 --- a/AWSiOSSDKv2.podspec +++ b/AWSiOSSDKv2.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'AWSiOSSDKv2' - s.version = '2.0.8' + s.version = '2.0.9' s.summary = 'Amazon Web Services SDK for iOS.' s.description = 'The AWS SDK for iOS provides a library, code samples, and documentation for developers to build connected mobile applications using AWS.' @@ -35,61 +35,61 @@ Pod::Spec.new do |s| autoscaling.source_files = 'AutoScaling/*.{h,m}' autoscaling.resources = ['AutoScaling/Resources/*.json'] end - + s.subspec 'CloudWatch' do |cloudwatch| cloudwatch.dependency 'AWSiOSSDKv2/AWSCore' cloudwatch.source_files = 'CloudWatch/*.{h,m}' cloudwatch.resources = ['CloudWatch/Resources/*.json'] end - + s.subspec 'DynamoDB' do |ddb| ddb.dependency 'AWSiOSSDKv2/AWSCore' ddb.source_files = 'DynamoDB/*.{h,m}' ddb.resources = ['DynamoDB/Resources/*.json'] end - + s.subspec 'EC2' do |ec2| ec2.dependency 'AWSiOSSDKv2/AWSCore' ec2.source_files = 'EC2/*.{h,m}' ec2.resources = ['EC2/Resources/*.json'] end - + s.subspec 'ElasticLoadBalancing' do |elasticloadbalancing| elasticloadbalancing.dependency 'AWSiOSSDKv2/AWSCore' elasticloadbalancing.source_files = 'ElasticLoadBalancing/*.{h,m}' elasticloadbalancing.resources = ['ElasticLoadBalancing/Resources/*.json'] end - + s.subspec 'Kinesis' do |kinesis| kinesis.dependency 'AWSiOSSDKv2/AWSCore' kinesis.source_files = 'Kinesis/*.{h,m}' kinesis.resources = ['Kinesis/Resources/*.json'] end - + s.subspec 'S3' do |s3| s3.dependency 'AWSiOSSDKv2/AWSCore' s3.source_files = 'S3/*.{h,m}' s3.resources = ['S3/Resources/*.json'] end - + s.subspec 'SES' do |ses| ses.dependency 'AWSiOSSDKv2/AWSCore' ses.source_files = 'SES/*.{h,m}' ses.resources = ['SES/Resources/*.json'] end - + s.subspec 'SimpleDB' do |simpledb| simpledb.dependency 'AWSiOSSDKv2/AWSCore' simpledb.source_files = 'SimpleDB/*.{h,m}' simpledb.resources = ['SimpleDB/Resources/*.json'] end - + s.subspec 'SNS' do |sns| sns.dependency 'AWSiOSSDKv2/AWSCore' sns.source_files = 'SNS/*.{h,m}' sns.resources = ['SNS/Resources/*.json'] end - + s.subspec 'SQS' do |sqs| sqs.dependency 'AWSiOSSDKv2/AWSCore' sqs.source_files = 'SQS/*.{h,m}' diff --git a/AWSiOSSDKv2.xcodeproj/project.pbxproj b/AWSiOSSDKv2.xcodeproj/project.pbxproj index 4a4fe7bef0d..2674b18ff7a 100644 --- a/AWSiOSSDKv2.xcodeproj/project.pbxproj +++ b/AWSiOSSDKv2.xcodeproj/project.pbxproj @@ -2408,6 +2408,7 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_MODULES_AUTOLINK = NO; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; @@ -2446,6 +2447,7 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_MODULES_AUTOLINK = NO; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; diff --git a/AutoScaling/AWSAutoScaling.m b/AutoScaling/AWSAutoScaling.m index cac5c46825f..25f43a7a8cf 100644 --- a/AutoScaling/AWSAutoScaling.m +++ b/AutoScaling/AWSAutoScaling.m @@ -117,25 +117,35 @@ @interface AWSAutoScalingRequestRetryHandler : AWSURLRequestRetryHandler @implementation AWSAutoScalingRequestRetryHandler - (AWSNetworkingRetryType)shouldRetry:(uint32_t)currentRetryCount - response:(NSHTTPURLResponse *)response - data:(NSData *)data - error:(NSError *)error { + response:(NSHTTPURLResponse *)response + data:(NSData *)data + error:(NSError *)error { AWSNetworkingRetryType retryType = [super shouldRetry:currentRetryCount - response:response - data:data - error:error]; + response:response + data:data + error:error]; if(retryType == AWSNetworkingRetryTypeShouldNotRetry - && [error.domain isEqualToString:AWSAutoScalingErrorDomain] && currentRetryCount < self.maxRetryCount) { - switch (error.code) { - case AWSAutoScalingErrorIncompleteSignature: - case AWSAutoScalingErrorInvalidClientTokenId: - case AWSAutoScalingErrorMissingAuthenticationToken: - retryType = AWSNetworkingRetryTypeShouldRefreshCredentialsAndRetry; - break; - - default: - break; + if ([error.domain isEqualToString:AWSAutoScalingErrorDomain]) { + switch (error.code) { + case AWSAutoScalingErrorIncompleteSignature: + case AWSAutoScalingErrorInvalidClientTokenId: + case AWSAutoScalingErrorMissingAuthenticationToken: + retryType = AWSNetworkingRetryTypeShouldRefreshCredentialsAndRetry; + break; + + default: + break; + } + } else if ([error.domain isEqualToString:AWSGeneralErrorDomain]) { + switch (error.code) { + case AWSGeneralErrorSignatureDoesNotMatch: + retryType = AWSNetworkingRetryTypeShouldCorrectClockSkewAndRetry; + break; + + default: + break; + } } } diff --git a/CloudWatch/AWSCloudWatch.m b/CloudWatch/AWSCloudWatch.m index d1344dfeed0..b0685f0a2af 100644 --- a/CloudWatch/AWSCloudWatch.m +++ b/CloudWatch/AWSCloudWatch.m @@ -120,25 +120,35 @@ @interface AWSCloudWatchRequestRetryHandler : AWSURLRequestRetryHandler @implementation AWSCloudWatchRequestRetryHandler - (AWSNetworkingRetryType)shouldRetry:(uint32_t)currentRetryCount - response:(NSHTTPURLResponse *)response - data:(NSData *)data - error:(NSError *)error { + response:(NSHTTPURLResponse *)response + data:(NSData *)data + error:(NSError *)error { AWSNetworkingRetryType retryType = [super shouldRetry:currentRetryCount - response:response - data:data - error:error]; + response:response + data:data + error:error]; if(retryType == AWSNetworkingRetryTypeShouldNotRetry - && [error.domain isEqualToString:AWSCloudWatchErrorDomain] && currentRetryCount < self.maxRetryCount) { - switch (error.code) { - case AWSCloudWatchErrorIncompleteSignature: - case AWSCloudWatchErrorInvalidClientTokenId: - case AWSCloudWatchErrorMissingAuthenticationToken: - retryType = AWSNetworkingRetryTypeShouldRefreshCredentialsAndRetry; - break; - - default: - break; + if ([error.domain isEqualToString:AWSCloudWatchErrorDomain]) { + switch (error.code) { + case AWSCloudWatchErrorIncompleteSignature: + case AWSCloudWatchErrorInvalidClientTokenId: + case AWSCloudWatchErrorMissingAuthenticationToken: + retryType = AWSNetworkingRetryTypeShouldRefreshCredentialsAndRetry; + break; + + default: + break; + } + } else if ([error.domain isEqualToString:AWSGeneralErrorDomain]) { + switch (error.code) { + case AWSGeneralErrorSignatureDoesNotMatch: + retryType = AWSNetworkingRetryTypeShouldCorrectClockSkewAndRetry; + break; + + default: + break; + } } } @@ -194,7 +204,7 @@ - (instancetype)initWithConfiguration:(AWSServiceConfiguration *)configuration { _configuration.baseURL = _configuration.endpoint.URL; _configuration.requestInterceptors = @[[AWSNetworkingRequestInterceptor new], signer]; - _configuration.retryHandler = [[AWSURLRequestRetryHandler alloc] initWithMaximumRetryCount:_configuration.maxRetryCount]; + _configuration.retryHandler = [[AWSCloudWatchRequestRetryHandler alloc] initWithMaximumRetryCount:_configuration.maxRetryCount]; _configuration.headers = @{@"Host" : _configuration.endpoint.hostName}; _networking = [AWSNetworking networking:_configuration]; diff --git a/DynamoDB/AWSDynamoDBObjectMapper.m b/DynamoDB/AWSDynamoDBObjectMapper.m index 4f8a773aed8..be317186eb4 100644 --- a/DynamoDB/AWSDynamoDBObjectMapper.m +++ b/DynamoDB/AWSDynamoDBObjectMapper.m @@ -15,6 +15,7 @@ #import "AWSDynamoDBObjectMapper.h" #import "AWSDynamoDB.h" +#import "Bolts.h" @interface AWSDynamoDBAttributeValue (AWSDynamoDBObjectMapper) diff --git a/ElasticLoadBalancing/AWSElasticLoadBalancing.m b/ElasticLoadBalancing/AWSElasticLoadBalancing.m index d98d26a939f..ca2b978ddb7 100644 --- a/ElasticLoadBalancing/AWSElasticLoadBalancing.m +++ b/ElasticLoadBalancing/AWSElasticLoadBalancing.m @@ -127,25 +127,35 @@ @interface AWSElasticLoadBalancingRequestRetryHandler : AWSURLRequestRetryHandle @implementation AWSElasticLoadBalancingRequestRetryHandler - (AWSNetworkingRetryType)shouldRetry:(uint32_t)currentRetryCount - response:(NSHTTPURLResponse *)response - data:(NSData *)data - error:(NSError *)error { + response:(NSHTTPURLResponse *)response + data:(NSData *)data + error:(NSError *)error { AWSNetworkingRetryType retryType = [super shouldRetry:currentRetryCount - response:response - data:data - error:error]; + response:response + data:data + error:error]; if(retryType == AWSNetworkingRetryTypeShouldNotRetry - && [error.domain isEqualToString:AWSElasticLoadBalancingErrorDomain] && currentRetryCount < self.maxRetryCount) { - switch (error.code) { - case AWSElasticLoadBalancingErrorIncompleteSignature: - case AWSElasticLoadBalancingErrorInvalidClientTokenId: - case AWSElasticLoadBalancingErrorMissingAuthenticationToken: - retryType = AWSNetworkingRetryTypeShouldRefreshCredentialsAndRetry; - break; - - default: - break; + if ([error.domain isEqualToString:AWSElasticLoadBalancingErrorDomain]) { + switch (error.code) { + case AWSElasticLoadBalancingErrorIncompleteSignature: + case AWSElasticLoadBalancingErrorInvalidClientTokenId: + case AWSElasticLoadBalancingErrorMissingAuthenticationToken: + retryType = AWSNetworkingRetryTypeShouldRefreshCredentialsAndRetry; + break; + + default: + break; + } + } else if ([error.domain isEqualToString:AWSGeneralErrorDomain]) { + switch (error.code) { + case AWSGeneralErrorSignatureDoesNotMatch: + retryType = AWSNetworkingRetryTypeShouldCorrectClockSkewAndRetry; + break; + + default: + break; + } } } diff --git a/Podfile b/Podfile index 1705f987eaf..6ff9a98d48e 100644 --- a/Podfile +++ b/Podfile @@ -1,3 +1,5 @@ +source 'https://github.com/CocoaPods/Specs.git' + pod 'Bolts', '~> 1.1.1' pod 'Mantle', '~> 1.4.1' pod 'TMCache', '~> 1.2.1' diff --git a/README.md b/README.md index 6682865efae..93a1990b494 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,207 @@ -# Version 2 of the AWS SDK for iOS +#Version 2 of the AWS SDK for iOS [![Version](http://cocoapod-badges.herokuapp.com/v/AWSiOSSDKv2/badge.png)](http://cocoadocs.org/docsets/AWSiOSSDKv2) [![Platform](http://cocoapod-badges.herokuapp.com/p/AWSiOSSDKv2/badge.png)](http://cocoadocs.org/docsets/AWSiOSSDKv2) -**Version 2 of the AWS Mobile SDK for iOS has reached General Availability (GA) and is no longer in Developer Preview. Version 1 is deprecated as of September 29, 2014 and will continue to be available until December 31, 2014 in our [aws-sdk-ios-v1](https://github.com/aws/aws-sdk-ios-v1) repository. If you are building new apps, we recommend you use Version 2.** +**Version 2 of the AWS Mobile SDK for iOS has reached General Availability (GA) and is no longer in Developer Preview. Version 1 is deprecated as of September 29, 2014, and will continue to be available until December 31, 2014, in our [aws-sdk-ios-v1](https://github.com/aws/aws-sdk-ios-v1) repository. If you are building new apps, we recommend you use Version 2.** -## Highlights +##Highlights -* **Amazon Cognito** – is a simple user identity and synchronization service that helps you securely manage and synchronize app data for your users across their mobile devices. With Amazon Cognito, you can save any kind of data, such as app preferences or game state, in the AWS Cloud without writing any backend code or managing any infrastructure. -* **Amazon Mobile Analytics** – is a service for collecting, visualizing and understanding app usage data at scale. Amazon Mobile Analytics reports are typically updated within 60 minutes from when data are received. Amazon Mobile Analytics is built to scale with the business and can collect and process billions of events from millions of endpoints. -* **Amazon Kinesis Recorder** – enables you to reliably record data to an Amazon Kinesis data stream from your mobile app. Kinesis Recorder batches requests to handle intermittent network connection and enable you to record events even when the device is offline. +* **Amazon Cognito** – A simple user identity and synchronization service that helps you securely manage and synchronize app data for your users across their mobile devices. With Amazon Cognito, you can save any kind of data, such as app preferences or game state, in the AWS cloud without writing any backend code or managing any infrastructure. +* **Amazon Mobile Analytics** – A service for collecting, visualizing, and understanding app usage data at scale. Amazon Mobile Analytics reports are typically updated within 60 minutes of data being received. Amazon Mobile Analytics is built to scale with the business and can collect and process billions of events from millions of endpoints. +* **Amazon Kinesis Recorder** – Enables you to reliably record data to an Amazon Kinesis data stream from your mobile app. Kinesis Recorder batches requests to handle intermittent network connections, and it enables you to record events even when the device is offline. * **Amazon DynamoDB Object Mapper** - We have made it easier to use DynamoDB from the AWS SDK for iOS by providing the DynamoDB Object Mapper for iOS. The DynamoDB Object Mapper makes it easy to set up connections to a DynamoDB database and supports high-level operations like creating, getting, querying, updating, and deleting records. -* **Amazon S3 Transfer Manager** - We have rebuilt the S3TransferManager to utilize BFTask in AWS SDK for iOS. It has a clean interface, and all of the operations are now asynchronous. +* **Amazon S3 Transfer Manager** - We have rebuilt the S3TransferManager to utilize BFTask in the AWS SDK for iOS. It has a clean interface, and all of the operations are now asynchronous. * **ARC support** - The AWS SDK for iOS is now ARC enabled from the ground up to improve overall memory management. * **BFTask support** - With native BFTask support in the AWS SDK for iOS, you can chain async requests instead of nesting them. This makes the logic cleaner while keeping the code more readable. * **Conforming Objective-C recommendations** - The AWS SDK for iOS conforms to Objective-C best practices. The SDK returns NSErrors instead of throwing exceptions. iOS developers will now feel at home when using the AWS Mobile SDK. -* **Official CocoaPods support** - Including the AWS SDK for iOS in your project is now easier than ever. You just need to add `pod "AWSiOSSDKv2"` and `pod "AWSCognitoSync"` to your Podfile. +* **Official CocoaPods support** - Including the AWS SDK for iOS in your project is now easier than ever. You just need to add `pod 'AWSiOSSDKv2'` and `pod 'AWSCognitoSync'` to your Podfile. -## Requirements +##Setting Up + +To get started with the AWS SDK for iOS, you can set up the SDK and start building a new project, or you can integrate the SDK in an existing project. You can also run the samples to get a sense of how the SDK works. + +The AWS Mobile SDK for iOS supports the following versions of software: * Xcode 5 and later * iOS 7 and later -## Installation +You can check out the [SDK source code](https://github.com/aws/aws-sdk-ios). + +##Include the SDK for iOS in an Existing Application + +The [sample apps](https://github.com/awslabs/aws-sdk-ios-samples) are standalone projects that are already set up for you. You can also integrate the SDK for iOS with an existing application. If you have an existing app in which you'd like to use AWS, there are two ways to import the AWS Mobile SDK for iOS into your project: + +###CocoaPods + +1. The AWS Mobile SDK for iOS is available through [CocoaPods](http://cocoapods.org). If you have not installed CocoaPods, install CocoaPods by running the command: + + $ sudo gem install cocoapods + +1. In your project directory (the directory where your `*.xcodeproj` file is), create a plain text file named **Podfile** (without any file extension) and add the following lines. If you want to use [Amazon Cognito Sync](http://aws.amazon.com/cognito/), make sure to include `pod 'AWSCognitoSync'` as well. + + source 'https://github.com/CocoaPods/Specs.git' + + pod 'AWSiOSSDKv2' + pod 'AWSCognitoSync' + + ![image](readme-images/cocoapods-setup-01.png?raw=true) + +1. Then run the following command: + + $ pod install + +1. Open up `*.xcworkspace` with Xcode and start using the SDK. + + ![image](readme-images/cocoapods-setup-02.png?raw=true) + + **Note**: Do **NOT** use `*.xcodeproj`. If you open up a project file instead of a workspace, you receive an error: + + ld: library not found for -lPods-AWSiOSSDKv2 + clang: error: linker command failed with exit code 1 (use -v to see invocation) + +###Frameworks + +1. Download the SDK from our [AWS Mobile SDK](http://aws.amazon.com/mobile/sdk) page. The SDK is stored in a compressed file archive named `aws-ios-sdk-#.#.#` (where `#.#.#` represents the version number, so for version 2.0.0, the filename is `aws-ios-sdk-2.0.0`). + + **Note**: The size of **AWSiOSSDKv2.framework** is > 65MB; however, it does not add > 65MB to your app binary when imported to your project because: + + * **Only objects that you use in your app are included in your app binary from the framework** + + **AWSiOSSDKv2.framework** contains a static library, and object code in the library is incorporated in your app binary file at build time. When the `-ObjC` and `-all_load` linker flags are **NOT** used, Xcode is smart enough to figure out what objects from the framework are necessary for your app and include just those objects. **AWSiOSSDKv2.framework** is packaged so that you do not need to include these linker flags. (If you are using a third-party framework that requires you to include the `-ObjC` or `-all_load` linker flags, you can use `-force_load` instead to include those frameworks.) + + For example, if you only use Amazon DynamoDB in your app and none of the other supported services, your app won't include object code for those other services. Unless you use every single object from the framework, only a portion of the code from the framework will be included in your app. + + * **The framework contains five architectures, while apps on a device need at most three** + + We currently compile the AWS Mobile SDK for iOS for five architectures: `armv7`, `armv7s`, `arm64`, `i386`, and `x86_64`. If you want to optimize your app for 64-bit devices including iPhone 6 and iPhone 5S, you need to build your app with `arm64` support. 32-bit iOS devices that the AWS SDK for iOS supports use the `armv7` and `armv7s` architectures. + + The 64-bit iPhone simulators use `x86_64`, and 32-bit simulators use `i386` because they run on the Mac. We support these architectures so that the developers can run their apps with our framework on the simulator for testing. `x86_64` and `i386` support is essential for testing, but the code is unnecessary for apps on the App Store. Even if you use every single object from the AWS Mobile SDK, the app that you submit to Apple never includes about two fifths of the code included in the framework. + + * **Apps on the App Store are compressed** + + After you submit your app to Apple, it is encrypted for DRM purposes and re-compressed. This leads to an even smaller footprint. + +1. With your project open in Xcode, Control+click **Frameworks** and then click **Add files to "\"...**. + +1. In Finder, navigate to the `AWSiOSSDKv2.framework` file and select it. Click **Add**. If you want to use [Amazon Cognito Sync](http://aws.amazon.com/cognito/), you also need to add the `AWSCognitoSync.framework`, which is in the **extras** directory. + +1. Following the same procedure, add the following frameworks, located in the **third-party** directory, into your project. -AWSiOSSDKv2 is available through [CocoaPods](http://cocoapods.org), to install -it simply add the following line to your **Podfile**: + * `Bolts.framework` (If your application uses the Facebook SDK, you may not need this framework, as it's already included with the Facebook SDK.) + * `GZIP.framework` + * `Mantle.framework` + * `Reachability.framework` + * `TMCache.framework` + * `UICKeyChainStore.framework` + * `XMLDictionary.framework` - pod "AWSiOSSDKv2" - pod "AWSCognitoSync" +1. Drag and drop the following JSON files, located in the **service-definitions** directory, into your project. -The detailed instructions are available at [Setup the SDK for iOS](http://docs.aws.amazon.com/mobile/sdkforios/developerguide/setup.html). + * `autoscaling-2011-01-01.json` + * `cib-2014-06-30.json` + * `css-2014-06-30.json` + * `dynamodb-2012-08-10.json` + * `ec2-2014-06-15.json` + * `elasticloadbalancing-2012-06-01.json` + * `email-2010-12-01.json` + * `kinesis-2013-12-02.json` + * `mobileanalytics-2014-06-30.json` + * `monitoring-2010-08-01.json` + * `s3-2006-03-01.json` + * `sdb-2009-04-15.json` + * `sns-2010-03-31.json` + * `sqs-2012-11-05.json` + * `sts-2011-06-15.json` -## Getting Started is Easy Using Swift +1. Open a target for your project, select **Build Phases**, expand **Link Binary With Libraries**, click the **+** button, and add `libsqlite3.dylib` and `libz.dylib`. -It is easy to use the AWS SDK for iOS with Swift. Please see five simple steps below to get started with Swift. +##Update the SDK to a Newer Version -1. Create an Objective-C bridging header file. -1. Import the service headers in the bridging header. +When we release a new version of the SDK, you can pick up the changes as described below. - #import "DynamoDB.h" +###CocoaPods -1. Point **SWIFT_OBJC_BRIDGING_HEADER** to the bridging header by going to **Your Target** => **Build Settings** => **SWIFT_OBJC_BRIDGING_HEADER**. +1. Run the following command in your project directory. CocoaPods automatically picks up the new changes. + + $ pod update + + **Note**: If your pod is having an issue, you can delete `Podfile.lock` and `Pods/` then run `pod install` to cleanly install the SDK. + + ![image](readme-images/cocoapods-setup-03.png?raw=true) + +###Frameworks + +1. In Xcode select the following frameworks and hit **delete** on your keyboard. Then select **Move to Trash**: + + * `AWSiOSSDKv2.framework` + * `AWSCognitoSync.framework` + * `Bolts.framework` + * `GZIP.framework` + * `Mantle.framework` + * `Reachability.framework` + * `TMCache.framework` + * `UICKeyChainStore.framework` + * `XMLDictionary.framework` + +1. Also, delete the JSON files: + + * `autoscaling-2011-01-01.json` + * `cib-2014-06-30.json` + * `css-2014-06-30.json` + * `dynamodb-2012-08-10.json` + * `ec2-2014-06-15.json` + * `elasticloadbalancing-2012-06-01.json` + * `email-2010-12-01.json` + * `kinesis-2013-12-02.json` + * `mobileanalytics-2014-06-30.json` + * `monitoring-2010-08-01.json` + * `s3-2006-03-01.json` + * `sdb-2009-04-15.json` + * `sns-2010-03-31.json` + * `sqs-2012-11-05.json` + * `sts-2011-06-15.json` + +1. Follow the installation process above to include the new version of the SDK. + +##Getting Started with Swift + +1. Create an Objective-C bridging header file using Xcode. + +1. In the bridging header, import the appropriate headers for the services you are using. The header file import convention for CocoaPods is `#import "SERVICENAME.h"`, and for frameworks it is `#import `, as in the following examples: + + **CocoaPods** + + #import "AWSCore.h" + #import "S3.h" + #import "DynamoDB.h" + #import "SQS.h" + #import "SNS.h" + + **Frameworks** + + #import + #import + #import + #import + #import + + ![image](readme-images/objc-bridging-header-01.png?raw=true) + +1. From **Your Target** > **Build Settings** > **Objective-C Bridging Header**, point **Objective-C Bridging Header** to the bridging header you just created. + + ![image](readme-images/objc-bridging-header-02.png?raw=true) + + +1. Import the AWSCore header in the application delegate. + + **CocoaPods** + + #import "AWSCore.h" + + **Frameworks** + + #import 1. Create a default service configuration by adding the following code snippet in the `@optional func application(_ application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool` application delegate method. @@ -69,14 +229,22 @@ It is easy to use the AWS SDK for iOS with Swift. Please see five simple steps b return nil } + + **Note**: Most of the service client classes have a singleton method to get a default client. The naming convention is `+ defaultSERVICENAME` (e.g. `+ defaultDynamoDB` in the above code snippet). This singleton method creates a service client with `defaultServiceConfiguration`, which you set up in step 5, and maintains a strong reference to the client. + +##Getting Started with Objective-C -## Using Objective-C +1. Import the AWSCore header in the application delegate. -1. Import AWSCore header in the application delegate. + **CocoaPods** + + #import "AWSCore.h" - #import "AWSCore.h" + **Frameworks** + + #import -1. Create a default service configuration by adding the following code snippet in the `application:didFinishLaunchingWithOptions:` application delegate method. +1. Create a default service configuration by adding the following code snippet in the `- application:didFinishLaunchingWithOptions:` application delegate method. AWSCognitoCredentialsProvider *credentialsProvider = [AWSCognitoCredentialsProvider credentialsWithRegionType:AWSRegionUSEast1 accountId:AWSAccountID @@ -87,9 +255,21 @@ It is easy to use the AWS SDK for iOS with Swift. Please see five simple steps b credentialsProvider:credentialsProvider]; [AWSServiceManager defaultServiceManager].defaultServiceConfiguration = configuration; -1. Import service headers where you want to use the services. +1. Import service headers where you want to use the services. The header file import convention for CocoaPods is `#import "SERVICENAME.h"`, and for frameworks it is `#import `, as in the following examples: - #import "S3.h" + **CocoaPods** + + #import "S3.h" + #import "DynamoDB.h" + #import "SQS.h" + #import "SNS.h" + + **Frameworks** + + #import + #import + #import + #import 1. Make a call to the AWS services. @@ -105,12 +285,111 @@ It is easy to use the AWS SDK for iOS with Swift. Please see five simple steps b return nil; }]; -## Talk to Us Visit the [Issues](/aws/aws-ask-ios-v2/issues) to leave feedback and to connect with other users of the SDK. + **Note**: Most of the service client classes have a singleton method to get a default client. The naming convention is `+ defaultSERVICENAME` (e.g. `+ defaultS3TransferManager` in the above code snippet). This singleton method creates a service client with `defaultServiceConfiguration`, which you set up in step 5, and maintains a strong reference to the client. + +##BFTask + +With native BFTask support in the SDK for iOS, you can chain async requests instead of nesting them. It makes the logic cleaner, while keeping the code more readable. Read this [blog post](http://mobile.awsblog.com/post/Tx2B17V9NSVLP3I/The-AWS-Mobile-SDK-for-iOS-How-to-use-BFTask) to learn how to use BFTask. + +##Logging + +Changing log levels during development may make debugging easier. You can change the log level by importing AWSCore.h and calling: + +**Swift** + + AWSLogger.defaultLogger().logLevel = .Verbose + +The following logging level options are available: + +* `.None` +* `.Error` (This is the default. Only error logs are printed to the console.) +* `.Warn` +* `.Info` +* `.Debug` +* `.Verbose` + +**Objective-C** + + [AWSLogger defaultLogger].logLevel = AWSLogLevelVerbose; + +The following logging level options are available: + +* `AWSLogLevelNone` +* `AWSLogLevelError` (This is the default. Only error logs are printed to the console.) +* `AWSLogLevelWarn` +* `AWSLogLevelInfo` +* `AWSLogLevelDebug` +* `AWSLogLevelVerbose` + +##Sample Apps + +The AWS SDK for iOS includes sample apps that demonstrate common use cases. + +###Cognito Sync Sample ([Objective-C](https://github.com/awslabs/aws-sdk-ios-samples/tree/master/CognitoSync-Sample/Objective-C/)) + +This sample demonstrates how to securely manage and sync your mobile app data and create unique identities via login providers including Facebook, Google, and Login with Amazon. + +**AWS Services Demonstrated**: + +* [Amazon Cognito Sync](http://aws.amazon.com/cognito/) +* [Amazon Cognito Identity](http://aws.amazon.com/cognito/) + +###DynamoDB Object Mapper Sample ([Objective-C](https://github.com/awslabs/aws-sdk-ios-samples/tree/master/DynamoDBObjectMapper-Sample/Objective-C/)) + +This sample demonstrates how to insert / update / delete / query items using DynamoDB Object Mapper. + +**AWS Services Demonstrated**: + +* [Amazon DynamoDB](http://aws.amazon.com/dynamodb/) +* [Amazon Cognito Identity](http://aws.amazon.com/cognito/) + +###S3 Transfer Manager Sample ([Objective-C](https://github.com/awslabs/aws-sdk-ios-samples/tree/master/S3TransferManager-Sample/Objective-C/)) + +This sample demonstrates how to upload / download multiple files simultaneously using S3 Transfer Manager. It also shows how to pause, resume, and cancel file upload / download. + +**AWS Services Demonstrated**: + +* [Amazon S3](http://aws.amazon.com/s3/) +* [Amazon Cognito Identity](http://aws.amazon.com/cognito/) + +###SNS Mobile Push and Mobile Analytics Sample ([Swift](https://github.com/awslabs/aws-sdk-ios-samples/tree/master/SNS-MobileAnalytics-Sample/Swift/)) + +This sample demonstrates how to set up Amazon SNS Mobile Push and record events using Amazon Mobile Analytics. + +**AWS Services Demonstrated**: + +* [Amazon SNS Mobile Push](http://aws.amazon.com/sns/) +* [Amazon Mobile Analytics](http://aws.amazon.com/mobileanalytics/) +* [Amazon Cognito Identity](http://aws.amazon.com/cognito/) + +##Install the Reference Documentation in Xcode + +The AWS Mobile SDK for iOS zip file includes documentation in the DocSets format that you can view within Xcode. The easiest way to install the documentation is to use the Mac OS X terminal. + +1. Open the Mac OS X terminal and go to the directory containing the expanded archive. For example: + + $ cd ~/Downloads/aws-ios-sdk-2.0.0 + + **Note**: Remember to replace 2.0.0 in the example above with the actual version number of the AWS SDK for iOS that you downloaded. + +1. Create a directory called `~/Library/Developer/Shared/Documentation/DocSets`: + + $ mkdir -p ~/Library/Developer/Shared/Documentation/DocSets + +1. Copy (or move) `Documentation/com.amazon.aws.ios.docset` from the SDK installation files to the directory you created in the previous step: + + $ mv Documentation/com.amazon.aws.ios.docset ~/Library/Developer/Shared/Documentation/DocSets/ + +1. If Xcode was running during this procedure, restart Xcode. To browse the documentation, go to **Help**, click **Documentation and API Reference**, and select **AWS SDK for iOS v2.0 Documentation** (where '2.0' is the appropriate version number). + +##Talk to Us + +Visit our GitHub [Issues](https://github.com/aws/aws-sdk-ios/issues) to leave feedback and to connect with other users of the SDK. -## Author +##Author Amazon Web Services -## License +##License -AWSiOSSDKv2 is available under the Apache License. See the **LICENSE** file for more info. \ No newline at end of file +The AWS Mobile SDK for iOS is available under the Apache License. See the **LICENSE** file for more info. diff --git a/S3/AWSS3Model.h b/S3/AWSS3Model.h index 6eb94bb9a1e..675e546a393 100644 --- a/S3/AWSS3Model.h +++ b/S3/AWSS3Model.h @@ -186,7 +186,6 @@ typedef NS_ENUM(NSInteger, AWSS3Type) { @class AWSS3CreateBucketRequest; @class AWSS3CreateMultipartUploadOutput; @class AWSS3CreateMultipartUploadRequest; -@class AWSS3Delete; @class AWSS3DeleteBucketCorsRequest; @class AWSS3DeleteBucketLifecycleRequest; @class AWSS3DeleteBucketPolicyRequest; @@ -272,6 +271,7 @@ typedef NS_ENUM(NSInteger, AWSS3Type) { @class AWSS3PutObjectRequest; @class AWSS3Redirect; @class AWSS3RedirectAllRequestsTo; +@class AWSS3Remove; @class AWSS3ReplicateObjectOutput; @class AWSS3ReplicateObjectRequest; @class AWSS3ReplicateObjectResult; @@ -633,17 +633,6 @@ typedef NS_ENUM(NSInteger, AWSS3Type) { @end -@interface AWSS3Delete : AWSModel - -@property (nonatomic, strong) NSArray *objects; - -/** - * Element to enable quiet mode for the request. When you add this element, you must set its value to true. - */ -@property (nonatomic, strong) NSNumber *quiet; - -@end - @interface AWSS3DeleteBucketCorsRequest : AWSRequest @property (nonatomic, strong) NSString *bucket; @@ -748,12 +737,12 @@ typedef NS_ENUM(NSInteger, AWSS3Type) { @interface AWSS3DeleteObjectsRequest : AWSRequest @property (nonatomic, strong) NSString *bucket; -@property (nonatomic, strong) AWSS3Delete *delete; /** * The concatenation of the authentication device's serial number, a space, and the value that is displayed on your authentication device. */ @property (nonatomic, strong) NSString *MFA; +@property (nonatomic, strong) AWSS3Remove *remove; @end @@ -2236,6 +2225,17 @@ typedef NS_ENUM(NSInteger, AWSS3Type) { @end +@interface AWSS3Remove : AWSModel + +@property (nonatomic, strong) NSArray *objects; + +/** + * Element to enable quiet mode for the request. When you add this element, you must set its value to true. + */ +@property (nonatomic, strong) NSNumber *quiet; + +@end + @interface AWSS3ReplicateObjectOutput : AWSModel diff --git a/S3/AWSS3Model.m b/S3/AWSS3Model.m index de1b999054f..e27713f88bd 100644 --- a/S3/AWSS3Model.m +++ b/S3/AWSS3Model.m @@ -494,21 +494,6 @@ + (NSValueTransformer *)storageClassJSONTransformer { @end -@implementation AWSS3Delete - -+ (NSDictionary *)JSONKeyPathsByPropertyKey { - return @{ - @"objects" : @"Objects", - @"quiet" : @"Quiet", - }; -} - -+ (NSValueTransformer *)objectsJSONTransformer { - return [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:[AWSS3ObjectIdentifier class]]; -} - -@end - @implementation AWSS3DeleteBucketCorsRequest + (NSDictionary *)JSONKeyPathsByPropertyKey { @@ -643,13 +628,13 @@ @implementation AWSS3DeleteObjectsRequest + (NSDictionary *)JSONKeyPathsByPropertyKey { return @{ @"bucket" : @"Bucket", - @"delete" : @"Delete", @"MFA" : @"MFA", + @"remove" : @"Delete", }; } -+ (NSValueTransformer *)deleteJSONTransformer { - return [NSValueTransformer mtl_JSONDictionaryTransformerWithModelClass:[AWSS3Delete class]]; ++ (NSValueTransformer *)removeJSONTransformer { + return [NSValueTransformer mtl_JSONDictionaryTransformerWithModelClass:[AWSS3Remove class]]; } @end @@ -2595,6 +2580,21 @@ + (NSValueTransformer *)protocolJSONTransformer { @end +@implementation AWSS3Remove + ++ (NSDictionary *)JSONKeyPathsByPropertyKey { + return @{ + @"objects" : @"Objects", + @"quiet" : @"Quiet", + }; +} + ++ (NSValueTransformer *)objectsJSONTransformer { + return [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:[AWSS3ObjectIdentifier class]]; +} + +@end + @implementation AWSS3ReplicateObjectOutput + (NSDictionary *)JSONKeyPathsByPropertyKey { diff --git a/S3/AWSS3PreSignedURL.h b/S3/AWSS3PreSignedURL.h index 1f5117c42cf..f74ef4aa7aa 100644 --- a/S3/AWSS3PreSignedURL.h +++ b/S3/AWSS3PreSignedURL.h @@ -91,7 +91,7 @@ typedef NS_ENUM(NSInteger, AWSS3PresignedURLErrorType) { @property (nonatomic, strong) NSString *versionId; /** - * (Optional) A standard MIME type describing the format of the object data. only apply when AWSHTTPMethod property is AWSHTTPMethodPUT. Default value is "binary/octet-stream". + * (Optional) A standard MIME type describing the format of the object data. only apply when AWSHTTPMethod property is AWSHTTPMethodPUT. */ @property (nonatomic, strong) NSString *contentType; diff --git a/S3/AWSS3PreSignedURL.m b/S3/AWSS3PreSignedURL.m index ded475683de..ce330adf29e 100644 --- a/S3/AWSS3PreSignedURL.m +++ b/S3/AWSS3PreSignedURL.m @@ -17,6 +17,7 @@ #import "AWSCategory.h" #import "AWSSignature.h" #import "AWSLogging.h" +#import "Bolts.h" NSString *const AWSS3PresignedURLErrorDomain = @"com.amazonaws.AWSS3PresignedURLErrorDomain"; @@ -94,6 +95,16 @@ - (BFTask *)getPreSignedURL:(AWSS3GetPreSignedURLRequest *)getPreSignedURLReques userInfo:@{NSLocalizedDescriptionKey: @"credentialProvider in configuration can not be nil"}] ]; } + + //validate expiration date if using temporary token and refresh it if condition met + if ([credentialProvider respondsToSelector:@selector(expiration)]) { + if ([credentialProvider respondsToSelector:@selector(refresh)]) { + if ([credentialProvider.expiration timeIntervalSinceNow] < getPreSignedURLRequest.minimumCredentialsExpirationInterval) { + //need to refresh temp credential + [[credentialProvider refresh] waitUntilFinished]; + } + } + } //validate accessKey if ([credentialProvider respondsToSelector:@selector(accessKey)] && [credentialProvider.accessKey length] > 0) { @@ -160,15 +171,6 @@ - (BFTask *)getPreSignedURL:(AWSS3GetPreSignedURLRequest *)getPreSignedURLReques break; } - //validate expiration date if using temporary token - if ([credentialProvider respondsToSelector:@selector(expiration)]) { - if ([credentialProvider respondsToSelector:@selector(refresh)]) { - if ([credentialProvider.expiration timeIntervalSinceNow] < getPreSignedURLRequest.minimumCredentialsExpirationInterval) { - //need to refresh temp credential - [[credentialProvider refresh] waitUntilFinished]; - } - } - } //generate baseURL String (use virtualHostStyle if possible) NSString *keyPath = nil; @@ -242,7 +244,7 @@ - (BFTask *)getPreSignedURL:(AWSS3GetPreSignedURLRequest *)getPreSignedURLReques } } else { - canonicalizedResource = [NSString stringWithFormat:@"/%@/%@", bucketName, [keyName aws_stringWithURLEncoding]]; + canonicalizedResource = [NSString stringWithFormat:@"/%@/%@", bucketName, [keyName aws_stringWithURLEncodingPath]]; } NSString *stringToSign = [NSString stringWithFormat:@"%@\n%@\n%@\n%d\n%@%@", diff --git a/S3/AWSS3TransferManager.h b/S3/AWSS3TransferManager.h index 172730987c7..79f5ecf589f 100644 --- a/S3/AWSS3TransferManager.h +++ b/S3/AWSS3TransferManager.h @@ -28,6 +28,7 @@ typedef NS_ENUM(NSInteger, AWSS3TransferManagerErrorType) { }; typedef NS_ENUM(NSInteger, AWSS3TransferManagerRequestState) { + AWSS3TransferManagerRequestStateNotStarted, AWSS3TransferManagerRequestStateRunning, AWSS3TransferManagerRequestStatePaused, AWSS3TransferManagerRequestStateCanceling, @@ -109,18 +110,6 @@ typedef void (^AWSS3TransferManagerResumeAllBlock) (AWSRequest *request); */ - (BFTask *)clearCache; - -/** - * The limit of the disk cache size in bytes. When exceeded, older requests will be discarded. Setting this value to 0.0 meaning no practical limit. The default value is 5MB. - */ -@property (nonatomic, assign) NSUInteger diskByteLimit; - -/** - * The age limit of the cached requests. When exceeded, requests older than the specified age will be discarded. Setting this value to 0 meaning no practical limit. The default is no age limit(i.e. 0.0). - */ -@property (nonatomic, assign) NSTimeInterval diskAgeLimit; - - @end @interface AWSS3TransferManagerUploadRequest : AWSS3PutObjectRequest diff --git a/S3/AWSS3TransferManager.m b/S3/AWSS3TransferManager.m index b7379064b37..a62ac9861bb 100644 --- a/S3/AWSS3TransferManager.m +++ b/S3/AWSS3TransferManager.m @@ -47,8 +47,6 @@ @interface AWSS3TransferManagerUploadRequest () @interface AWSS3TransferManagerDownloadRequest () -@property (nonatomic, strong) NSURL *temporaryFileURL; -@property (nonatomic, strong) NSURL *originalFileURL; @property (nonatomic, assign) AWSS3TransferManagerRequestState state; @property (nonatomic, strong) NSString *cacheIdentifier; @@ -173,6 +171,8 @@ - (BFTask *)upload:(AWSS3TransferManagerUploadRequest *)uploadRequest return [BFTask taskWithError:task.error]; } } else { + [uploadRequest setValue:[NSNumber numberWithInteger:AWSS3TransferManagerRequestStateCompleted] + forKey:@"state"]; return [BFTask taskWithResult:task.result]; } }]; @@ -191,7 +191,7 @@ - (BFTask *)putObject:(AWSS3TransferManagerUploadRequest *)uploadRequest //delete cached Object if state is not Paused if (uploadRequest.state != AWSS3TransferManagerRequestStatePaused) { - [self removeObjectForKey:cacheKey removeTempFile:YES]; + [self.cache removeObjectForKey:cacheKey]; } if (task.error) { return [BFTask taskWithError:task.error]; @@ -367,7 +367,7 @@ - (BFTask *)multipartUpload:(AWSS3TransferManagerUploadRequest *)uploadRequest //delete cached Object if state is not Paused if (uploadRequest.state != AWSS3TransferManagerRequestStatePaused) { - [self removeObjectForKey:cacheKey removeTempFile:YES]; + [self.cache removeObjectForKey:cacheKey]; } if (uploadRequest.state == AWSS3TransferManagerRequestStateCanceling) { @@ -423,13 +423,10 @@ - (BFTask *)download:(AWSS3TransferManagerDownloadRequest *)downloadRequest } else if (downloadRequest.state == AWSS3TransferManagerRequestStateCanceling){ NSDictionary *userInfo = @{NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedString(@"can not continue to download a cancelled task.", nil)]}; return [BFTask taskWithError:[NSError errorWithDomain:AWSS3TransferManagerErrorDomain code:AWSS3TransferManagerErrorCancelled userInfo:userInfo]]; - } else { - //change state to running - [downloadRequest setValue:[NSNumber numberWithInteger:AWSS3TransferManagerRequestStateRunning] forKey:@"state"]; } - //Generate a new tempFileURL if it is a new request.∫ - if ([downloadRequest valueForKey:@"temporaryFileURL"] == nil) { + //if it is a new request. + if (downloadRequest.state != AWSS3TransferManagerRequestStatePaused) { //If downloadFileURL is nil, create a URL in temporary folder for user. if (downloadRequest.downloadingFileURL == nil) { @@ -458,22 +455,14 @@ - (BFTask *)download:(AWSS3TransferManagerDownloadRequest *)downloadRequest } downloadRequest.downloadingFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:generatedfileName]]; + } else { + //if file already existed, remove it to avoid received data has been appended to exist file. + [[NSFileManager defaultManager] removeItemAtURL:downloadRequest.downloadingFileURL error:nil]; } - //create a tempFileURL - NSString *tempFileName = [[downloadRequest.downloadingFileURL lastPathComponent] stringByAppendingString:cacheKey]; - NSURL *tempFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:tempFileName]]; - - //save current downloadFileURL - [downloadRequest setValue:downloadRequest.downloadingFileURL forKey:@"originalFileURL"]; - - //save the tempFileURL - [downloadRequest setValue:tempFileURL forKey:@"temporaryFileURL"]; - //set the downloadURL to use this tempURL instead. - downloadRequest.downloadingFileURL = tempFileURL; } else { //if the is a paused task, set the range - NSURL *tempFileURL = [downloadRequest valueForKey:@"temporaryFileURL"]; + NSURL *tempFileURL = downloadRequest.downloadingFileURL; if (tempFileURL) { if ([[NSFileManager defaultManager] fileExistsAtPath:tempFileURL.path] == NO) { AWSLogError(@"tempfile is not exist, unable to resume"); @@ -491,6 +480,9 @@ - (BFTask *)download:(AWSS3TransferManagerDownloadRequest *)downloadRequest } } + //change state to running + [downloadRequest setValue:[NSNumber numberWithInteger:AWSS3TransferManagerRequestStateRunning] forKey:@"state"]; + //set shouldWriteDirectly to YES [downloadRequest setValue:@YES forKey:@"shouldWriteDirectly"]; @@ -512,46 +504,21 @@ - (BFTask *)getObject:(AWSS3TransferManagerDownloadRequest *)downloadRequest [getObjectRequest aws_copyPropertiesFromObject:downloadRequest]; BFTask *downloadTask = [[[self.s3 getObject:getObjectRequest] continueWithBlock:^id(BFTask *task) { - NSURL *tempFileURL = [downloadRequest valueForKey:@"temporaryFileURL"]; - NSURL *originalFileURL = [downloadRequest valueForKey:@"originalFileURL"]; + + //delete cached Object if state is not Paused + if (downloadRequest.state != AWSS3TransferManagerRequestStatePaused) { + [self.cache removeObjectForKey:cacheKey]; + } if (task.error) { - //download got error, check if tempFile has been created. - if ([[NSFileManager defaultManager] fileExistsAtPath:tempFileURL.path]) { - AWSLogDebug(@"tempFile has not been created yet."); - } - - if (downloadRequest.state != AWSS3TransferManagerRequestStatePaused) { - [self removeObjectForKey:cacheKey removeTempFile:YES]; - } return [BFTask taskWithError:task.error]; } - //If task complete without error, move the completed file to originalFileURL - if (tempFileURL && originalFileURL) { - NSError *error = nil; - [[NSFileManager defaultManager] moveItemAtURL:tempFileURL - toURL:originalFileURL - error:&error]; - if (error) { - //got error when try to move completed file. - return [BFTask taskWithError:error]; - } - } - - //delete cached Object if state is not Paused - if (downloadRequest.state != AWSS3TransferManagerRequestStatePaused) { - [self removeObjectForKey:cacheKey removeTempFile:YES]; - } - AWSS3TransferManagerDownloadOutput *downloadOutput = [AWSS3TransferManagerDownloadOutput new]; if (task.result) { AWSS3GetObjectOutput *getObjectOutput = task.result; - //set the body to originalFileURL - getObjectOutput.body = [downloadRequest valueForKey:@"originalFileURL"]; - [downloadOutput aws_copyPropertiesFromObject:getObjectOutput]; } [downloadRequest setValue:[NSNumber numberWithInteger:AWSS3TransferManagerRequestStateCompleted] @@ -649,7 +616,7 @@ - (BFTask *)resumeAll:(AWSS3TransferManagerResumeAllBlock)block { } //remove Resumed Object - [self removeObjectForKey:key removeTempFile:NO]; + [self.cache removeObjectForKey:key]; } return [[BFTask taskForCompletionOfAllTasks:tasks] continueWithBlock:^id(BFTask *task) { @@ -663,58 +630,13 @@ - (BFTask *)resumeAll:(AWSS3TransferManagerResumeAllBlock)block { - (BFTask *)clearCache { BFTaskCompletionSource *taskCompletionSource = [BFTaskCompletionSource new]; - - __block NSMutableArray* allKeys = [NSMutableArray new]; - TMDiskCacheObjectBlock iterateObjectBlock = ^(TMDiskCache *cache, NSString *key, id object, NSURL *fileURL){ - [allKeys addObject:key]; - }; - [self.cache.diskCache enumerateObjectsWithBlock:iterateObjectBlock]; - - for(id key in allKeys){ - [self removeObjectForKey:key removeTempFile:YES]; - } + [self.cache removeAllObjects:^(TMCache *cache) { + taskCompletionSource.result = nil; + }]; return taskCompletionSource.task; } -- (void)setDiskByteLimit:(NSUInteger)limit { - [self.cache.diskCache setByteLimit:limit]; -} - -- (void)setDiskAgeLimit:(NSTimeInterval)limit { - [self.cache.diskCache setAgeLimit:limit]; -} - --(void) removeObjectForKey:(NSString *)key removeTempFile:(BOOL)flag { - TMDiskCacheObjectBlock willDeleteObjectBlock = ^(TMDiskCache *cache, NSString *key, id object, NSURL *fileURL){ - id theObject = nil; - if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) { - theObject = [NSKeyedUnarchiver unarchiveObjectWithFile:[fileURL path]]; - } - id deleteObject = theObject; - if(deleteObject!=nil && [deleteObject isKindOfClass:[ AWSS3TransferManagerDownloadRequest class]]){ - NSURL *tempFileURL = [deleteObject valueForKey:@"temporaryFileURL"]; - if(tempFileURL){ - NSError *error = nil; - if(tempFileURL != nil && [[NSFileManager defaultManager] fileExistsAtPath:[tempFileURL path]]){ - [[NSFileManager defaultManager] removeItemAtPath: [tempFileURL path] error: &error]; - } - } - } - }; - - if(flag){ - [self.cache.diskCache setWillRemoveObjectBlock: willDeleteObjectBlock]; - } - else{ - [self.cache.diskCache setWillRemoveObjectBlock: nil]; - } - - [self.cache removeObjectForKey:key]; -} - - - - (void)abortMultipartUploadsForRequest:(AWSS3TransferManagerUploadRequest *)uploadRequest{ AWSS3AbortMultipartUploadRequest *abortMultipartUploadRequest = [AWSS3AbortMultipartUploadRequest new]; abortMultipartUploadRequest.bucket = uploadRequest.bucket; @@ -735,6 +657,14 @@ - (void)abortMultipartUploadsForRequest:(AWSS3TransferManagerUploadRequest *)upl @implementation AWSS3TransferManagerUploadRequest +- (instancetype)init { + if (self = [super init]) { + _state = AWSS3TransferManagerRequestStateNotStarted; + } + + return self; +} + - (BFTask *)cancel { if (self.state != AWSS3TransferManagerRequestStateCompleted) { self.state = AWSS3TransferManagerRequestStateCanceling; @@ -795,6 +725,14 @@ @implementation AWSS3TransferManagerUploadOutput @implementation AWSS3TransferManagerDownloadRequest +- (instancetype)init { + if (self = [super init]) { + _state = AWSS3TransferManagerRequestStateNotStarted; + } + + return self; +} + - (BFTask *)cancel { if (self.state != AWSS3TransferManagerRequestStateCompleted) { self.state = AWSS3TransferManagerRequestStateCanceling; diff --git a/SES/AWSSES.m b/SES/AWSSES.m index fa3e7f5a07e..22e3febd631 100644 --- a/SES/AWSSES.m +++ b/SES/AWSSES.m @@ -110,25 +110,35 @@ @interface AWSSESRequestRetryHandler : AWSURLRequestRetryHandler @implementation AWSSESRequestRetryHandler - (AWSNetworkingRetryType)shouldRetry:(uint32_t)currentRetryCount - response:(NSHTTPURLResponse *)response - data:(NSData *)data - error:(NSError *)error { + response:(NSHTTPURLResponse *)response + data:(NSData *)data + error:(NSError *)error { AWSNetworkingRetryType retryType = [super shouldRetry:currentRetryCount - response:response - data:data - error:error]; + response:response + data:data + error:error]; if(retryType == AWSNetworkingRetryTypeShouldNotRetry - && [error.domain isEqualToString:AWSSESErrorDomain] && currentRetryCount < self.maxRetryCount) { - switch (error.code) { - case AWSSESErrorIncompleteSignature: - case AWSSESErrorInvalidClientTokenId: - case AWSSESErrorMissingAuthenticationToken: - retryType = AWSNetworkingRetryTypeShouldRefreshCredentialsAndRetry; - break; - - default: - break; + if ([error.domain isEqualToString:AWSSESErrorDomain]) { + switch (error.code) { + case AWSSESErrorIncompleteSignature: + case AWSSESErrorInvalidClientTokenId: + case AWSSESErrorMissingAuthenticationToken: + retryType = AWSNetworkingRetryTypeShouldRefreshCredentialsAndRetry; + break; + + default: + break; + } + } else if ([error.domain isEqualToString:AWSGeneralErrorDomain]) { + switch (error.code) { + case AWSGeneralErrorSignatureDoesNotMatch: + retryType = AWSNetworkingRetryTypeShouldCorrectClockSkewAndRetry; + break; + + default: + break; + } } } diff --git a/SNS/AWSSNS.m b/SNS/AWSSNS.m index b6dec9a06f8..f537f3cadf9 100644 --- a/SNS/AWSSNS.m +++ b/SNS/AWSSNS.m @@ -120,25 +120,35 @@ @interface AWSSNSRequestRetryHandler : AWSURLRequestRetryHandler @implementation AWSSNSRequestRetryHandler - (AWSNetworkingRetryType)shouldRetry:(uint32_t)currentRetryCount - response:(NSHTTPURLResponse *)response - data:(NSData *)data - error:(NSError *)error { + response:(NSHTTPURLResponse *)response + data:(NSData *)data + error:(NSError *)error { AWSNetworkingRetryType retryType = [super shouldRetry:currentRetryCount - response:response - data:data - error:error]; + response:response + data:data + error:error]; if(retryType == AWSNetworkingRetryTypeShouldNotRetry - && [error.domain isEqualToString:AWSSNSErrorDomain] && currentRetryCount < self.maxRetryCount) { - switch (error.code) { - case AWSSNSErrorIncompleteSignature: - case AWSSNSErrorInvalidClientTokenId: - case AWSSNSErrorMissingAuthenticationToken: - retryType = AWSNetworkingRetryTypeShouldRefreshCredentialsAndRetry; - break; - - default: - break; + if ([error.domain isEqualToString:AWSSNSErrorDomain]) { + switch (error.code) { + case AWSSNSErrorIncompleteSignature: + case AWSSNSErrorInvalidClientTokenId: + case AWSSNSErrorMissingAuthenticationToken: + retryType = AWSNetworkingRetryTypeShouldRefreshCredentialsAndRetry; + break; + + default: + break; + } + } else if ([error.domain isEqualToString:AWSGeneralErrorDomain]) { + switch (error.code) { + case AWSGeneralErrorSignatureDoesNotMatch: + retryType = AWSNetworkingRetryTypeShouldCorrectClockSkewAndRetry; + break; + + default: + break; + } } } diff --git a/SQS/AWSSQS.m b/SQS/AWSSQS.m index eb7a62703e7..3fdd898ef9f 100644 --- a/SQS/AWSSQS.m +++ b/SQS/AWSSQS.m @@ -125,25 +125,35 @@ @interface AWSSQSRequestRetryHandler : AWSURLRequestRetryHandler @implementation AWSSQSRequestRetryHandler - (AWSNetworkingRetryType)shouldRetry:(uint32_t)currentRetryCount - response:(NSHTTPURLResponse *)response - data:(NSData *)data - error:(NSError *)error { + response:(NSHTTPURLResponse *)response + data:(NSData *)data + error:(NSError *)error { AWSNetworkingRetryType retryType = [super shouldRetry:currentRetryCount - response:response - data:data - error:error]; + response:response + data:data + error:error]; if(retryType == AWSNetworkingRetryTypeShouldNotRetry - && [error.domain isEqualToString:AWSSQSErrorDomain] && currentRetryCount < self.maxRetryCount) { - switch (error.code) { - case AWSSQSErrorIncompleteSignature: - case AWSSQSErrorInvalidClientTokenId: - case AWSSQSErrorMissingAuthenticationToken: - retryType = AWSNetworkingRetryTypeShouldRefreshCredentialsAndRetry; - break; - - default: - break; + if ([error.domain isEqualToString:AWSSQSErrorDomain]) { + switch (error.code) { + case AWSSQSErrorIncompleteSignature: + case AWSSQSErrorInvalidClientTokenId: + case AWSSQSErrorMissingAuthenticationToken: + retryType = AWSNetworkingRetryTypeShouldRefreshCredentialsAndRetry; + break; + + default: + break; + } + } else if ([error.domain isEqualToString:AWSGeneralErrorDomain]) { + switch (error.code) { + case AWSGeneralErrorSignatureDoesNotMatch: + retryType = AWSNetworkingRetryTypeShouldCorrectClockSkewAndRetry; + break; + + default: + break; + } } } diff --git a/Scripts/GenerateAppleDocs.sh b/Scripts/GenerateAppleDocs.sh index 3a263e518f0..5b9acdfc7eb 100755 --- a/Scripts/GenerateAppleDocs.sh +++ b/Scripts/GenerateAppleDocs.sh @@ -9,7 +9,7 @@ function cleanup } -VERSION="2.0.8" +VERSION="2.0.9" if [ -n $1 ] && [ "$1" == "clean" ]; then cleanup diff --git a/Scripts/SdkHeader.sh b/Scripts/SdkHeader.sh index c24e8c74624..31d278c5a42 100755 --- a/Scripts/SdkHeader.sh +++ b/Scripts/SdkHeader.sh @@ -2,5 +2,6 @@ echo "correct the way to import framework headers" find . -name '*.h' | xargs perl -pi -e 's/"Mantle.h"//g' find . -name '*.h' | xargs perl -pi -e 's/"Bolts.h"//g' find . -name '*.h' | xargs perl -pi -e 's/"TMCache.h"//g' +find . -name '*.h' | xargs perl -pi -e 's/"BFTask.h"//g' find . -name '*.h' | xargs perl -pi -e 's/#import "/#import /g' \ No newline at end of file diff --git a/Scripts/objc-fix.patch b/Scripts/objc-fix.patch index 668d8e62879..3662c797192 100644 --- a/Scripts/objc-fix.patch +++ b/Scripts/objc-fix.patch @@ -1,11 +1,5 @@ -commit 8041eb00578676832b15bc3cf2a130db93fc05ab -Author: Yosuke Matsuda -Date: Fri Sep 12 16:04:56 2014 -0700 - - New - diff --git a/AWSCore/Utility/AWSCategory.m b/AWSCore/Utility/AWSCategory.m -index 1b24179..dd0a53a 100644 +index b90afee..7ff2a12 100644 --- a/AWSCore/Utility/AWSCategory.m +++ b/AWSCore/Utility/AWSCategory.m @@ -16,6 +16,8 @@ @@ -39,9 +33,9 @@ index 1b24179..dd0a53a 100644 + +@end + - @implementation NSData (AWS) + @implementation NSDate (AWS) - static char base64EncodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + static NSTimeInterval _clockskew = 0.0; diff --git a/Pods/GZIP/GZIP/GZIP.h b/Pods/GZIP/GZIP/GZIP.h index e1f5d78..426e6c8 100644 --- a/Pods/GZIP/GZIP/GZIP.h diff --git a/readme-images/cocoapods-setup-01.png b/readme-images/cocoapods-setup-01.png new file mode 100644 index 00000000000..6e6a78e89aa Binary files /dev/null and b/readme-images/cocoapods-setup-01.png differ diff --git a/readme-images/cocoapods-setup-02.png b/readme-images/cocoapods-setup-02.png new file mode 100644 index 00000000000..bc6e7f48ccd Binary files /dev/null and b/readme-images/cocoapods-setup-02.png differ diff --git a/readme-images/cocoapods-setup-03.png b/readme-images/cocoapods-setup-03.png new file mode 100644 index 00000000000..035fe2b7309 Binary files /dev/null and b/readme-images/cocoapods-setup-03.png differ diff --git a/readme-images/objc-bridging-header-01.png b/readme-images/objc-bridging-header-01.png new file mode 100644 index 00000000000..dc742168aa5 Binary files /dev/null and b/readme-images/objc-bridging-header-01.png differ diff --git a/readme-images/objc-bridging-header-02.png b/readme-images/objc-bridging-header-02.png new file mode 100644 index 00000000000..a914cb2ad22 Binary files /dev/null and b/readme-images/objc-bridging-header-02.png differ