diff --git a/AWSAPIGateway.podspec b/AWSAPIGateway.podspec index 755262c1f75..67707c128a4 100644 --- a/AWSAPIGateway.podspec +++ b/AWSAPIGateway.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'AWSAPIGateway' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -13,7 +13,7 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSAPIGateway/*.{h,m}' end diff --git a/AWSAPIGateway/AWSAPIGatewayClient.m b/AWSAPIGateway/AWSAPIGatewayClient.m index 6923d09e383..b50824c8c87 100644 --- a/AWSAPIGateway/AWSAPIGatewayClient.m +++ b/AWSAPIGateway/AWSAPIGatewayClient.m @@ -23,7 +23,7 @@ static NSString *const AWSAPIGatewayAPIKeyHeader = @"x-api-key"; -static NSString *const AWSAPIGatewaySDKVersion = @"2.6.12"; +static NSString *const AWSAPIGatewaySDKVersion = @"2.6.13"; static int defaultChunkSize = 1024; diff --git a/AWSAPIGateway/Info.plist b/AWSAPIGateway/Info.plist index faaa68791a9..4f1b86ecbe1 100644 --- a/AWSAPIGateway/Info.plist +++ b/AWSAPIGateway/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSAuth.podspec b/AWSAuth.podspec index 14e1c2f7942..f1d1f3c2844 100644 --- a/AWSAuth.podspec +++ b/AWSAuth.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSAuth' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -14,23 +14,23 @@ Pod::Spec.new do |s| s.requires_arc = true s.subspec 'Core' do |authcore| - authcore.dependency 'AWSAuthCore', '2.6.12' + authcore.dependency 'AWSAuthCore', '2.6.13' end s.subspec 'FacebookSignIn' do |facebook| - facebook.dependency 'AWSFacebookSignIn', '2.6.12' + facebook.dependency 'AWSFacebookSignIn', '2.6.13' end s.subspec 'GoogleSignIn' do |google| - google.dependency 'AWSGoogleSignIn', '2.6.12' + google.dependency 'AWSGoogleSignIn', '2.6.13' end s.subspec 'UserPoolsSignIn' do |up| - up.dependency 'AWSUserPoolsSignIn', '2.6.12' + up.dependency 'AWSUserPoolsSignIn', '2.6.13' end s.subspec 'UI' do |ui| - ui.dependency 'AWSAuthUI', '2.6.12' + ui.dependency 'AWSAuthUI', '2.6.13' end end diff --git a/AWSAuthCore.podspec b/AWSAuthCore.podspec index a108c658978..f35a73f1dcc 100644 --- a/AWSAuthCore.podspec +++ b/AWSAuthCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSAuthCore' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,7 +12,7 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSAuthSDK/Sources/AWSAuthCore/*.{h,m}' s.public_header_files = 'AWSAuthSDK/Sources/AWSAuthCore/*.h' end diff --git a/AWSAuthSDK/Sources/AWSAuthCore/Info.plist b/AWSAuthSDK/Sources/AWSAuthCore/Info.plist index 9f4043017b3..471d2df6be9 100644 --- a/AWSAuthSDK/Sources/AWSAuthCore/Info.plist +++ b/AWSAuthSDK/Sources/AWSAuthCore/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/AWSAuthSDK/Sources/AWSAuthUI/Info.plist b/AWSAuthSDK/Sources/AWSAuthUI/Info.plist index 9f4043017b3..471d2df6be9 100644 --- a/AWSAuthSDK/Sources/AWSAuthUI/Info.plist +++ b/AWSAuthSDK/Sources/AWSAuthUI/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/AWSAuthSDK/Sources/AWSFacebookSignIn/Info.plist b/AWSAuthSDK/Sources/AWSFacebookSignIn/Info.plist index 9f4043017b3..471d2df6be9 100644 --- a/AWSAuthSDK/Sources/AWSFacebookSignIn/Info.plist +++ b/AWSAuthSDK/Sources/AWSFacebookSignIn/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/AWSAuthSDK/Sources/AWSGoogleSignIn/Info.plist b/AWSAuthSDK/Sources/AWSGoogleSignIn/Info.plist index 9f4043017b3..471d2df6be9 100644 --- a/AWSAuthSDK/Sources/AWSGoogleSignIn/Info.plist +++ b/AWSAuthSDK/Sources/AWSGoogleSignIn/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/AWSAuthSDK/Sources/AWSMobileClient/Info.plist b/AWSAuthSDK/Sources/AWSMobileClient/Info.plist index 4fd88feaab1..93aa2dfabae 100644 --- a/AWSAuthSDK/Sources/AWSMobileClient/Info.plist +++ b/AWSAuthSDK/Sources/AWSMobileClient/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/AWSAuthSDK/Sources/AWSUserPoolsSignIn/Info.plist b/AWSAuthSDK/Sources/AWSUserPoolsSignIn/Info.plist index 9f4043017b3..471d2df6be9 100644 --- a/AWSAuthSDK/Sources/AWSUserPoolsSignIn/Info.plist +++ b/AWSAuthSDK/Sources/AWSUserPoolsSignIn/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/AWSAuthUI.podspec b/AWSAuthUI.podspec index cdd553a9070..f01a8f9d497 100644 --- a/AWSAuthUI.podspec +++ b/AWSAuthUI.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSAuthUI' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,8 +12,8 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' - s.dependency 'AWSAuthCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' + s.dependency 'AWSAuthCore', '2.6.13' s.source_files = 'AWSAuthSDK/Sources/AWSAuthUI/*.{h,m}', 'AWSAuthSDK/Sources/AWSAuthUI/**/*.{h,m}', 'AWSAuthSDK/Sources/AWSUserPoolsSignIn/UserPoolsUI/AWSFormTableCell.h', 'AWSAuthSDK/Sources/AWSUserPoolsSignIn/UserPoolsUI/AWSTableInputCell.h', 'AWSAuthSDK/Sources/AWSUserPoolsSignIn/UserPoolsUI/AWSFormTableDelegate.h', 'AWSAuthSDK/Sources/AWSUserPoolsSignIn/UserPoolsUI/AWSUserPoolsUIHelper.h' s.public_header_files = 'AWSAuthSDK/Sources/AWSAuthUI/AWSAuthUI.h', 'AWSAuthSDK/Sources/AWSAuthUI/AWSAuthUIViewController.h', 'AWSAuthSDK/Sources/AWSAuthUI/AWSAuthUIConfiguration.h' s.private_header_files = 'AWSAuthSDK/Sources/AWSUserPoolsSignIn/UserPoolsUI/AWSFormTableCell.h', 'AWSAuthSDK/Sources/AWSAuthUI/AWSSignInViewController.h', 'AWSAuthSDK/Sources/AWSUserPoolsSignIn/UserPoolsUI/AWSTableInputCell.h', 'AWSAuthSDK/Sources/AWSUserPoolsSignIn/UserPoolsUI/AWSFormTableDelegate.h', 'AWSAuthSDK/Sources/AWSUserPoolsSignIn/UserPoolsUI/AWSUserPoolsUIHelper.h' diff --git a/AWSAutoScaling.podspec b/AWSAutoScaling.podspec index c64cdb90062..df45a02d702 100644 --- a/AWSAutoScaling.podspec +++ b/AWSAutoScaling.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSAutoScaling' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,6 +12,6 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSAutoScaling/*.{h,m}' end diff --git a/AWSAutoScaling/AWSAutoScalingService.m b/AWSAutoScaling/AWSAutoScalingService.m index da7087a2c44..a198b000565 100644 --- a/AWSAutoScaling/AWSAutoScalingService.m +++ b/AWSAutoScaling/AWSAutoScalingService.m @@ -26,7 +26,7 @@ #import "AWSAutoScalingResources.h" static NSString *const AWSInfoAutoScaling = @"AutoScaling"; -static NSString *const AWSAutoScalingSDKVersion = @"2.6.12"; +static NSString *const AWSAutoScalingSDKVersion = @"2.6.13"; @interface AWSAutoScalingResponseSerializer : AWSXMLResponseSerializer diff --git a/AWSAutoScaling/Info.plist b/AWSAutoScaling/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSAutoScaling/Info.plist +++ b/AWSAutoScaling/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSCloudWatch.podspec b/AWSCloudWatch.podspec index 0fa5689947e..e7dc39023b8 100644 --- a/AWSCloudWatch.podspec +++ b/AWSCloudWatch.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSCloudWatch' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,6 +12,6 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSCloudWatch/*.{h,m}' end diff --git a/AWSCloudWatch/AWSCloudWatchService.m b/AWSCloudWatch/AWSCloudWatchService.m index 679960e7ab0..00ca5189afb 100644 --- a/AWSCloudWatch/AWSCloudWatchService.m +++ b/AWSCloudWatch/AWSCloudWatchService.m @@ -26,7 +26,7 @@ #import "AWSCloudWatchResources.h" static NSString *const AWSInfoCloudWatch = @"CloudWatch"; -static NSString *const AWSCloudWatchSDKVersion = @"2.6.12"; +static NSString *const AWSCloudWatchSDKVersion = @"2.6.13"; @interface AWSCloudWatchResponseSerializer : AWSXMLResponseSerializer diff --git a/AWSCloudWatch/Info.plist b/AWSCloudWatch/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSCloudWatch/Info.plist +++ b/AWSCloudWatch/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSCognito.podspec b/AWSCognito.podspec index d7546e0f503..15721341738 100644 --- a/AWSCognito.podspec +++ b/AWSCognito.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSCognito' - s.version = '2.6.12' + s.version = '2.6.13' s.summary = 'Amazon Cognito SDK for iOS' s.description = 'Amazon Cognito offers multi device data synchronization with offline access' @@ -13,7 +13,7 @@ Pod::Spec.new do |s| :tag => s.version} s.requires_arc = true s.library = 'sqlite3' - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSCognito/*.{h,m}', 'AWSCognito/**/*.{h,m}' s.public_header_files = 'AWSCognito/*.h', 'AWSCognito/CognitoSync/*.h' s.private_header_files = 'AWSCognito/Fabric/*.h', 'AWSCognito/Internal/*.h' diff --git a/AWSCognito/AWSCognitoService.m b/AWSCognito/AWSCognitoService.m index 76f06fadbd8..7d013243e60 100644 --- a/AWSCognito/AWSCognitoService.m +++ b/AWSCognito/AWSCognitoService.m @@ -34,7 +34,7 @@ #import "Fabric+FABKits.h" static NSString *const AWSInfoCognito = @"Cognito"; -static NSString *const AWSCognitoSDKVersion = @"2.6.12"; +static NSString *const AWSCognitoSDKVersion = @"2.6.13"; NSString *const AWSCognitoDidStartSynchronizeNotification = @"com.amazon.cognito.AWSCognitoDidStartSynchronizeNotification"; NSString *const AWSCognitoDidEndSynchronizeNotification = @"com.amazon.cognito.AWSCognitoDidEndSynchronizeNotification"; diff --git a/AWSCognito/CognitoSync/AWSCognitoSyncService.m b/AWSCognito/CognitoSync/AWSCognitoSyncService.m index f25db2f4c30..f29233312ce 100644 --- a/AWSCognito/CognitoSync/AWSCognitoSyncService.m +++ b/AWSCognito/CognitoSync/AWSCognitoSyncService.m @@ -26,7 +26,7 @@ #import "AWSCognitoSyncResources.h" static NSString *const AWSInfoCognitoSync = @"CognitoSync"; -static NSString *const AWSCognitoSyncSDKVersion = @"2.6.12"; +static NSString *const AWSCognitoSyncSDKVersion = @"2.6.13"; @interface AWSCognitoSyncResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSCognito/Info.plist b/AWSCognito/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSCognito/Info.plist +++ b/AWSCognito/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSCognitoAuth.podspec b/AWSCognitoAuth.podspec index 929308f0c5b..b82506a166c 100644 --- a/AWSCognitoAuth.podspec +++ b/AWSCognitoAuth.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSCognitoAuth' - s.version = '2.6.12' + s.version = '2.6.13' s.summary = 'Amazon Cognito Auth SDK for iOS' s.description = 'Amazon Cognito Auth enables sign up and authentication of your end users via a hosted UI' diff --git a/AWSCognitoAuth/AWSCognitoAuth.m b/AWSCognitoAuth/AWSCognitoAuth.m index 72fb8201a30..d34995fa367 100644 --- a/AWSCognitoAuth/AWSCognitoAuth.m +++ b/AWSCognitoAuth/AWSCognitoAuth.m @@ -42,7 +42,7 @@ @interface AWSCognitoAuth()CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSCognitoIdentityProvider.podspec b/AWSCognitoIdentityProvider.podspec index 2789d244cdf..6359903b972 100644 --- a/AWSCognitoIdentityProvider.podspec +++ b/AWSCognitoIdentityProvider.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSCognitoIdentityProvider' - s.version = '2.6.12' + s.version = '2.6.13' s.summary = 'Amazon Cognito Identity Provider SDK for iOS (Beta)' s.description = 'Amazon Cognito Identity Provider enables sign up and authentication of your end users' @@ -13,7 +13,7 @@ Pod::Spec.new do |s| :tag => s.version} s.requires_arc = true s.dependency 'AWSCognitoIdentityProviderASF', '1.0.1' - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSCognitoIdentityProvider/**/*.{h,m,c}' s.public_header_files = 'AWSCognitoIdentityProvider/*.h', 'AWSCognitoIdentityProvider/CognitoIdentityProvider/*.h' s.private_header_files = 'AWSCognitoIdentityProvider/Internal/*.h' diff --git a/AWSCognitoIdentityProvider/CognitoIdentityProvider/AWSCognitoIdentityProviderService.m b/AWSCognitoIdentityProvider/CognitoIdentityProvider/AWSCognitoIdentityProviderService.m index 5745a416ebe..6ab0ecfacb0 100644 --- a/AWSCognitoIdentityProvider/CognitoIdentityProvider/AWSCognitoIdentityProviderService.m +++ b/AWSCognitoIdentityProvider/CognitoIdentityProvider/AWSCognitoIdentityProviderService.m @@ -26,7 +26,7 @@ #import "AWSCognitoIdentityProviderResources.h" static NSString *const AWSInfoCognitoIdentityProvider = @"CognitoIdentityProvider"; -static NSString *const AWSCognitoIdentityProviderSDKVersion = @"2.6.12"; +static NSString *const AWSCognitoIdentityProviderSDKVersion = @"2.6.13"; @interface AWSCognitoIdentityProviderResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSCognitoIdentityProvider/Info.plist b/AWSCognitoIdentityProvider/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSCognitoIdentityProvider/Info.plist +++ b/AWSCognitoIdentityProvider/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSCognitoSync.podspec b/AWSCognitoSync.podspec index fa09f03cac5..2033ea5b38c 100644 --- a/AWSCognitoSync.podspec +++ b/AWSCognitoSync.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'AWSCognitoSync' - s.version = '2.6.12' + s.version = '2.6.13' s.summary = 'Amazon Cognito SDK for iOS' s.description = 'Amazon Cognito offers multi device data synchronization with offline access' @@ -14,7 +14,7 @@ Pod::Spec.new do |s| :tag => s.version} s.requires_arc = true s.library = 'sqlite3' - s.dependency 'AWSCognito', '2.6.12' + s.dependency 'AWSCognito', '2.6.13' s.deprecated = true s.deprecated_in_favor_of = 'AWSCognito' diff --git a/AWSCore.podspec b/AWSCore.podspec index 08b03c1dc93..247b70cea3c 100644 --- a/AWSCore.podspec +++ b/AWSCore.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'AWSCore' - s.version = '2.6.12' + s.version = '2.6.13' 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.' diff --git a/AWSCore/Info.plist b/AWSCore/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSCore/Info.plist +++ b/AWSCore/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSCore/Service/AWSService.m b/AWSCore/Service/AWSService.m index 50748edcb92..d2743f727ae 100644 --- a/AWSCore/Service/AWSService.m +++ b/AWSCore/Service/AWSService.m @@ -21,7 +21,7 @@ #import "AWSCocoaLumberjack.h" #import "AWSCategory.h" -NSString *const AWSiOSSDKVersion = @"2.6.12"; +NSString *const AWSiOSSDKVersion = @"2.6.13"; NSString *const AWSServiceErrorDomain = @"com.amazonaws.AWSServiceErrorDomain"; static NSString *const AWSServiceConfigurationUnknown = @"Unknown"; diff --git a/AWSDynamoDB.podspec b/AWSDynamoDB.podspec index d2571069c34..985c0b2c956 100644 --- a/AWSDynamoDB.podspec +++ b/AWSDynamoDB.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSDynamoDB' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,6 +12,6 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSDynamoDB/*.{h,m}' end diff --git a/AWSDynamoDB/AWSDynamoDBService.m b/AWSDynamoDB/AWSDynamoDBService.m index 0ac7cfadf31..85f94f0d989 100644 --- a/AWSDynamoDB/AWSDynamoDBService.m +++ b/AWSDynamoDB/AWSDynamoDBService.m @@ -27,7 +27,7 @@ #import "AWSDynamoDBRequestRetryHandler.h" static NSString *const AWSInfoDynamoDB = @"DynamoDB"; -static NSString *const AWSDynamoDBSDKVersion = @"2.6.12"; +static NSString *const AWSDynamoDBSDKVersion = @"2.6.13"; @interface AWSDynamoDBResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSDynamoDB/Info.plist b/AWSDynamoDB/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSDynamoDB/Info.plist +++ b/AWSDynamoDB/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSEC2.podspec b/AWSEC2.podspec index a5487183edc..dab13a8013f 100644 --- a/AWSEC2.podspec +++ b/AWSEC2.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSEC2' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,6 +12,6 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSEC2/*.{h,m}' end diff --git a/AWSEC2/AWSEC2Service.m b/AWSEC2/AWSEC2Service.m index 603e935617c..1cea2eef66f 100644 --- a/AWSEC2/AWSEC2Service.m +++ b/AWSEC2/AWSEC2Service.m @@ -27,7 +27,7 @@ #import "AWSEC2Serializer.h" static NSString *const AWSInfoEC2 = @"EC2"; -static NSString *const AWSEC2SDKVersion = @"2.6.12"; +static NSString *const AWSEC2SDKVersion = @"2.6.13"; @interface AWSEC2ResponseSerializer : AWSXMLResponseSerializer diff --git a/AWSEC2/Info.plist b/AWSEC2/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSEC2/Info.plist +++ b/AWSEC2/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSElasticLoadBalancing.podspec b/AWSElasticLoadBalancing.podspec index d30fd62dd48..880dfa59021 100644 --- a/AWSElasticLoadBalancing.podspec +++ b/AWSElasticLoadBalancing.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSElasticLoadBalancing' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,6 +12,6 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSElasticLoadBalancing/*.{h,m}' end diff --git a/AWSElasticLoadBalancing/AWSElasticLoadBalancingService.m b/AWSElasticLoadBalancing/AWSElasticLoadBalancingService.m index 8dee98e829e..8d031553319 100644 --- a/AWSElasticLoadBalancing/AWSElasticLoadBalancingService.m +++ b/AWSElasticLoadBalancing/AWSElasticLoadBalancingService.m @@ -26,7 +26,7 @@ #import "AWSElasticLoadBalancingResources.h" static NSString *const AWSInfoElasticLoadBalancing = @"ElasticLoadBalancing"; -static NSString *const AWSElasticLoadBalancingSDKVersion = @"2.6.12"; +static NSString *const AWSElasticLoadBalancingSDKVersion = @"2.6.13"; @interface AWSElasticLoadBalancingResponseSerializer : AWSXMLResponseSerializer diff --git a/AWSElasticLoadBalancing/Info.plist b/AWSElasticLoadBalancing/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSElasticLoadBalancing/Info.plist +++ b/AWSElasticLoadBalancing/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSFacebookSignIn.podspec b/AWSFacebookSignIn.podspec index e24a3953a5d..2216aafbd1b 100644 --- a/AWSFacebookSignIn.podspec +++ b/AWSFacebookSignIn.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSFacebookSignIn' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,7 +12,7 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSAuthCore', '2.6.12' + s.dependency 'AWSAuthCore', '2.6.13' s.dependency 'FBSDKLoginKit', '~> 4.0' s.dependency 'FBSDKCoreKit', '~> 4.0' s.source_files = 'AWSAuthSDK/Sources/AWSFacebookSignIn/*.{h,m}' diff --git a/AWSGoogleSignIn.podspec b/AWSGoogleSignIn.podspec index 8d8f0249d7e..e8a43cb1437 100644 --- a/AWSGoogleSignIn.podspec +++ b/AWSGoogleSignIn.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSGoogleSignIn' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,7 +12,7 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSAuthCore', '2.6.12' + s.dependency 'AWSAuthCore', '2.6.13' s.source_files = 'AWSAuthSDK/Sources/AWSGoogleSignIn/*.{h,m}', 'AWSAuthSDK/Dependencies/GoogleHeaders/*.h' s.public_header_files = 'AWSAuthSDK/Sources/AWSGoogleSignIn/*.h' s.private_header_files = 'AWSAuthSDK/Dependencies/GoogleHeaders/*.h' diff --git a/AWSIoT.podspec b/AWSIoT.podspec index 6a1a7d153ec..915ba5baa6e 100644 --- a/AWSIoT.podspec +++ b/AWSIoT.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSIoT' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,7 +12,7 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSIoT/*.{h,m}', 'AWSIoT/**/*.{h,m}' s.private_header_files = 'AWSIoT/Internal/*.h' end diff --git a/AWSIoT/AWSIoTDataService.m b/AWSIoT/AWSIoTDataService.m index 62ffb43a4c8..5b30f97f21e 100644 --- a/AWSIoT/AWSIoTDataService.m +++ b/AWSIoT/AWSIoTDataService.m @@ -26,7 +26,7 @@ #import "AWSIoTDataResources.h" static NSString *const AWSInfoIoTData = @"IoTData"; -static NSString *const AWSIoTDataSDKVersion = @"2.6.12"; +static NSString *const AWSIoTDataSDKVersion = @"2.6.13"; @interface AWSIoTDataResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSIoT/AWSIoTService.m b/AWSIoT/AWSIoTService.m index d7396c22399..48012f230a5 100644 --- a/AWSIoT/AWSIoTService.m +++ b/AWSIoT/AWSIoTService.m @@ -26,7 +26,7 @@ #import "AWSIoTResources.h" static NSString *const AWSInfoIoT = @"IoT"; -static NSString *const AWSIoTSDKVersion = @"2.6.12"; +static NSString *const AWSIoTSDKVersion = @"2.6.13"; @interface AWSIoTResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSIoT/Info.plist b/AWSIoT/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSIoT/Info.plist +++ b/AWSIoT/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSKMS.podspec b/AWSKMS.podspec index 36384db5d09..509cc0263fc 100644 --- a/AWSKMS.podspec +++ b/AWSKMS.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSKMS' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,6 +12,6 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSKMS/*.{h,m}' end diff --git a/AWSKMS/AWSKMSService.m b/AWSKMS/AWSKMSService.m index 87afff0ae2d..236aca87bef 100644 --- a/AWSKMS/AWSKMSService.m +++ b/AWSKMS/AWSKMSService.m @@ -26,7 +26,7 @@ #import "AWSKMSResources.h" static NSString *const AWSInfoKMS = @"KMS"; -static NSString *const AWSKMSSDKVersion = @"2.6.12"; +static NSString *const AWSKMSSDKVersion = @"2.6.13"; @interface AWSKMSResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSKMS/Info.plist b/AWSKMS/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSKMS/Info.plist +++ b/AWSKMS/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSKinesis.podspec b/AWSKinesis.podspec index d92970c1824..44f4c54b090 100644 --- a/AWSKinesis.podspec +++ b/AWSKinesis.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSKinesis' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,6 +12,6 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSKinesis/*.{h,m}' end diff --git a/AWSKinesis/AWSFirehoseService.m b/AWSKinesis/AWSFirehoseService.m index 822bac16575..7f25d5640f1 100644 --- a/AWSKinesis/AWSFirehoseService.m +++ b/AWSKinesis/AWSFirehoseService.m @@ -26,7 +26,7 @@ #import "AWSFirehoseResources.h" static NSString *const AWSInfoFirehose = @"Firehose"; -static NSString *const AWSFirehoseSDKVersion = @"2.6.12"; +static NSString *const AWSFirehoseSDKVersion = @"2.6.13"; @interface AWSFirehoseResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSKinesis/AWSKinesisService.m b/AWSKinesis/AWSKinesisService.m index c174d32098e..054dfeb4c9f 100644 --- a/AWSKinesis/AWSKinesisService.m +++ b/AWSKinesis/AWSKinesisService.m @@ -27,7 +27,7 @@ #import "AWSKinesisRequestRetryHandler.h" static NSString *const AWSInfoKinesis = @"Kinesis"; -static NSString *const AWSKinesisSDKVersion = @"2.6.12"; +static NSString *const AWSKinesisSDKVersion = @"2.6.13"; @interface AWSKinesisResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSKinesis/Info.plist b/AWSKinesis/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSKinesis/Info.plist +++ b/AWSKinesis/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSLambda.podspec b/AWSLambda.podspec index 39291d91a25..d80f3d9a078 100644 --- a/AWSLambda.podspec +++ b/AWSLambda.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSLambda' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,6 +12,6 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSLambda/*.{h,m}' end diff --git a/AWSLambda/AWSLambdaService.m b/AWSLambda/AWSLambdaService.m index 76fd6cdece7..9b4e40e102f 100644 --- a/AWSLambda/AWSLambdaService.m +++ b/AWSLambda/AWSLambdaService.m @@ -27,7 +27,7 @@ #import "AWSLambdaRequestRetryHandler.h" static NSString *const AWSInfoLambda = @"Lambda"; -static NSString *const AWSLambdaSDKVersion = @"2.6.12"; +static NSString *const AWSLambdaSDKVersion = @"2.6.13"; @interface AWSLambdaResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSLambda/Info.plist b/AWSLambda/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSLambda/Info.plist +++ b/AWSLambda/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSLex.podspec b/AWSLex.podspec index 8274d595947..53acea1a389 100644 --- a/AWSLex.podspec +++ b/AWSLex.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSLex' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,7 +12,7 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSLex/*.{h,m}', 'AWSLex/Bluefront/include/*.h' s.public_header_files = 'AWSLex/*.h' s.private_header_files = 'AWSLex/Bluefront/include/*.h' diff --git a/AWSLex/AWSLexInteractionKit.m b/AWSLex/AWSLexInteractionKit.m index 016a515c073..536f5ab69d7 100644 --- a/AWSLex/AWSLexInteractionKit.m +++ b/AWSLex/AWSLexInteractionKit.m @@ -22,7 +22,7 @@ #import NSString *const AWSInfoInteractionKit = @"LexInteractionKit"; -NSString *const AWSInteractionKitSDKVersion = @"2.6.12"; +NSString *const AWSInteractionKitSDKVersion = @"2.6.13"; NSString *const AWSInternalLexInteractionKit = @"LexInteractionKitClient"; NSString *const AWSLexInteractionKitUserAgent = @"interactionkit"; NSString *const AWSLexInteractionKitErrorDomain = @"com.amazonaws.AWSLexInteractionKitErrorDomain"; diff --git a/AWSLex/AWSLexService.m b/AWSLex/AWSLexService.m index e90a82aad8f..842c3c5a1db 100644 --- a/AWSLex/AWSLexService.m +++ b/AWSLex/AWSLexService.m @@ -28,7 +28,7 @@ #import "AWSLexSignature.h" static NSString *const AWSInfoLex = @"Lex"; -static NSString *const AWSLexSDKVersion = @"2.6.12"; +static NSString *const AWSLexSDKVersion = @"2.6.13"; @interface AWSLexResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSLex/Info.plist b/AWSLex/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSLex/Info.plist +++ b/AWSLex/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSLogs.podspec b/AWSLogs.podspec index 93d8e7d9838..8f59a4c3e45 100644 --- a/AWSLogs.podspec +++ b/AWSLogs.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSLogs' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,6 +12,6 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSLogs/*.{h,m}' end diff --git a/AWSLogs/AWSLogsService.m b/AWSLogs/AWSLogsService.m index 68dc90725ae..1b3172df39d 100644 --- a/AWSLogs/AWSLogsService.m +++ b/AWSLogs/AWSLogsService.m @@ -26,7 +26,7 @@ #import "AWSLogsResources.h" static NSString *const AWSInfoLogs = @"Logs"; -static NSString *const AWSLogsSDKVersion = @"2.6.12"; +static NSString *const AWSLogsSDKVersion = @"2.6.13"; @interface AWSLogsResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSLogs/Info.plist b/AWSLogs/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSLogs/Info.plist +++ b/AWSLogs/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSMachineLearning.podspec b/AWSMachineLearning.podspec index e6ba1c375ff..bfff15c58a2 100644 --- a/AWSMachineLearning.podspec +++ b/AWSMachineLearning.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSMachineLearning' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,6 +12,6 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSMachineLearning/*.{h,m}' end diff --git a/AWSMachineLearning/AWSMachineLearningService.m b/AWSMachineLearning/AWSMachineLearningService.m index 41bcb511c7c..7863bbae0cb 100644 --- a/AWSMachineLearning/AWSMachineLearningService.m +++ b/AWSMachineLearning/AWSMachineLearningService.m @@ -26,7 +26,7 @@ #import "AWSMachineLearningResources.h" static NSString *const AWSInfoMachineLearning = @"MachineLearning"; -static NSString *const AWSMachineLearningSDKVersion = @"2.6.12"; +static NSString *const AWSMachineLearningSDKVersion = @"2.6.13"; diff --git a/AWSMachineLearning/Info.plist b/AWSMachineLearning/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSMachineLearning/Info.plist +++ b/AWSMachineLearning/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSMobileAnalytics.podspec b/AWSMobileAnalytics.podspec index c1a7ab36ce1..fe3d92b600c 100644 --- a/AWSMobileAnalytics.podspec +++ b/AWSMobileAnalytics.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSMobileAnalytics' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,7 +12,7 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSMobileAnalytics/*.{h,m}', 'AWSMobileAnalytics/**/*.{h,m}' s.private_header_files = 'AWSMobileAnalytics/Internal/*.h' end diff --git a/AWSMobileAnalytics/AWSMobileAnalyticsERS/AWSMobileAnalyticsERSService.m b/AWSMobileAnalytics/AWSMobileAnalyticsERS/AWSMobileAnalyticsERSService.m index 4d520334cb8..40f82d88a86 100644 --- a/AWSMobileAnalytics/AWSMobileAnalyticsERS/AWSMobileAnalyticsERSService.m +++ b/AWSMobileAnalytics/AWSMobileAnalyticsERS/AWSMobileAnalyticsERSService.m @@ -26,7 +26,7 @@ #import "AWSMobileAnalyticsERSResources.h" static NSString *const AWSInfoMobileAnalyticsERS = @"MobileAnalyticsERS"; -static NSString *const AWSMobileAnalyticsERSSDKVersion = @"2.6.12"; +static NSString *const AWSMobileAnalyticsERSSDKVersion = @"2.6.13"; @interface AWSMobileAnalyticsERSResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSMobileAnalytics/Info.plist b/AWSMobileAnalytics/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSMobileAnalytics/Info.plist +++ b/AWSMobileAnalytics/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSMobileClient.podspec b/AWSMobileClient.podspec index 2a7cdc5703c..f52adf6fecf 100644 --- a/AWSMobileClient.podspec +++ b/AWSMobileClient.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSMobileClient' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,7 +12,7 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSAuthCore', '2.6.12' + s.dependency 'AWSAuthCore', '2.6.13' s.source_files = 'AWSAuthSDK/Sources/AWSMobileClient/*.{h,m}' s.public_header_files = 'AWSAuthSDK/Sources/AWSMobileClient/AWSMobileClient.h' end diff --git a/AWSPinpoint.podspec b/AWSPinpoint.podspec index 11081107eed..3bba9b8b7ee 100644 --- a/AWSPinpoint.podspec +++ b/AWSPinpoint.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSPinpoint' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,7 +12,7 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSPinpoint/*.{h,m}', 'AWSPinpoint/**/*.{h,m}' s.private_header_files = 'AWSPinpoint/Internal/*.h' end diff --git a/AWSPinpoint/AWSPinpointAnalytics/AWSPinpointAnalyticsService.m b/AWSPinpoint/AWSPinpointAnalytics/AWSPinpointAnalyticsService.m index 9575a8883cf..2c02e41f300 100644 --- a/AWSPinpoint/AWSPinpointAnalytics/AWSPinpointAnalyticsService.m +++ b/AWSPinpoint/AWSPinpointAnalytics/AWSPinpointAnalyticsService.m @@ -26,7 +26,7 @@ #import "AWSPinpointAnalyticsResources.h" static NSString *const AWSInfoPinpointAnalytics = @"PinpointAnalytics"; -static NSString *const AWSPinpointAnalyticsSDKVersion = @"2.6.12"; +static NSString *const AWSPinpointAnalyticsSDKVersion = @"2.6.13"; @interface AWSPinpointAnalyticsResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSPinpoint/AWSPinpointTargeting/AWSPinpointTargetingService.m b/AWSPinpoint/AWSPinpointTargeting/AWSPinpointTargetingService.m index 284f9d79860..14301b6b6e0 100644 --- a/AWSPinpoint/AWSPinpointTargeting/AWSPinpointTargetingService.m +++ b/AWSPinpoint/AWSPinpointTargeting/AWSPinpointTargetingService.m @@ -26,7 +26,7 @@ #import "AWSPinpointTargetingResources.h" static NSString *const AWSInfoPinpointTargeting = @"PinpointTargeting"; -static NSString *const AWSPinpointTargetingSDKVersion = @"2.6.12"; +static NSString *const AWSPinpointTargetingSDKVersion = @"2.6.13"; @interface AWSPinpointTargetingResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSPinpoint/Info.plist b/AWSPinpoint/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSPinpoint/Info.plist +++ b/AWSPinpoint/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSPolly.podspec b/AWSPolly.podspec index 46e07f21e7e..2395673383c 100644 --- a/AWSPolly.podspec +++ b/AWSPolly.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSPolly' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,6 +12,6 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSPolly/*.{h,m}' end diff --git a/AWSPolly/AWSPollyService.m b/AWSPolly/AWSPollyService.m index c3dabe2d3fa..fedb52620a7 100644 --- a/AWSPolly/AWSPollyService.m +++ b/AWSPolly/AWSPollyService.m @@ -26,7 +26,7 @@ #import "AWSPollyResources.h" static NSString *const AWSInfoPolly = @"Polly"; -static NSString *const AWSPollySDKVersion = @"2.6.12"; +static NSString *const AWSPollySDKVersion = @"2.6.13"; @interface AWSPollyResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSPolly/AWSPollySynthesizeSpeechURLBuilder.m b/AWSPolly/AWSPollySynthesizeSpeechURLBuilder.m index c14bff639e3..caf8782b1ad 100644 --- a/AWSPolly/AWSPollySynthesizeSpeechURLBuilder.m +++ b/AWSPolly/AWSPollySynthesizeSpeechURLBuilder.m @@ -16,7 +16,7 @@ #import "AWSPollySynthesizeSpeechURLBuilder.h" static NSString *const AWSInfoPollySynthesizeSpeechURLBuilder = @"PollySynthesizeSpeechUrlBuilder"; -static NSString *const AWSPollySDKVersion = @"2.6.12"; +static NSString *const AWSPollySDKVersion = @"2.6.13"; NSString *const AWSPollySynthesizeSpeechURLBuilderErrorDomain = @"com.amazonaws.AWSPollySynthesizeSpeechURLBuilderErrorDomain"; NSString *const AWSPollyPresignedUrlPath = @"v1/speech"; diff --git a/AWSPolly/Info.plist b/AWSPolly/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSPolly/Info.plist +++ b/AWSPolly/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSRekognition.podspec b/AWSRekognition.podspec index f71281971e2..85fc2688b2f 100644 --- a/AWSRekognition.podspec +++ b/AWSRekognition.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSRekognition' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,6 +12,6 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSRekognition/*.{h,m}' end diff --git a/AWSRekognition/AWSRekognitionService.m b/AWSRekognition/AWSRekognitionService.m index e1358e59874..bc423117fd0 100644 --- a/AWSRekognition/AWSRekognitionService.m +++ b/AWSRekognition/AWSRekognitionService.m @@ -26,7 +26,7 @@ #import "AWSRekognitionResources.h" static NSString *const AWSInfoRekognition = @"Rekognition"; -static NSString *const AWSRekognitionSDKVersion = @"2.6.12"; +static NSString *const AWSRekognitionSDKVersion = @"2.6.13"; @interface AWSRekognitionResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSRekognition/Info.plist b/AWSRekognition/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSRekognition/Info.plist +++ b/AWSRekognition/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSS3.podspec b/AWSS3.podspec index 130e90f8513..9e9108dd0f4 100644 --- a/AWSS3.podspec +++ b/AWSS3.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSS3' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,6 +12,6 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSS3/*.{h,m}' end diff --git a/AWSS3/AWSS3PreSignedURL.h b/AWSS3/AWSS3PreSignedURL.h index 9f3a5991d7f..cbfff2282bc 100644 --- a/AWSS3/AWSS3PreSignedURL.h +++ b/AWSS3/AWSS3PreSignedURL.h @@ -204,7 +204,6 @@ typedef NS_ENUM(NSInteger, AWSS3PresignedURLErrorType) { */ @interface AWSS3GetPreSignedURLRequest : NSObject - /** Returns whether the client has enabled accelerate mode for getting and putting objects. The default is `NO`. */ diff --git a/AWSS3/AWSS3PreSignedURL.m b/AWSS3/AWSS3PreSignedURL.m index 5e8cee44b01..fa5d271dfb0 100644 --- a/AWSS3/AWSS3PreSignedURL.m +++ b/AWSS3/AWSS3PreSignedURL.m @@ -26,7 +26,7 @@ static NSString *const AWSS3PreSignedURLBuilderAcceleratedEndpoint = @"s3-accelerate.amazonaws.com"; static NSString *const AWSInfoS3PreSignedURLBuilder = @"S3PreSignedURLBuilder"; -static NSString *const AWSS3PreSignedURLBuilderSDKVersion = @"2.6.12"; +static NSString *const AWSS3PreSignedURLBuilderSDKVersion = @"2.6.13"; @interface AWSS3PreSignedURLBuilder() @@ -46,6 +46,14 @@ - (void) setRegion:(AWSRegionType)regionType service:(AWSServiceType)serviceType @end +@interface AWSS3GetPreSignedURLRequest () + +@property (nonatomic, strong) NSMutableDictionary *internalRequestParameters; +@property (nonatomic, strong) NSMutableDictionary *internalRequestHeaders; +@property NSString *uploadID; +@property NSNumber *partNumber; +@end + @implementation AWSS3PreSignedURLBuilder static AWSSynchronizedMutableDictionary *_serviceClients = nil; @@ -273,6 +281,17 @@ - (instancetype)initWithConfiguration:(AWSServiceConfiguration *)configuration { } [getPreSignedURLRequest setValue:host forRequestHeader:@"host"]; + //If this is a presigned request for a multipart upload, set the uploadID and partNumber on the request. + if (getPreSignedURLRequest.uploadID + && getPreSignedURLRequest.partNumber) { + + [getPreSignedURLRequest setValue:getPreSignedURLRequest.uploadID + forRequestParameter:@"uploadId"]; + + [getPreSignedURLRequest setValue:[NSString stringWithFormat:@"%@", getPreSignedURLRequest.partNumber] + forRequestParameter:@"partNumber"]; + } + AWSEndpoint *newEndpoint = [[AWSEndpoint alloc]initWithRegion:configuration.regionType service:AWSServiceS3 URL:[NSURL URLWithString:[NSString stringWithFormat:@"%@://%@", endpoint.useUnsafeURL?@"http":@"https", host]]]; int32_t expireDuration = [expires timeIntervalSinceNow]; @@ -296,13 +315,6 @@ - (instancetype)initWithConfiguration:(AWSServiceConfiguration *)configuration { @end -@interface AWSS3GetPreSignedURLRequest () - -@property (nonatomic, strong) NSMutableDictionary *internalRequestParameters; -@property (nonatomic, strong) NSMutableDictionary *internalRequestHeaders; - -@end - @implementation AWSS3GetPreSignedURLRequest - (instancetype)init { diff --git a/AWSS3/AWSS3Service.m b/AWSS3/AWSS3Service.m index dc18b4a33a9..c229c5da206 100644 --- a/AWSS3/AWSS3Service.m +++ b/AWSS3/AWSS3Service.m @@ -28,7 +28,7 @@ #import "AWSS3Serializer.h" static NSString *const AWSInfoS3 = @"S3"; -static NSString *const AWSS3SDKVersion = @"2.6.12"; +static NSString *const AWSS3SDKVersion = @"2.6.13"; diff --git a/AWSS3/AWSS3TransferUtility+Validation.h b/AWSS3/AWSS3TransferUtility+Validation.h new file mode 100644 index 00000000000..4624bda37c3 --- /dev/null +++ b/AWSS3/AWSS3TransferUtility+Validation.h @@ -0,0 +1,21 @@ +// +// Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// A copy of the License is located at +// +// http://aws.amazon.com/apache2.0 +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. +// + +#import + +@interface AWSS3TransferUtility (Validation) +- (AWSTask *) validateParameters: (NSString * )bucket fileURL:(NSURL *)fileURL accelerationModeEnabled: (BOOL) accelerationModeEnabled; + +@end diff --git a/AWSS3/AWSS3TransferUtility+Validation.m b/AWSS3/AWSS3TransferUtility+Validation.m new file mode 100644 index 00000000000..b12df40a922 --- /dev/null +++ b/AWSS3/AWSS3TransferUtility+Validation.m @@ -0,0 +1,47 @@ +// +// Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// A copy of the License is located at +// +// http://aws.amazon.com/apache2.0 +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. +// + +#import "AWSS3TransferUtility+Validation.h" + +@implementation AWSS3TransferUtility (Validation) + +- (AWSTask *) validateParameters: (NSString * )bucket fileURL:(NSURL *)fileURL accelerationModeEnabled: (BOOL) accelerationModeEnabled +{ + //Validate input parameter: bucket + if (!bucket || [bucket length] == 0) { + NSInteger errorCode = (accelerationModeEnabled) ? + AWSS3PresignedURLErrorInvalidBucketNameForAccelerateModeEnabled : AWSS3PresignedURLErrorInvalidBucketName; + + NSString *errorMessage = @"Invalid bucket specified. Please specify a bucket name or configure the bucket property in `AWSS3TransferUtilityConfiguration`."; + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMessage + forKey:NSLocalizedDescriptionKey]; + + return [AWSTask taskWithError:[NSError errorWithDomain:AWSS3PresignedURLErrorDomain + code:errorCode + userInfo:userInfo]]; + } + + NSString *filePath = [fileURL path]; + // Error out if the length of file name < minimum file path length (2 characters) or file does not exist + if ([filePath length] < 2 || + ! [[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + AWSDDLogDebug(@"I am here"); + return [AWSTask taskWithError:[NSError errorWithDomain:AWSS3TransferUtilityErrorDomain + code:AWSS3TransferUtilityErrorLocalFileNotFound + userInfo:nil]]; + } + return nil; +} +@end diff --git a/AWSS3/AWSS3TransferUtility.h b/AWSS3/AWSS3TransferUtility.h index f1ac6910e4e..7817d4a2771 100644 --- a/AWSS3/AWSS3TransferUtility.h +++ b/AWSS3/AWSS3TransferUtility.h @@ -16,6 +16,8 @@ #import #import +#import "AWSS3Service.h" + NS_ASSUME_NONNULL_BEGIN FOUNDATION_EXPORT NSString *const AWSS3TransferUtilityErrorDomain; @@ -27,16 +29,20 @@ typedef NS_ENUM(NSInteger, AWSS3TransferUtilityErrorType) { AWSS3TransferUtilityErrorLocalFileNotFound }; + FOUNDATION_EXPORT NSString *const AWSS3TransferUtilityURLSessionDidBecomeInvalidNotification; @class AWSS3TransferUtilityConfiguration; @class AWSS3TransferUtilityTask; @class AWSS3TransferUtilityUploadTask; +@class AWSS3TransferUtilityMultiPartUploadTask; @class AWSS3TransferUtilityDownloadTask; @class AWSS3TransferUtilityExpression; @class AWSS3TransferUtilityUploadExpression; +@class AWSS3TransferUtilityMultiPartUploadExpression; @class AWSS3TransferUtilityDownloadExpression; + /** The upload completion handler. @@ -46,6 +52,15 @@ FOUNDATION_EXPORT NSString *const AWSS3TransferUtilityURLSessionDidBecomeInvalid typedef void (^AWSS3TransferUtilityUploadCompletionHandlerBlock) (AWSS3TransferUtilityUploadTask *task, NSError * _Nullable error); +/** + The upload completion handler for MultiPart. + + @param task The upload task object. + @param error Returns the error object when the download failed. + */ +typedef void (^AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock) (AWSS3TransferUtilityMultiPartUploadTask *task, + NSError * _Nullable error); + /** The download completion handler. @@ -70,6 +85,16 @@ typedef void (^AWSS3TransferUtilityDownloadCompletionHandlerBlock) (AWSS3Transfe typedef void (^AWSS3TransferUtilityProgressBlock) (AWSS3TransferUtilityTask *task, NSProgress *progress); +/** + The multi part transfer progress feedback block. + + @param task The upload task object. + @param progress The progress object. +*/ +typedef void (^AWSS3TransferUtilityMultiPartProgressBlock) (AWSS3TransferUtilityMultiPartUploadTask *task, + NSProgress *progress); + + #pragma mark - AWSS3TransferUtility /** @@ -257,7 +282,7 @@ typedef void (^AWSS3TransferUtilityProgressBlock) (AWSS3TransferUtilityTask *tas + (void)removeS3TransferUtilityForKey:(NSString *)key; /** - Tells `AWSS3TransferUtility` that events related to a URL session are waiting to be processed. This method needs to be called in the `- application:handleEventsForBackgroundURLSession:completionHandler:` applicatoin delegate for `AWSS3TransferUtility` to work. + Tells `AWSS3TransferUtility` that events related to a URL session are waiting to be processed. This method needs to be called in the `- application:handleEventsForBackgroundURLSession:completionHandler:` application delegate for `AWSS3TransferUtility` to work. @param application The singleton app object. @param identifier The identifier of the URL session requiring attention. @@ -340,6 +365,84 @@ handleEventsForBackgroundURLSession:(NSString *)identifier expression:(nullable AWSS3TransferUtilityUploadExpression *)expression completionHandler:(nullable AWSS3TransferUtilityUploadCompletionHandlerBlock)completionHandler; +/** + Saves the `NSData` to a temporary directory and uploads it to the configured Amazon S3 bucket in `AWSS3TransferUtilityConfiguration` using Multipart. + + @param data The data to upload. + @param key The Amazon S3 object key name. + @param contentType `Content-Type` of the data. + @param expression The container object to configure the upload request. + @param completionHandler The completion handler when the upload completes. + + @return Returns an instance of `AWSTask`. On successful initialization, `task.result` contains an instance of `AWSS3TransferUtilityMultiPartUploadTask`. + */ +- (AWSTask *)uploadDataUsingMultiPart:(NSData *)data + key:(NSString *)key + contentType:(NSString *)contentType + expression:(nullable AWSS3TransferUtilityMultiPartUploadExpression *)expression + completionHandler:(nullable AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock)completionHandler + NS_SWIFT_NAME(uploadUsingMultiPart(data:key:contentType:expression:completionHandler:)); + +/** + Saves the `NSData` to a temporary directory and uploads it to the specified Amazon S3 bucket using Multipart. + + @param data The data to upload. + @param bucket The Amazon S3 bucket name. + @param key The Amazon S3 object key name. + @param contentType `Content-Type` of the data. + @param expression The container object to configure the upload request. + @param completionHandler The completion handler when the upload completes. + + @return Returns an instance of `AWSTask`. On successful initialization, `task.result` contains an instance of `AWSS3TransferUtilityMultiPartUploadTask`. + */ +- (AWSTask *)uploadDataUsingMultiPart:(NSData *)data + bucket:(NSString *)bucket + key:(NSString *)key + contentType:(NSString *)contentType + expression:(nullable AWSS3TransferUtilityMultiPartUploadExpression *)expression + completionHandler:(nullable AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock)completionHandler + NS_SWIFT_NAME(uploadUsingMultiPart(data:bucket:key:contentType:expression:completionHandler:)); + +/** + Uploads the file to the configured Amazon S3 bucket in `AWSS3TransferUtilityConfiguration` using MultiPart. + + @param fileURL The file URL of the file to upload. + @param key The Amazon S3 object key name. + @param contentType `Content-Type` of the file. + @param expression The container object to configure the upload request. + @param completionHandler The completion handler when the upload completes. + + @return Returns an instance of `AWSTask`. On successful initialization, `task.result` contains an instance of `AWSS3TransferUtilityMultiPartUploadTask`. + */ +- (AWSTask *)uploadFileUsingMultiPart:(NSURL *)fileURL + key:(NSString *)key + contentType:(NSString *)contentType + expression:(nullable AWSS3TransferUtilityMultiPartUploadExpression *)expression + completionHandler:(nullable AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock)completionHandler + NS_SWIFT_NAME(uploadUsingMultiPart(fileURL:key:contentType:expression:completionHandler:)); + + +/** + Uploads the file to the specified Amazon S3 bucket using MultiPart. + + @param fileURL The file URL of the file to upload. + @param bucket The Amazon S3 bucket name. + @param key The Amazon S3 object key name. + @param contentType `Content-Type` of the file. + @param expression The container object to configure the upload request. + @param completionHandler The completion handler when the upload completes. + + @return Returns an instance of `AWSTask`. On successful initialization, `task.result` contains an instance of `AWSS3TransferUtilityMultiPartUploadTask`. + */ +- (AWSTask *)uploadFileUsingMultiPart:(NSURL *)fileURL + bucket:(NSString *)bucket + key:(NSString *)key + contentType:(NSString *)contentType + expression:(nullable AWSS3TransferUtilityMultiPartUploadExpression *)expression + completionHandler:(nullable AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock)completionHandler + NS_SWIFT_NAME(uploadUsingMultiPart(fileURL:bucket:key:contentType:expression:completionHandler:)); + + /** Downloads the specified Amazon S3 object as `NSData` from the bucket configured in `AWSS3TransferUtilityConfiguration`. @@ -404,8 +507,8 @@ handleEventsForBackgroundURLSession:(NSString *)identifier /** Assigns progress feedback and completion handler blocks. This method should be called when the app was suspended while the transfer is still happening. - @param uploadBlocksAssigner The block for assigning the upload pregree feedback and completion handler blocks. - @param downloadBlocksAssigner The block for assigning the download pregree feedback and completion handler blocks. + @param uploadBlocksAssigner The block for assigning the upload progress feedback and completion handler blocks. + @param downloadBlocksAssigner The block for assigning the download progress feedback and completion handler blocks. */ - (void)enumerateToAssignBlocksForUploadTask:(nullable void (^)(AWSS3TransferUtilityUploadTask *uploadTask, _Nullable AWSS3TransferUtilityProgressBlock * _Nullable uploadProgressBlockReference, @@ -415,11 +518,28 @@ handleEventsForBackgroundURLSession:(NSString *)identifier _Nullable AWSS3TransferUtilityDownloadCompletionHandlerBlock * _Nullable completionHandlerReference))downloadBlocksAssigner; /** - Retrieves all running tasks. + Assigns progress feedback and completion handler blocks. This method should be called when the app was suspended while the transfer is still happening. + + @param uploadBlocksAssigner The block for assigning the upload progress feedback and completion handler blocks. + @param multiPartUploadBlocksAssigner The block for assigning the multipart upload progress feedback and completion handler blocks. + @param downloadBlocksAssigner The block for assigning the download progress feedback and completion handler blocks. + */ +-(void)enumerateToAssignBlocksForUploadTask:(void (^)(AWSS3TransferUtilityUploadTask *uploadTask, + AWSS3TransferUtilityProgressBlock _Nullable * _Nullable uploadProgressBlockReference, + AWSS3TransferUtilityUploadCompletionHandlerBlock _Nullable * _Nullable completionHandlerReference))uploadBlocksAssigner + multiPartUploadBlocksAssigner: (void (^) (AWSS3TransferUtilityMultiPartUploadTask *multiPartUploadTask, + AWSS3TransferUtilityMultiPartProgressBlock _Nullable * _Nullable multiPartUploadProgressBlockReference, + AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock _Nullable * _Nullable completionHandlerReference)) multiPartUploadBlocksAssigner + downloadBlocksAssigner:(void (^)(AWSS3TransferUtilityDownloadTask *downloadTask, + AWSS3TransferUtilityProgressBlock _Nullable * _Nullable downloadProgressBlockReference, + AWSS3TransferUtilityDownloadCompletionHandlerBlock _Nullable * _Nullable completionHandlerReference))downloadBlocksAssigner; - @return An array of `AWSS3TransferUtilityTask`. +/** + Retrieves all running tasks. + @deprecated Use `getUploadTasks:, getMultiPartUploadTasks: and getDownloadTasks:` methods instead. + @return An array of containing `AWSS3TransferUtilityUploadTask` and `AWSS3TransferUtilityDownloadTask` objects. */ -- (AWSTask *> *)getAllTasks; +- (AWSTask *> *)getAllTasks __attribute((deprecated)); /** Retrieves all running upload tasks. @@ -428,6 +548,13 @@ handleEventsForBackgroundURLSession:(NSString *)identifier */ - (AWSTask *> *)getUploadTasks; +/** + Retrieves all running MultiPart upload tasks. + + @return An array of `AWSS3TransferUtilityMultiPartUploadTask`. + */ +- (AWSTask *> *)getMultiPartUploadTasks; + /** Retrieves all running download tasks. @@ -445,6 +572,9 @@ handleEventsForBackgroundURLSession:(NSString *)identifier @property (nonatomic, nullable, copy) NSString *bucket; +@property NSInteger retryLimit; + +@property (nonatomic, nullable) NSNumber *multiPartConcurrencyLimit; @end @@ -455,6 +585,12 @@ handleEventsForBackgroundURLSession:(NSString *)identifier */ @interface AWSS3TransferUtilityTask : NSObject +/** + An identifier uniquely identifies the transferID. + */ + +@property (readonly) NSString *transferID; + /** An identifier uniquely identifies the task within a given `AWSS3TransferUtility` instance. */ @@ -514,6 +650,48 @@ handleEventsForBackgroundURLSession:(NSString *)identifier @end +/** + The task object to represent a multipart upload task. + */ +@interface AWSS3TransferUtilityMultiPartUploadTask: NSObject + +/** + An identifier uniquely identifies the transferID. + */ +@property (readonly) NSString *transferID; + +/** + The Amazon S3 bucket name associated with the transfer. + */ +@property (readonly) NSString *bucket; + +/** + The Amazon S3 object key name associated with the transfer. + */ +@property (readonly) NSString *key; + +/** + The transfer progress. + */ +@property (readonly) NSProgress *progress; + +/** + Cancels the task. + */ +- (void)cancel; + +/** + Resumes the task, if it is suspended. + */ +- (void)resume; + +/** + Temporarily suspends a task. + */ +- (void)suspend; +@end + + /** The task object to represent a download task. */ @@ -573,6 +751,45 @@ handleEventsForBackgroundURLSession:(NSString *)identifier @end + +/** + The expression object for configuring a Multipart upload task. + */ +@interface AWSS3TransferUtilityMultiPartUploadExpression : NSObject + +/** + This NSDictionary can contains additional request headers to be included in the pre-signed URL. Default is emtpy. + */ +@property (nonatomic, readonly) NSDictionary *requestHeaders; + +/** + This NSDictionary can contains additional request parameters to be included in the pre-signed URL. Adding additional request parameters enables more advanced pre-signed URLs, such as accessing Amazon S3's torrent resource for an object, or for specifying a version ID when accessing an object. Default is emtpy. + */ +@property (nonatomic, readonly) NSDictionary *requestParameters; + +/** + The progress feedback block. + */ +@property (copy, nonatomic, nullable) AWSS3TransferUtilityMultiPartProgressBlock progressBlock; +/** + Set an additional request header to be included in the pre-signed URL. + + @param value The value of the request parameter being added. Set to nil if parameter doesn't contains value. + @param requestHeader The name of the request header. + */ +- (void)setValue:(nullable NSString *)value forRequestHeader:(NSString *)requestHeader; + +/** + Set an additional request parameter to be included in the pre-signed URL. Adding additional request parameters enables more advanced pre-signed URLs, such as accessing Amazon S3's torrent resource for an object, or for specifying a version ID when accessing an object. + + @param value The value of the request parameter being added. Set to nil if parameter doesn't contains value. + @param requestParameter The name of the request parameter, as it appears in the URL's query string (e.g. AWSS3PresignedURLVersionID). + */ +- (void)setValue:(nullable NSString *)value forRequestParameter:(NSString *)requestParameter; + +@end + + /** The expression object for configuring a download task. */ diff --git a/AWSS3/AWSS3TransferUtility.m b/AWSS3/AWSS3TransferUtility.m index 1ad7436834f..512e1f6bad8 100644 --- a/AWSS3/AWSS3TransferUtility.m +++ b/AWSS3/AWSS3TransferUtility.m @@ -15,57 +15,116 @@ #import "AWSS3TransferUtility.h" #import "AWSS3PreSignedURL.h" +#import "AWSS3Service.h" #import "AWSSynchronizedMutableDictionary.h" +#import "AWSFMDB.h" +#import "AWSS3TransferUtility+Validation.h" + // Public constants NSString *const AWSS3TransferUtilityErrorDomain = @"com.amazonaws.AWSS3TransferUtilityErrorDomain"; NSString *const AWSS3TransferUtilityURLSessionDidBecomeInvalidNotification = @"com.amazonaws.AWSS3TransferUtility.AWSS3TransferUtilityURLSessionDidBecomeInvalidNotification"; -// Private constnats + +// Private constants static NSString *const AWSS3TransferUtilityIdentifier = @"com.amazonaws.AWSS3TransferUtility.Identifier"; static NSTimeInterval const AWSS3TransferUtilityTimeoutIntervalForResource = 50 * 60; // 50 minutes static NSString *const AWSS3TransferUtilityUserAgent = @"transfer-utility"; static NSString *const AWSInfoS3TransferUtility = @"S3TransferUtility"; +static NSString *const AWSS3TransferUtilityRetryExceeded = @"AWSS3TransferUtilityRetryExceeded"; +static NSString *const AWSS3TransferUtilityRetrySucceeded = @"AWSS3TransferUtilityRetrySucceeded"; +static NSUInteger const AWSS3TransferUtilityMultiPartSize = 5 * 1024 * 1024; +static NSString *const AWSS3TransferUtiltityRequestTimeoutErrorCode = @"RequestTimeout"; +static int const AWSS3TransferUtilityMultiPartDefaultConcurrencyLimit = 5; + #pragma mark - Private classes +@interface AWSS3TransferUtilityUploadSubTask: NSObject +@end + +@interface AWSS3TransferUtilityUploadSubTask() +@property (strong, nonatomic) NSURLSessionTask *sessionTask; +@property (strong, nonatomic) NSNumber *partNumber; +@property (readwrite) NSUInteger taskIdentifier; +@property (strong, nonatomic) NSString *eTag; +@property int64_t totalBytesExpectedToSend; +@property int64_t totalBytesSent; +@property NSString *responseData; +@property NSString *file; +@property NSString *transferID; +@property NSString *status; +@property NSString *uploadID; + +@end + @interface AWSS3TransferUtility() @property (strong, nonatomic) AWSServiceConfiguration *configuration; @property (strong, nonatomic) AWSS3TransferUtilityConfiguration *transferUtilityConfiguration; @property (strong, nonatomic) AWSS3PreSignedURLBuilder *preSignedURLBuilder; +@property (strong, nonatomic) AWSS3 *s3; @property (strong, nonatomic) NSURLSession *session; @property (strong, nonatomic) NSString *sessionIdentifier; -@property (strong, nonatomic) NSString *temporaryDirectoryPath; +@property (strong, nonatomic) NSString *cacheDirectoryPath; @property (strong, nonatomic) AWSSynchronizedMutableDictionary *taskDictionary; @property (copy, nonatomic) void (^backgroundURLSessionCompletionHandler)(void); - +@property (strong, nonatomic) AWSFMDatabaseQueue *databaseQueue; @end @interface AWSS3TransferUtilityTask() @property (strong, nonatomic) NSURLSessionTask *sessionTask; - +@property (strong, nonatomic) NSString *transferID; @property (strong, nonatomic) NSString *bucket; @property (strong, nonatomic) NSString *key; - -// Temporary storages @property (strong, nonatomic) NSData *data; @property (strong, nonatomic) NSURL *location; @property (strong, nonatomic) NSError *error; - +@property int retryCount; +@property NSString *nsURLSessionID; +@property NSString *file; +@property NSString *transferType; +@property NSString *status; +@property (strong) AWSFMDatabaseQueue *databaseQueue; @end @interface AWSS3TransferUtilityUploadTask() @property (strong, nonatomic) AWSS3TransferUtilityUploadExpression *expression; +@property NSString *responseData; +@property (atomic) BOOL cancelled; +@property BOOL temporaryFileCreated; +@end +@interface AWSS3TransferUtilityMultiPartUploadTask() + +@property (strong, nonatomic) AWSS3TransferUtilityMultiPartUploadExpression *expression; +@property NSString * uploadID; +@property BOOL cancelled; +@property BOOL temporaryFileCreated; +@property NSMutableDictionary *waitingPartsDictionary; +@property (strong, nonatomic) NSMutableDictionary *completedPartsDictionary; +@property (strong, nonatomic) NSMutableDictionary *inProgressPartsDictionary; +@property int retryCount; +@property int partNumber; +@property NSString *file; +@property NSString *transferType; +@property NSString *nsURLSessionID; +@property (strong) AWSFMDatabaseQueue *databaseQueue; +@property (strong, nonatomic) NSError *error; +@property (strong, nonatomic) NSString *bucket; +@property (strong, nonatomic) NSString *key; +@property (strong, nonatomic) NSString *transferID; +@property NSString *status; +@property NSNumber *contentLength; @end @interface AWSS3TransferUtilityDownloadTask() @property (strong, nonatomic) AWSS3TransferUtilityDownloadExpression *expression; - +@property BOOL cancelled; +@property NSString *responseData; @end @interface AWSS3TransferUtilityExpression() @@ -85,6 +144,17 @@ @interface AWSS3TransferUtilityUploadExpression() @end +@interface AWSS3TransferUtilityMultiPartUploadExpression() + +@property (strong, nonatomic) NSMutableDictionary *internalRequestHeaders; +@property (strong, nonatomic) NSMutableDictionary *internalRequestParameters; +- (void)assignRequestParameters:(AWSS3GetPreSignedURLRequest *)getPreSignedURLRequest; +- (void)assignRequestHeaders:(AWSS3GetPreSignedURLRequest *)getPreSignedURLRequest; +@property (copy, atomic) AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock completionHandler; + +@end + + @interface AWSS3TransferUtilityDownloadExpression() @property (copy, atomic) AWSS3TransferUtilityDownloadCompletionHandlerBlock completionHandler; @@ -97,6 +167,19 @@ - (instancetype)initWithConfiguration:(AWSServiceConfiguration *)configuration; @end + +@interface AWSS3() + +- (instancetype)initWithConfiguration:(AWSServiceConfiguration *)configuration; + +@end + + +@interface AWSS3GetPreSignedURLRequest() +@property NSString *uploadID; +@property NSNumber *partNumber; +@end + #pragma mark - AWSS3TransferUtility @implementation AWSS3TransferUtility @@ -112,6 +195,7 @@ + (instancetype)defaultS3TransferUtility { AWSServiceConfiguration *serviceConfiguration = nil; AWSS3TransferUtilityConfiguration *transferUtilityConfiguration = nil; AWSServiceInfo *serviceInfo = [[AWSInfo defaultAWSInfo] defaultServiceInfo:AWSInfoS3TransferUtility]; + if (serviceInfo) { serviceConfiguration = [[AWSServiceConfiguration alloc] initWithRegion:serviceInfo.region credentialsProvider:serviceInfo.cognitoCredentialsProvider]; @@ -172,7 +256,7 @@ + (instancetype)S3TransferUtilityForKey:(NSString *)key { forKey:key]; if (serviceInfo) { AWSServiceConfiguration *serviceConfiguration = [[AWSServiceConfiguration alloc] initWithRegion:serviceInfo.region - credentialsProvider:serviceInfo.cognitoCredentialsProvider]; + credentialsProvider:serviceInfo.cognitoCredentialsProvider]; NSNumber *accelerateModeEnabled = [serviceInfo.infoDictionary valueForKey:@"AccelerateModeEnabled"]; AWSS3TransferUtilityConfiguration *transferUtilityConfiguration = [AWSS3TransferUtilityConfiguration new]; @@ -209,14 +293,16 @@ - (instancetype)initWithConfiguration:(AWSServiceConfiguration *)serviceConfigur [_configuration addUserAgentProductToken:AWSS3TransferUtilityUserAgent]; _transferUtilityConfiguration = [transferUtilityConfiguration copy]; - _transferUtilityConfiguration.bucket = transferUtilityConfiguration.bucket; - + _preSignedURLBuilder = [[AWSS3PreSignedURLBuilder alloc] initWithConfiguration:_configuration]; + _s3 = [[AWSS3 alloc] initWithConfiguration:_configuration]; if (identifier) { _sessionIdentifier = identifier; - } else { - _sessionIdentifier = AWSS3TransferUtilityIdentifier; + } + else { + NSString *uuid = [[NSUUID UUID] UUIDString]; + _sessionIdentifier = [AWSS3TransferUtilityIdentifier stringByAppendingString:uuid]; } NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:_sessionIdentifier]; @@ -237,9 +323,15 @@ - (instancetype)initWithConfiguration:(AWSServiceConfiguration *)serviceConfigur _taskDictionary = [AWSSynchronizedMutableDictionary new]; - // Creates a temporary directory for data uploads - _temporaryDirectoryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[_sessionIdentifier aws_md5String]]; - NSURL *directoryURL = [NSURL fileURLWithPath:_temporaryDirectoryPath]; + // Creates a temporary directory for data uploads in the caches directory + + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + NSString *cachePath = [paths objectAtIndex:0]; + + _cacheDirectoryPath = [cachePath stringByAppendingPathComponent:AWSInfoS3TransferUtility]; + AWSDDLogDebug(@"Temporary dir Path is %@", _cacheDirectoryPath); + + NSURL *directoryURL = [NSURL fileURLWithPath:_cacheDirectoryPath]; NSError *error = nil; BOOL result = [[NSFileManager defaultManager] createDirectoryAtURL:directoryURL withIntermediateDirectories:YES @@ -248,16 +340,293 @@ - (instancetype)initWithConfiguration:(AWSServiceConfiguration *)serviceConfigur if (!result) { AWSDDLogError(@"Failed to create a temporary directory: %@", error); } - - // Clean up the temporary directory - __weak AWSS3TransferUtility *weakSelf = self; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ - [weakSelf cleanUpTemporaryDirectory]; - }); + + //Instantiate the Database Helper + self.databaseQueue = [self createDatabase]; } return self; } +#pragma mark - recovery methods + +- (void) recover:(void (^)(AWSS3TransferUtilityUploadTask *uploadTask, + AWSS3TransferUtilityProgressBlock *uploadProgressBlockReference, + AWSS3TransferUtilityUploadCompletionHandlerBlock *completionHandlerReference))uploadBlocksAssigner +multiPartUploadBlocksAssigner: (void (^) (AWSS3TransferUtilityMultiPartUploadTask *multiPartUploadTask, + AWSS3TransferUtilityMultiPartProgressBlock *multiPartUploadProgressBlockReference, + AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock *completionHandlerReference)) multiPartUploadBlocksAssigner +downloadBlocksAssigner:(void (^)(AWSS3TransferUtilityDownloadTask *downloadTask, + AWSS3TransferUtilityProgressBlock *downloadProgressBlockReference, + AWSS3TransferUtilityDownloadCompletionHandlerBlock *completionHandlerReference))downloadBlocksAssigner + completionHandler:(void (^)(NSError *_Nullable error)) completionHandler { + + //Create temporary datastructures to hold the database records. + NSMutableDictionary *multiPartUploads = [NSMutableDictionary new]; + NSMutableDictionary *transferRequests = [NSMutableDictionary new]; + + //Get All Tasks from DB + NSMutableArray *tasks = [self getTransferTaskDataFromDB:_sessionIdentifier]; + + + //Iterate through the tasks and populate + for( NSMutableDictionary *task in tasks ) { + NSString *transferType = [task objectForKey:@"transfer_type"]; + int sessionTaskID = [[task objectForKey:@"session_task_id"] intValue]; + + if ([transferType isEqualToString:@"UPLOAD"]) { + AWSS3TransferUtilityUploadTask *transferUtilityUploadTask = [AWSS3TransferUtilityUploadTask new]; + transferUtilityUploadTask.nsURLSessionID = self.sessionIdentifier; + transferUtilityUploadTask.databaseQueue = self.databaseQueue; + transferUtilityUploadTask.bucket = [task objectForKey:@"bucket"]; + transferUtilityUploadTask.key = [task objectForKey:@"key"]; + transferUtilityUploadTask.expression = [AWSS3TransferUtilityUploadExpression new]; + transferUtilityUploadTask.expression.internalRequestHeaders = [[self getDictionaryFromJson:[task objectForKey:@"request_headers"]] mutableCopy]; + transferUtilityUploadTask.expression.internalRequestParameters = [[self getDictionaryFromJson:[task objectForKey:@"request_parameters"]] mutableCopy]; + transferUtilityUploadTask.transferID = [task objectForKey:@"transfer_id"]; + transferUtilityUploadTask.file = [task objectForKey:@"file"]; + transferUtilityUploadTask.cancelled = NO; + transferUtilityUploadTask.retryCount = [[task objectForKey:@"retry_count"] intValue]; + transferUtilityUploadTask.temporaryFileCreated = [[task objectForKey:@"temporary_file_created"] boolValue]; + transferUtilityUploadTask.status = [task objectForKey:@"status"]; + + //Add the progress block and callback function + if (uploadBlocksAssigner) { + AWSS3TransferUtilityProgressBlock progressBlock = nil; + AWSS3TransferUtilityUploadCompletionHandlerBlock completionHandler = nil; + + uploadBlocksAssigner(transferUtilityUploadTask, &progressBlock, &completionHandler); + + if (progressBlock) { + transferUtilityUploadTask.expression.progressBlock = progressBlock; + } + if (completionHandler) { + transferUtilityUploadTask.expression.completionHandler = completionHandler; + } + } + + //Lodge in temporary Dictionary + [transferRequests setObject:transferUtilityUploadTask forKey:@(sessionTaskID)]; + } + else if ([transferType isEqualToString:@"DOWNLOAD"]) { + AWSS3TransferUtilityDownloadTask *transferUtilityDownloadTask = [AWSS3TransferUtilityDownloadTask new]; + transferUtilityDownloadTask.nsURLSessionID = self.sessionIdentifier; + transferUtilityDownloadTask.databaseQueue = self.databaseQueue; + transferUtilityDownloadTask.bucket = [task objectForKey:@"bucket"]; + transferUtilityDownloadTask.key = [task objectForKey:@"key"]; + transferUtilityDownloadTask.expression = [AWSS3TransferUtilityDownloadExpression new]; + transferUtilityDownloadTask.expression.internalRequestHeaders = [[self getDictionaryFromJson:[task objectForKey:@"request_headers"]] mutableCopy]; + transferUtilityDownloadTask.expression.internalRequestParameters = [[self getDictionaryFromJson:[task objectForKey:@"request_parameters"]] mutableCopy]; + transferUtilityDownloadTask.transferID = [task objectForKey:@"transfer_id"]; + transferUtilityDownloadTask.file = [task objectForKey:@"file"]; + transferUtilityDownloadTask.cancelled = NO; + transferUtilityDownloadTask.retryCount = [[task objectForKey:@"retry_count"] intValue]; + transferUtilityDownloadTask.status = [task objectForKey:@"status"]; + + //Add the progress block and callback Function + if (downloadBlocksAssigner) { + AWSS3TransferUtilityProgressBlock progressBlock = nil; + AWSS3TransferUtilityDownloadCompletionHandlerBlock completionHandler = nil; + + downloadBlocksAssigner(transferUtilityDownloadTask, &progressBlock, &completionHandler); + + if (progressBlock) { + transferUtilityDownloadTask.expression.progressBlock = progressBlock; + } + if (completionHandler) { + transferUtilityDownloadTask.expression.completionHandler = completionHandler; + } + } + + //Lodge in temporary Dictionary + [transferRequests setObject:transferUtilityDownloadTask forKey:@(sessionTaskID)]; + + [self.taskDictionary setObject:transferUtilityDownloadTask forKey:@(sessionTaskID) ]; + AWSDDLogDebug(@"Added Download Transfer task %d to task dictionary", sessionTaskID); + } + else if ([transferType isEqualToString:@"MULTI_PART_UPLOAD"]) { + AWSS3TransferUtilityMultiPartUploadTask *transferUtilityMultiPartUploadTask = [AWSS3TransferUtilityMultiPartUploadTask new]; + transferUtilityMultiPartUploadTask.nsURLSessionID = self.sessionIdentifier; + transferUtilityMultiPartUploadTask.databaseQueue = self.databaseQueue; + transferUtilityMultiPartUploadTask.bucket = [task objectForKey:@"bucket"]; + transferUtilityMultiPartUploadTask.key = [task objectForKey:@"key"]; + transferUtilityMultiPartUploadTask.expression = [AWSS3TransferUtilityMultiPartUploadExpression new]; + transferUtilityMultiPartUploadTask.expression.internalRequestHeaders = [[self getDictionaryFromJson:[task objectForKey:@"request_headers"]] mutableCopy]; + transferUtilityMultiPartUploadTask.expression.internalRequestParameters = [[self getDictionaryFromJson:[task objectForKey:@"request_parameters"]] mutableCopy]; + transferUtilityMultiPartUploadTask.transferID = [task objectForKey:@"transfer_id"]; + transferUtilityMultiPartUploadTask.file = [task objectForKey:@"file"]; + transferUtilityMultiPartUploadTask.temporaryFileCreated = [[task objectForKey:@"temporary_file_created"] boolValue]; + transferUtilityMultiPartUploadTask.contentLength = [task objectForKey:@"content_length"]; + transferUtilityMultiPartUploadTask.cancelled = NO; + transferUtilityMultiPartUploadTask.retryCount = [[task objectForKey:@"retry_count"] intValue]; + transferUtilityMultiPartUploadTask.uploadID = [task objectForKey:@"multi_part_id"]; + + //Add the progress block and callback Function + if (multiPartUploadBlocksAssigner) { + AWSS3TransferUtilityMultiPartProgressBlock progressBlock = nil; + AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock completionHandler = nil; + multiPartUploadBlocksAssigner(transferUtilityMultiPartUploadTask, &progressBlock, &completionHandler); + if (progressBlock) { + transferUtilityMultiPartUploadTask.expression.progressBlock = progressBlock; + } + if (completionHandler) { + transferUtilityMultiPartUploadTask.expression.completionHandler = completionHandler; + } + } + [multiPartUploads setObject:transferUtilityMultiPartUploadTask forKey:transferUtilityMultiPartUploadTask.uploadID]; + } + else if ([transferType isEqualToString:@"MULTI_PART_UPLOAD_SUB_TASK"]) { + AWSS3TransferUtilityUploadSubTask *subTask = [AWSS3TransferUtilityUploadSubTask new]; + subTask.taskIdentifier = sessionTaskID; + subTask.file = [task objectForKey:@"file"]; + subTask.partNumber = [task objectForKey:@"part_number"]; + subTask.eTag =[task objectForKey:@"etag"]; + subTask.uploadID = [task objectForKey:@"multi_part_id"]; + subTask.status = [task objectForKey:@"status"]; + subTask.transferID = [task objectForKey:@"transfer_id"]; + subTask.totalBytesExpectedToSend = [[task objectForKey:@"content_length"] integerValue]; + + //Lodge in temporary Dictionary + [transferRequests setObject:subTask forKey:@(sessionTaskID)]; + + } + } + + //Reattach to the NSURLsession objects + [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { + + if ([dataTasks count] != 0) { + AWSDDLogError(@"The underlying NSURLSession contains data tasks. This should not happen."); + } + + //Loop through all the upload Tasks. + for( NSURLSessionUploadTask *task in uploadTasks ) { + + //Get the Task + id obj = [transferRequests objectForKey:@(task.taskIdentifier)]; + + if ([obj isKindOfClass:[AWSS3TransferUtilityUploadTask class]]) + { + + //Found a upload task. + AWSS3TransferUtilityUploadTask *uploadTask = obj; + uploadTask.sessionTask = task; + [self.taskDictionary setObject:uploadTask forKey:@(uploadTask.taskIdentifier)]; + AWSDDLogDebug(@"Added Upload Transfer task %@ to task dictionary", @(uploadTask.taskIdentifier)); + + //Remove this object from the transferRequests list + [transferRequests removeObjectForKey:@(task.taskIdentifier)]; + + //Check if it is InProgress + if ([uploadTask.status isEqualToString:AWSS3TransferUtilityInProgressStatus]) { + //Check if the the underlying task is completed. If so, delete the record from the DB, clean up any temp files and call the completion handler. + if ([task state] == NSURLSessionTaskStateCompleted ) { + [self.taskDictionary removeObjectForKey:@(uploadTask.taskIdentifier)]; + if ( uploadTask.temporaryFileCreated) { + [self removeFile:uploadTask.file]; + } + [self deleteTransferRequestFromDB:uploadTask.transferID databaseQueue:_databaseQueue]; + if(uploadTask.expression.completionHandler) { + uploadTask.expression.completionHandler(uploadTask,nil); + } + continue; + } + //If it is in any other status than running, then we need to recover by retrying. + if ([task state] != NSURLSessionTaskStateRunning ) { + //We think the task in IN_PROGRESS. The underlying task is not running. + //Recover the situation by retrying. + [self retryUpload:uploadTask]; + continue; + } + } + } + else if ([obj isKindOfClass:[AWSS3TransferUtilityUploadSubTask class]]) { + //Found a upload subtask. + AWSS3TransferUtilityUploadSubTask *subTaskObj = obj; + subTaskObj.sessionTask = task; + AWSS3TransferUtilityMultiPartUploadTask *multiPartUploadTask = [multiPartUploads objectForKey:subTaskObj.uploadID]; + + [self.taskDictionary setObject:multiPartUploadTask forKey:@(task.taskIdentifier)]; + AWSDDLogDebug(@"Added MP task[%@] for session ID: %@",multiPartUploadTask.uploadID, @(task.taskIdentifier)); + + //Remove this object from the transferRequests list + [transferRequests removeObjectForKey:@(task.taskIdentifier)]; + + //Check if it is is already completed. If it is, add it to the completed parts list and go to the next iteration of the loop + if ( [subTaskObj.status isEqualToString:AWSS3TransferUtilityCompletedStatus]) { + [multiPartUploadTask.completedPartsDictionary setObject:subTaskObj forKey:@(task.taskIdentifier)]; + multiPartUploadTask.progress.completedUnitCount += subTaskObj.totalBytesExpectedToSend; + continue; + } + + //Check if it is in Waiting status. If it is, add it to the waiting parts list and go to the next iteration of the loop. + if ( [subTaskObj.status isEqualToString:AWSS3TransferUtilityWaitingStatus]) { + [multiPartUploadTask.waitingPartsDictionary setObject:subTaskObj forKey:@(task.taskIdentifier)]; + continue; + } + + //Add it to the InProgress list + [multiPartUploadTask.inProgressPartsDictionary setObject:subTaskObj forKey:@(task.taskIdentifier)]; + + //Check if it is in Paused status. If it is, there is nothing more to do. + if ( [subTaskObj.status isEqualToString:AWSS3TransferUtilityPausedStatus]) { + continue; + } + + //The only state that it can be now is in IN_PROGRESS. Check if the underlying NSURLSessionTask is Not running. + if ([task state] != NSURLSessionTaskStateRunning ) { + AWSDDLogDebug(@"SubTask %lu is in %@ according to DB, but the underlying task is not running. Retrying", (unsigned long)subTaskObj.taskIdentifier, + subTaskObj.status); + //We think the task in IN_PROGRESS. The underlying task is not running. + //Recover the situation by retrying. + [self retryUploadSubTask:multiPartUploadTask subTask:subTaskObj]; + } + } + else { + AWSDDLogWarn(@"Object not found in taskDictionary for %lu. Ignoring.",(unsigned long)task.taskIdentifier); + } + } + + for( NSURLSessionDownloadTask *task in downloadTasks ) { + id obj = [self.taskDictionary objectForKey:@(task.taskIdentifier)]; + if ([obj isKindOfClass:[AWSS3TransferUtilityDownloadTask class]]) + { + //Found a download task + AWSS3TransferUtilityDownloadTask *downloadTask = obj; + downloadTask.sessionTask = task; + [self.taskDictionary setObject:downloadTask forKey:@(downloadTask.taskIdentifier)]; + + //Remove this request from the transferRequests list. + [transferRequests removeObjectForKey:@(task.taskIdentifier)]; + + //Check if this is in progress + if ([downloadTask.status isEqualToString:AWSS3TransferUtilityInProgressStatus]) { + //Check if the underlying task's status is not in Progress. + if ( [task state] != NSURLSessionTaskStateRunning ) { + //We think the task in Progress. The underlying task is not in progress. + //Recover the situation by retrying + [self retryDownload:downloadTask]; + continue; + } + } + } + else { + AWSDDLogError(@"Object not found in taskDictionary for %lu",(unsigned long)task.taskIdentifier); + } + } + + //Finished iterating through the tasks present in the NSURLSession. + //If there are any left in the transferRequests list, it means that we think they are running, but NSURLSession doesn't know about them. + //We will ignore these tasks for now. + + //Call the completion handler if one was provided. + if (completionHandler) { + completionHandler(nil); + } + + }]; +} + + #pragma mark - Upload methods - (AWSTask *)uploadData:(NSData *)data @@ -279,9 +648,10 @@ - (instancetype)initWithConfiguration:(AWSServiceConfiguration *)serviceConfigur contentType:(NSString *)contentType expression:(AWSS3TransferUtilityUploadExpression *)expression completionHandler:(AWSS3TransferUtilityUploadCompletionHandlerBlock)completionHandler { + // Saves the data as a file in the temporary directory. NSString *fileName = [NSString stringWithFormat:@"%@.tmp", [[NSProcessInfo processInfo] globallyUniqueString]]; - NSString *filePath = [self.temporaryDirectoryPath stringByAppendingPathComponent:fileName]; + NSString *filePath = [self.cacheDirectoryPath stringByAppendingPathComponent:fileName]; NSURL *fileURL = [NSURL fileURLWithPath:filePath]; NSError *error = nil; @@ -293,16 +663,17 @@ - (instancetype)initWithConfiguration:(AWSServiceConfiguration *)serviceConfigur AWSS3TransferUtilityUploadTask *uploadTask = [AWSS3TransferUtilityUploadTask new]; uploadTask.bucket = bucket; uploadTask.key = key; - completionHandler(uploadTask, error); + completionHandler(uploadTask,error); } return [AWSTask taskWithError:error]; } - return [self uploadFile:fileURL + return [self internalUploadFile:fileURL bucket:bucket key:key contentType:contentType expression:expression + temporaryFileCreated:YES completionHandler:completionHandler]; } @@ -325,54 +696,68 @@ - (instancetype)initWithConfiguration:(AWSServiceConfiguration *)serviceConfigur contentType:(NSString *)contentType expression:(AWSS3TransferUtilityUploadExpression *)expression completionHandler:(AWSS3TransferUtilityUploadCompletionHandlerBlock)completionHandler { - if (!bucket || [bucket length] == 0) { - NSInteger errorCode = (self.transferUtilityConfiguration.isAccelerateModeEnabled) ? - AWSS3PresignedURLErrorInvalidBucketNameForAccelerateModeEnabled : AWSS3PresignedURLErrorInvalidBucketName; - - NSString *errorMessage = @"Invalid bucket specified. Please specify a bucket name or configure the bucket property in `AWSS3TransferUtilityConfiguration`."; - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMessage - forKey:NSLocalizedDescriptionKey]; - - return [AWSTask taskWithError:[NSError errorWithDomain:AWSS3PresignedURLErrorDomain - code:errorCode - userInfo:userInfo]]; - } - - NSString *filePath = [fileURL path]; + return [self internalUploadFile:fileURL + bucket:bucket + key:key + contentType:contentType + expression:expression + temporaryFileCreated:NO + completionHandler:completionHandler]; +} - // Error out if the length of file name < minimum file path length (2 characters) or file does not exist - if ([filePath length] < 2 || - ! [[NSFileManager defaultManager] fileExistsAtPath:filePath]) { - return [AWSTask taskWithError:[NSError errorWithDomain:AWSS3TransferUtilityErrorDomain - code:AWSS3TransferUtilityErrorLocalFileNotFound - userInfo:nil]]; +- (AWSTask *)internalUploadFile:(NSURL *)fileURL + bucket:(NSString *)bucket + key:(NSString *)key + contentType:(NSString *)contentType + expression:(AWSS3TransferUtilityUploadExpression *)expression + temporaryFileCreated: (BOOL) temporaryFileCreated + completionHandler:(AWSS3TransferUtilityUploadCompletionHandlerBlock)completionHandler { + //Validate input parameters. + AWSTask *error = [self validateParameters:bucket fileURL:fileURL accelerationModeEnabled:self.transferUtilityConfiguration.isAccelerateModeEnabled]; + if (error ) { + if (temporaryFileCreated ) { + [self removeFile:[fileURL path]]; + } + return error; } + //Create Expression if required and set it up if (!expression) { expression = [AWSS3TransferUtilityUploadExpression new]; } - - AWSS3TransferUtilityUploadTask *transferUtilityTask = [AWSS3TransferUtilityUploadTask new]; - transferUtilityTask.bucket = bucket; - transferUtilityTask.key = key; - [expression setValue:contentType forRequestHeader:@"Content-Type"]; - expression.completionHandler = completionHandler; - transferUtilityTask.expression = expression; + //Create TransferUtility Upload Task + AWSS3TransferUtilityUploadTask *transferUtilityUploadTask = [AWSS3TransferUtilityUploadTask new]; + transferUtilityUploadTask.nsURLSessionID = self.sessionIdentifier; + transferUtilityUploadTask.databaseQueue = self.databaseQueue; + transferUtilityUploadTask.bucket = bucket; + transferUtilityUploadTask.key = key; + transferUtilityUploadTask.retryCount = 0; + transferUtilityUploadTask.expression = expression; + transferUtilityUploadTask.transferID = [[NSUUID UUID] UUIDString]; + transferUtilityUploadTask.file = [fileURL path]; + transferUtilityUploadTask.cancelled = NO; + transferUtilityUploadTask.temporaryFileCreated = temporaryFileCreated; + transferUtilityUploadTask.responseData = @""; + + return [self createUploadTask:transferUtilityUploadTask]; +} + +-(AWSTask *) createUploadTask: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTask { + //Create PreSigned URL Request AWSS3GetPreSignedURLRequest *getPreSignedURLRequest = [AWSS3GetPreSignedURLRequest new]; - getPreSignedURLRequest.bucket = bucket; - getPreSignedURLRequest.key = key; + getPreSignedURLRequest.bucket = transferUtilityUploadTask.bucket; + getPreSignedURLRequest.key = transferUtilityUploadTask.key; getPreSignedURLRequest.HTTPMethod = AWSHTTPMethodPUT; getPreSignedURLRequest.expires = [NSDate dateWithTimeIntervalSinceNow:AWSS3TransferUtilityTimeoutIntervalForResource]; getPreSignedURLRequest.minimumCredentialsExpirationInterval = AWSS3TransferUtilityTimeoutIntervalForResource; getPreSignedURLRequest.accelerateModeEnabled = self.transferUtilityConfiguration.isAccelerateModeEnabled; - [expression assignRequestHeaders:getPreSignedURLRequest]; - [expression assignRequestParameters:getPreSignedURLRequest]; + [transferUtilityUploadTask.expression assignRequestHeaders:getPreSignedURLRequest]; + [transferUtilityUploadTask.expression assignRequestParameters:getPreSignedURLRequest]; - __weak AWSS3TransferUtility *weakSelf = self; return [[self.preSignedURLBuilder getPreSignedURL:getPreSignedURLRequest] continueWithSuccessBlock:^id(AWSTask *task) { NSURL *presignedURL = task.result; @@ -382,23 +767,313 @@ - (instancetype)initWithConfiguration:(AWSServiceConfiguration *)serviceConfigur [request setValue:self.configuration.userAgent forHTTPHeaderField:@"User-Agent"]; - for (NSString *key in expression.requestHeaders) { - [request setValue:expression.requestHeaders[key] forHTTPHeaderField:key]; + for (NSString *key in transferUtilityUploadTask.expression.requestHeaders) { + [request setValue: transferUtilityUploadTask.expression.requestHeaders[key] forHTTPHeaderField:key]; } - AWSDDLogDebug(@"Request headers:\n%@", request.allHTTPHeaderFields); - NSURLSessionUploadTask *uploadTask = [weakSelf.session uploadTaskWithRequest:request - fromFile:fileURL]; + NSURLSessionUploadTask *uploadTask = [self.session uploadTaskWithRequest:request + fromFile:[NSURL fileURLWithPath:transferUtilityUploadTask.file]]; + transferUtilityUploadTask.sessionTask = uploadTask; + + AWSDDLogDebug(@"Setting taskIdentifier to %@", @(transferUtilityUploadTask.sessionTask.taskIdentifier)); + + //Add to task Dictionary + [self.taskDictionary setObject:transferUtilityUploadTask forKey:@(transferUtilityUploadTask.sessionTask.taskIdentifier) ]; + + //Add to Database + [self insertUploadTransferRequestInDB:transferUtilityUploadTask databaseQueue:_databaseQueue]; [uploadTask resume]; + return [AWSTask taskWithResult:transferUtilityUploadTask]; + }]; +} + + +- (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTask { + //Remove from taskDictionary + [self.taskDictionary removeObjectForKey:@(transferUtilityUploadTask.taskIdentifier)]; + + //Remove from Database + [self deleteTransferRequestFromDB:transferUtilityUploadTask.transferID taskIdentifier:transferUtilityUploadTask.taskIdentifier databaseQueue:_databaseQueue ]; + + AWSDDLogDebug(@"Removed object from key %@", @(transferUtilityUploadTask.taskIdentifier) ); + transferUtilityUploadTask.retryCount = transferUtilityUploadTask.retryCount + 1; + + //This will update the AWSS3TransferUtilityUploadTask passed into it with a new URL Session + //task and add it into the task Dictionary. + [self createUploadTask:transferUtilityUploadTask]; +} + +#pragma mark - MultiPart Upload methods + +- (AWSTask *)uploadDataUsingMultiPart:(NSData *)data + key:(NSString *)key + contentType:(NSString *)contentType + expression:(AWSS3TransferUtilityMultiPartUploadExpression *)expression + completionHandler:(AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock)completionHandler { + return [self uploadDataUsingMultiPart:data + bucket:self.transferUtilityConfiguration.bucket + key:key + contentType:contentType + expression:expression + completionHandler:completionHandler]; +} + +- (AWSTask *)uploadDataUsingMultiPart:(NSData *)data + bucket:(NSString *)bucket + key:(NSString *)key + contentType:(NSString *)contentType + expression:(AWSS3TransferUtilityMultiPartUploadExpression *)expression + completionHandler:(AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock)completionHandler { + + // Saves the data as a file in the temporary directory. + NSString *fileName = [NSString stringWithFormat:@"%@.tmp", [[NSProcessInfo processInfo] globallyUniqueString]]; + NSString *filePath = [self.cacheDirectoryPath stringByAppendingPathComponent:fileName]; + NSURL *fileURL = [NSURL fileURLWithPath:filePath]; + + NSError *error = nil; + BOOL result = [data writeToURL:fileURL + options:NSDataWritingAtomic + error:&error]; + if (!result) { + if (completionHandler) { + AWSS3TransferUtilityMultiPartUploadTask *uploadTask = [AWSS3TransferUtilityMultiPartUploadTask new]; + uploadTask.bucket = bucket; + uploadTask.key = key; + completionHandler(uploadTask, error); + } + return [AWSTask taskWithError:error]; + } + + return [self internalUploadFileUsingMultiPart:fileURL + bucket:bucket + key:key + contentType:contentType + expression:expression + temporaryFileCreated:YES + completionHandler:completionHandler]; +} + +- (AWSTask *)uploadFileUsingMultiPart:(NSURL *)fileURL + key:(NSString *)key + contentType:(NSString *)contentType + expression:(AWSS3TransferUtilityMultiPartUploadExpression *)expression + completionHandler:(AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock)completionHandler { + return [self uploadFileUsingMultiPart:fileURL + bucket:self.transferUtilityConfiguration.bucket + key:key + contentType:contentType + expression:expression + completionHandler:completionHandler]; +} + +- (AWSTask *)uploadFileUsingMultiPart:(NSURL *)fileURL + bucket:(NSString *)bucket + key:(NSString *)key + contentType:(NSString *)contentType + expression:(AWSS3TransferUtilityMultiPartUploadExpression *)expression + completionHandler:(AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock) completionHandler +{ + return [self internalUploadFileUsingMultiPart:fileURL + bucket:bucket + key:key + contentType:contentType + expression:expression + temporaryFileCreated:NO + completionHandler:completionHandler]; +} + +- (AWSTask *)internalUploadFileUsingMultiPart:(NSURL *)fileURL + bucket:(NSString *)bucket + key:(NSString *)key + contentType:(NSString *)contentType + expression:(AWSS3TransferUtilityMultiPartUploadExpression *)expression + temporaryFileCreated: (BOOL) temporaryFileCreated + completionHandler:(AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock) completionHandler { + + //Validate input parameters. + AWSTask *error = [self validateParameters:bucket fileURL:fileURL accelerationModeEnabled:self.transferUtilityConfiguration.isAccelerateModeEnabled]; + if (error ) { + if (temporaryFileCreated) { + [self removeFile:[fileURL path]]; + } + return error; + } + + //Create Expression if required and set values on the object + if (!expression) { + expression = [AWSS3TransferUtilityMultiPartUploadExpression new]; + } + [expression setValue:contentType forRequestHeader:@"Content-Type"]; + expression.completionHandler = completionHandler; + + //Create TransferUtility Multipart Upload Task + AWSS3TransferUtilityMultiPartUploadTask *transferUtilityMultiPartUploadTask = [AWSS3TransferUtilityMultiPartUploadTask new]; + transferUtilityMultiPartUploadTask.nsURLSessionID = self.sessionIdentifier; + transferUtilityMultiPartUploadTask.databaseQueue = self.databaseQueue; + transferUtilityMultiPartUploadTask.bucket = bucket; + transferUtilityMultiPartUploadTask.key = key; + transferUtilityMultiPartUploadTask.expression = expression; + transferUtilityMultiPartUploadTask.transferID = [[NSUUID UUID] UUIDString]; + transferUtilityMultiPartUploadTask.file = [fileURL path]; + transferUtilityMultiPartUploadTask.retryCount = 0; + transferUtilityMultiPartUploadTask.temporaryFileCreated = temporaryFileCreated; + + //Get the size of the file and calculate the number of parts. + NSError *nsError = nil; + NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] + error:&nsError]; + if (!attributes) { + if (transferUtilityMultiPartUploadTask.temporaryFileCreated) { + [self removeFile:transferUtilityMultiPartUploadTask.file]; + } + return [AWSTask taskWithError:nsError]; + } + unsigned long long fileSize = [attributes fileSize]; + AWSDDLogDebug(@"File size is %llu", fileSize); + NSUInteger partCount = ceil((float)fileSize /(unsigned long) AWSS3TransferUtilityMultiPartSize); + AWSDDLogDebug(@"Number of parts is %lu", (unsigned long) partCount); + transferUtilityMultiPartUploadTask.progress.totalUnitCount = fileSize; + transferUtilityMultiPartUploadTask.progress.completedUnitCount = (long long) 0; + transferUtilityMultiPartUploadTask.cancelled = NO; + transferUtilityMultiPartUploadTask.contentLength = [ [NSNumber alloc] initWithUnsignedLongLong:fileSize]; + + //Create the initial request to start the multipart process. + AWSS3CreateMultipartUploadRequest *uploadRequest = [AWSS3CreateMultipartUploadRequest new]; + + uploadRequest.bucket = bucket; + uploadRequest.key = key; + + //Initiate the multi part + return [[self.s3 createMultipartUpload:uploadRequest] continueWithBlock:^id(AWSTask *task) { + //Initiation of multi part failed. + if (task.error) { + if (transferUtilityMultiPartUploadTask.temporaryFileCreated) { + [self removeFile:transferUtilityMultiPartUploadTask.file]; + } + return [AWSTask taskWithResult:self]; + } + //Get the uploadID. This will be used with every part that we will upload. + AWSS3CreateMultipartUploadOutput *output = task.result; + transferUtilityMultiPartUploadTask.uploadID = output.uploadId; + + //Save the Multipart Upload in the DB + [self insertMultiPartUploadRequestInDB:transferUtilityMultiPartUploadTask databaseQueue:_databaseQueue]; + + AWSDDLogInfo(@"Initiated multipart upload on server: %@", output.uploadId); + AWSDDLogInfo(@"Concurrency Limit is %@", self.transferUtilityConfiguration.multiPartConcurrencyLimit); + //Loop through the file and upload the parts one by one + for (int32_t i = 1; i < partCount + 1; i++) { + NSUInteger dataLength = AWSS3TransferUtilityMultiPartSize; + if ( i == partCount) { + dataLength = fileSize - ( (i-1) * AWSS3TransferUtilityMultiPartSize); + } + + //Create a temporary file for this part. + NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:[fileURL path]]; + [fileHandle seekToFileOffset:(i - 1) * AWSS3TransferUtilityMultiPartSize]; + NSData *partData = [fileHandle readDataOfLength:dataLength]; + NSString *file = [self.cacheDirectoryPath stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]]; + NSURL *tempURL = [NSURL fileURLWithPath:file]; + [partData writeToURL:tempURL atomically:YES]; + partData = nil; + [fileHandle closeFile]; + + AWSS3TransferUtilityUploadSubTask *subTask = [AWSS3TransferUtilityUploadSubTask new]; + subTask.transferID = transferUtilityMultiPartUploadTask.transferID; + subTask.partNumber = @(i); + subTask.totalBytesExpectedToSend = dataLength; + subTask.totalBytesSent = (long long) 0; + subTask.file = file; + subTask.responseData = @""; + //Move the waitingParts to inProgress based on concurrency limit + if ( i <= [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue] ) { + subTask.status = AWSS3TransferUtilityInProgressStatus; + [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:YES]; + } + else { + subTask.status = AWSS3TransferUtilityWaitingStatus; + [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:NO]; + } + + } + if (transferUtilityMultiPartUploadTask.temporaryFileCreated) { + [self removeFile:transferUtilityMultiPartUploadTask.file]; + } + return [AWSTask taskWithResult:transferUtilityMultiPartUploadTask]; + }]; + return [AWSTask taskWithResult:transferUtilityMultiPartUploadTask]; +} + +-(void) createUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *) transferUtilityMultiPartUploadTask + subTask: (AWSS3TransferUtilityUploadSubTask *) subTask + startTransfer: (BOOL) startTransfer +{ + //Create a presignedURL for this part. + AWSS3GetPreSignedURLRequest *request = [AWSS3GetPreSignedURLRequest new]; + request.bucket = transferUtilityMultiPartUploadTask.bucket; + request.key = transferUtilityMultiPartUploadTask.key; + request.partNumber = subTask.partNumber; + request.uploadID = transferUtilityMultiPartUploadTask.uploadID; + request.HTTPMethod = AWSHTTPMethodPUT; + + request.expires = [NSDate dateWithTimeIntervalSinceNow:AWSS3TransferUtilityTimeoutIntervalForResource]; + request.minimumCredentialsExpirationInterval = AWSS3TransferUtilityTimeoutIntervalForResource; + request.accelerateModeEnabled = self.transferUtilityConfiguration.isAccelerateModeEnabled; + + [transferUtilityMultiPartUploadTask.expression assignRequestHeaders:request]; + [transferUtilityMultiPartUploadTask.expression assignRequestParameters:request]; + + [[self.preSignedURLBuilder getPreSignedURL:request] continueWithSuccessBlock:^id(AWSTask *task) { + NSURL *presignedURL = task.result; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:presignedURL]; + request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData; + request.HTTPMethod = @"PUT"; + [request setValue:[self.configuration.userAgent stringByAppendingString:@" MultiPart"] forHTTPHeaderField:@"User-Agent"]; + + for (NSString *key in transferUtilityMultiPartUploadTask.expression.requestHeaders) { + [request setValue:transferUtilityMultiPartUploadTask.expression.requestHeaders[key] forHTTPHeaderField:key]; + } + + NSURLSessionUploadTask *nsURLUploadTask = [_session uploadTaskWithRequest:request + fromFile:[NSURL fileURLWithPath:subTask.file]]; + //Create subtask to track this upload + subTask.sessionTask = nsURLUploadTask; + subTask.taskIdentifier = nsURLUploadTask.taskIdentifier; + + if (startTransfer) { + [transferUtilityMultiPartUploadTask.inProgressPartsDictionary setObject:subTask forKey:@(subTask.taskIdentifier)]; + [nsURLUploadTask resume]; + AWSDDLogDebug(@"Upload started %lu", (unsigned long)nsURLUploadTask.taskIdentifier); + } + else { + //Put the subtask into the waiting parts dictionary, with uploadTask as the key. + [transferUtilityMultiPartUploadTask.waitingPartsDictionary setObject:subTask forKey:@(subTask.taskIdentifier)]; + } - transferUtilityTask.sessionTask = uploadTask; - [weakSelf.taskDictionary setObject:transferUtilityTask - forKey:@(transferUtilityTask.taskIdentifier)]; - return [AWSTask taskWithResult:transferUtilityTask]; + //Also register transferUtilityMultiPartUploadTask into the taskDictionary for easy lookup in the NSURLCallback + [_taskDictionary setObject:transferUtilityMultiPartUploadTask forKey:@(subTask.taskIdentifier)]; + + //Save in Database + [self insertMultiPartUploadRequestSubTaskInDB:transferUtilityMultiPartUploadTask subTask:subTask databaseQueue:self.databaseQueue]; + return nil; }]; } +-(void) retryUploadSubTask: (AWSS3TransferUtilityMultiPartUploadTask *) transferUtilityMultiPartUploadTask + subTask: (AWSS3TransferUtilityUploadSubTask *) subTask { + + //Remove from TaskDictionary and inProgressPartsDictionary + [self.taskDictionary removeObjectForKey:@(subTask.taskIdentifier)]; + [transferUtilityMultiPartUploadTask.inProgressPartsDictionary removeObjectForKey:@(subTask.taskIdentifier)]; + + //Remove subTask from Database + [self deleteTransferRequestFromDB:subTask.transferID taskIdentifier:subTask.taskIdentifier databaseQueue:_databaseQueue]; + + transferUtilityMultiPartUploadTask.retryCount = transferUtilityMultiPartUploadTask.retryCount + 1; + [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:YES]; +} + #pragma mark - Download methods - (AWSTask *)downloadDataForKey:(NSString *)key @@ -450,6 +1125,7 @@ - (instancetype)initWithConfiguration:(AWSServiceConfiguration *)serviceConfigur key:(NSString *)key expression:(AWSS3TransferUtilityDownloadExpression *)expression completionHandler:(AWSS3TransferUtilityDownloadCompletionHandlerBlock)completionHandler { + //Validate that bucket has been specified. if (!bucket || [bucket length] == 0) { NSInteger errorCode = (self.transferUtilityConfiguration.isAccelerateModeEnabled) ? AWSS3PresignedURLErrorInvalidBucketNameForAccelerateModeEnabled : AWSS3PresignedURLErrorInvalidBucketName; @@ -462,29 +1138,41 @@ - (instancetype)initWithConfiguration:(AWSServiceConfiguration *)serviceConfigur userInfo:userInfo]]; } + //Create Expression if required and set completion Handler. if (!expression) { expression = [AWSS3TransferUtilityDownloadExpression new]; } - - AWSS3TransferUtilityDownloadTask *transferUtilityTask = [AWSS3TransferUtilityDownloadTask new]; - transferUtilityTask.location = fileURL; - transferUtilityTask.bucket = bucket; - transferUtilityTask.key = key; - expression.completionHandler = completionHandler; - transferUtilityTask.expression = expression; + //Create Download Task and set it up. + AWSS3TransferUtilityDownloadTask *transferUtilityDownloadTask = [AWSS3TransferUtilityDownloadTask new]; + transferUtilityDownloadTask.nsURLSessionID = self.sessionIdentifier; + transferUtilityDownloadTask.databaseQueue = self.databaseQueue; + transferUtilityDownloadTask.location = fileURL; + transferUtilityDownloadTask.bucket = bucket; + transferUtilityDownloadTask.key = key; + transferUtilityDownloadTask.expression = expression; + transferUtilityDownloadTask.transferID = [[NSUUID UUID] UUIDString]; + transferUtilityDownloadTask.file = [fileURL absoluteString]; + transferUtilityDownloadTask.cancelled = NO; + transferUtilityDownloadTask.retryCount = 0; + transferUtilityDownloadTask.responseData = @""; + + return [self createDownloadTask:transferUtilityDownloadTask]; +} + +-(AWSTask *) createDownloadTask: (AWSS3TransferUtilityDownloadTask *) transferUtilityDownloadTask { AWSS3GetPreSignedURLRequest *getPreSignedURLRequest = [AWSS3GetPreSignedURLRequest new]; - getPreSignedURLRequest.bucket = bucket; - getPreSignedURLRequest.key = key; + getPreSignedURLRequest.bucket = transferUtilityDownloadTask.bucket; + getPreSignedURLRequest.key = transferUtilityDownloadTask.key; getPreSignedURLRequest.HTTPMethod = AWSHTTPMethodGET; getPreSignedURLRequest.expires = [NSDate dateWithTimeIntervalSinceNow:AWSS3TransferUtilityTimeoutIntervalForResource]; + getPreSignedURLRequest.minimumCredentialsExpirationInterval = AWSS3TransferUtilityTimeoutIntervalForResource; //Was there in upload but not in download getPreSignedURLRequest.accelerateModeEnabled = self.transferUtilityConfiguration.isAccelerateModeEnabled; - [expression assignRequestHeaders:getPreSignedURLRequest]; - [expression assignRequestParameters:getPreSignedURLRequest]; + [transferUtilityDownloadTask.expression assignRequestHeaders:getPreSignedURLRequest]; + [transferUtilityDownloadTask.expression assignRequestParameters:getPreSignedURLRequest]; - __weak AWSS3TransferUtility *weakSelf = self; return [[self.preSignedURLBuilder getPreSignedURL:getPreSignedURLRequest] continueWithSuccessBlock:^id(AWSTask *task) { NSURL *presignedURL = task.result; @@ -494,193 +1182,225 @@ - (instancetype)initWithConfiguration:(AWSServiceConfiguration *)serviceConfigur [request setValue:[AWSServiceConfiguration baseUserAgent] forHTTPHeaderField:@"User-Agent"]; - for (NSString *key in expression.requestHeaders) { - [request setValue:expression.requestHeaders[key] forHTTPHeaderField:key]; + for (NSString *key in transferUtilityDownloadTask.expression.requestHeaders) { + [request setValue:transferUtilityDownloadTask.expression.requestHeaders[key] forHTTPHeaderField:key]; } AWSDDLogDebug(@"Request headers:\n%@", request.allHTTPHeaderFields); - NSURLSessionDownloadTask *downloadTask = [weakSelf.session downloadTaskWithRequest:request]; - [downloadTask resume]; + NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithRequest:request]; + transferUtilityDownloadTask.sessionTask = downloadTask; + transferUtilityDownloadTask.retryCount = 0; - transferUtilityTask.sessionTask = downloadTask; - [weakSelf.taskDictionary setObject:transferUtilityTask - forKey:@(transferUtilityTask.taskIdentifier)]; - return [AWSTask taskWithResult:transferUtilityTask]; + AWSDDLogDebug(@"Setting taskIdentifier to %@", @(transferUtilityDownloadTask.sessionTask.taskIdentifier)); + + //Add to taskDictionary + [self.taskDictionary setObject:transferUtilityDownloadTask forKey:@(transferUtilityDownloadTask.sessionTask.taskIdentifier) ]; + + //Add to Database + [self insertDownloadTransferRequestInDB:transferUtilityDownloadTask databaseQueue:_databaseQueue]; + [downloadTask resume]; + return [AWSTask taskWithResult:transferUtilityDownloadTask]; }]; } -#pragma mark - Utility methods - -- (void)enumerateToAssignBlocksForUploadTask:(void (^)(AWSS3TransferUtilityUploadTask *uploadTask, - AWSS3TransferUtilityProgressBlock *uploadProgressBlockReference, - AWSS3TransferUtilityUploadCompletionHandlerBlock *completionHandlerReference))uploadBlocksAssigner +- (void) retryDownload: (AWSS3TransferUtilityDownloadTask *) transferUtilityDownloadTask { + + //Remove from taskDictionary + [self.taskDictionary removeObjectForKey:@(transferUtilityDownloadTask.sessionTask.taskIdentifier)]; + + //Remove from Database + [self deleteTransferRequestFromDB:transferUtilityDownloadTask.transferID taskIdentifier:transferUtilityDownloadTask.sessionTask.taskIdentifier + databaseQueue:_databaseQueue]; + + AWSDDLogDebug(@"Removed object from key %@", @(transferUtilityDownloadTask.sessionTask.taskIdentifier) ); + transferUtilityDownloadTask.retryCount = transferUtilityDownloadTask.retryCount + 1; + + //This will update the AWSS3TransferUtilityDownloadTask passed into it with a new URL Session + //task and add it into the task Dictionary. + [self createDownloadTask:transferUtilityDownloadTask]; +} + +#pragma mark - Utility methods + +- (void)enumerateToAssignBlocksForUploadTask:(void (^)(AWSS3TransferUtilityUploadTask *uploadTask, + AWSS3TransferUtilityProgressBlock *uploadProgressBlockReference, + AWSS3TransferUtilityUploadCompletionHandlerBlock *completionHandlerReference))uploadBlocksAssigner downloadTask:(void (^)(AWSS3TransferUtilityDownloadTask *downloadTask, AWSS3TransferUtilityProgressBlock *downloadProgressBlockReference, AWSS3TransferUtilityDownloadCompletionHandlerBlock *completionHandlerReference))downloadBlocksAssigner { - __weak AWSS3TransferUtility *weakSelf = self; - [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { - if ([dataTasks count] != 0) { - AWSDDLogError(@"The underlying NSURLSession contains data tasks. This should not happen."); + + // Iterate through Tasks + for (id key in [self.taskDictionary allKeys]) { + id value = [self.taskDictionary objectForKey:key]; + if ( [value isKindOfClass:[AWSS3TransferUtilityUploadTask class]]) { + AWSS3TransferUtilityUploadTask *transferUtilityUploadTask = value; + if (uploadBlocksAssigner) { + AWSS3TransferUtilityProgressBlock progressBlock = nil; + AWSS3TransferUtilityUploadCompletionHandlerBlock completionHandler = nil; + + uploadBlocksAssigner(transferUtilityUploadTask, &progressBlock, &completionHandler); + + if (progressBlock) { + transferUtilityUploadTask.expression.progressBlock = progressBlock; + } + if (completionHandler) { + transferUtilityUploadTask.expression.completionHandler = completionHandler; + } + } } - - [uploadTasks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - NSURLSessionUploadTask *uploadTask = obj; - AWSS3TransferUtilityUploadTask *transferUtilityUploadTask = [weakSelf getUploadTask:uploadTask]; - if (transferUtilityUploadTask) { - if (uploadBlocksAssigner) { - AWSS3TransferUtilityProgressBlock progressBlock = nil; - AWSS3TransferUtilityUploadCompletionHandlerBlock completionHandler = nil; - - uploadBlocksAssigner(transferUtilityUploadTask, &progressBlock, &completionHandler); - - if (progressBlock) { - transferUtilityUploadTask.expression.progressBlock = progressBlock; - } - if (completionHandler) { - transferUtilityUploadTask.expression.completionHandler = completionHandler; - } + else if ([value isKindOfClass:[AWSS3TransferUtilityDownloadTask class]]) { + AWSS3TransferUtilityDownloadTask *transferUtilityDownloadTask = value; + if (downloadBlocksAssigner) { + AWSS3TransferUtilityProgressBlock progressBlock = nil; + AWSS3TransferUtilityDownloadCompletionHandlerBlock completionHandler = nil; + + downloadBlocksAssigner(transferUtilityDownloadTask, &progressBlock, &completionHandler); + + if (progressBlock) { + transferUtilityDownloadTask.expression.progressBlock = progressBlock; + } + if (completionHandler) { + transferUtilityDownloadTask.expression.completionHandler = completionHandler; } } - }]; - - [downloadTasks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - NSURLSessionDownloadTask *downloadTask = obj; - AWSS3TransferUtilityDownloadTask *transferUtilityDownloadTask = [weakSelf getDownloadTask:downloadTask]; - if (transferUtilityDownloadTask) { - if (downloadBlocksAssigner) { - AWSS3TransferUtilityProgressBlock progressBlock = nil; - AWSS3TransferUtilityDownloadCompletionHandlerBlock completionHandler = nil; - - downloadBlocksAssigner(transferUtilityDownloadTask, &progressBlock, &completionHandler); - - if (progressBlock) { - transferUtilityDownloadTask.expression.progressBlock = progressBlock; - } - if (completionHandler) { - transferUtilityDownloadTask.expression.completionHandler = completionHandler; - } + } + } +} + +-(void)enumerateToAssignBlocksForUploadTask:(void (^)(AWSS3TransferUtilityUploadTask *uploadTask, + AWSS3TransferUtilityProgressBlock *uploadProgressBlockReference, + AWSS3TransferUtilityUploadCompletionHandlerBlock *completionHandlerReference))uploadBlocksAssigner + multiPartUploadBlocksAssigner: (void (^) (AWSS3TransferUtilityMultiPartUploadTask *multiPartUploadTask, + AWSS3TransferUtilityMultiPartProgressBlock *multiPartUploadProgressBlockReference, + AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock *completionHandlerReference)) multiPartUploadBlocksAssigner + downloadBlocksAssigner:(void (^)(AWSS3TransferUtilityDownloadTask *downloadTask, + AWSS3TransferUtilityProgressBlock *downloadProgressBlockReference, + AWSS3TransferUtilityDownloadCompletionHandlerBlock *completionHandlerReference))downloadBlocksAssigner { + + [self enumerateToAssignBlocksForUploadTask:uploadBlocksAssigner downloadTask:downloadBlocksAssigner]; + + // Iterate through MultiPartUploadTasks + for (id key in [self.taskDictionary allKeys]) { + id value = [self.taskDictionary objectForKey:key]; + if ( [value isKindOfClass:[AWSS3TransferUtilityMultiPartUploadTask class]]) { + AWSS3TransferUtilityMultiPartUploadTask *task = value; + if (multiPartUploadBlocksAssigner) { + AWSS3TransferUtilityMultiPartProgressBlock progressBlock = nil; + AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock completionHandler = nil; + multiPartUploadBlocksAssigner(task, &progressBlock, &completionHandler); + if (progressBlock) { + task.expression.progressBlock = progressBlock; + } + if (completionHandler) { + task.expression.completionHandler = completionHandler; } } - }]; - }]; - + } + } } + - (AWSTask *)getAllTasks { AWSTaskCompletionSource *completionSource = [AWSTaskCompletionSource new]; - - NSMutableArray<__kindof AWSS3TransferUtilityTask *> *allTasks = [NSMutableArray new]; - __weak AWSS3TransferUtility *weakSelf = self; - [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { - if ([dataTasks count] != 0) { - AWSDDLogError(@"The underlying NSURLSession contains data tasks. This should not happen."); + NSMutableArray *allTasks = [NSMutableArray new]; + for (id key in [self.taskDictionary allKeys]) { + id value = [self.taskDictionary objectForKey:key]; + //To retain backward compatability, make sure that we filter out Multipart tasks. + if ( ! [value isKindOfClass:[AWSS3TransferUtilityMultiPartUploadTask class]]) { + [allTasks addObject:value]; } - - [uploadTasks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - NSURLSessionUploadTask *uploadTask = obj; - AWSS3TransferUtilityUploadTask *transferUtilityUploadTask = [weakSelf getUploadTask:uploadTask]; - if (transferUtilityUploadTask) { - [allTasks addObject:transferUtilityUploadTask]; - } - }]; - - [downloadTasks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - NSURLSessionDownloadTask *downloadTask = obj; - AWSS3TransferUtilityDownloadTask *transferUtilityDownloadTask = [weakSelf getDownloadTask:downloadTask]; - if (transferUtilityDownloadTask) { - [allTasks addObject:transferUtilityDownloadTask]; - } - }]; - - [completionSource setResult:allTasks]; - }]; - + [allTasks addObject:[self.taskDictionary objectForKey:key]]; + } + [completionSource setResult:allTasks]; return completionSource.task; } - (AWSTask *)getUploadTasks { AWSTaskCompletionSource *completionSource = [AWSTaskCompletionSource new]; + NSMutableArray *allTasks = [NSMutableArray new]; - NSMutableArray *allUploadTasks = [NSMutableArray new]; - __weak AWSS3TransferUtility *weakSelf = self; - [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { - if ([dataTasks count] != 0) { - AWSDDLogError(@"The underlying NSURLSession contains data tasks. This should not happen."); + for (id key in [self.taskDictionary allKeys]) { + id value = [self.taskDictionary objectForKey:key]; + if ( [value isKindOfClass:[AWSS3TransferUtilityUploadTask class]]) { + [allTasks addObject:value]; } - - [uploadTasks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - NSURLSessionUploadTask *uploadTask = obj; - AWSS3TransferUtilityUploadTask *transferUtilityUploadTask = [weakSelf getUploadTask:uploadTask]; - if (transferUtilityUploadTask) { - [allUploadTasks addObject:transferUtilityUploadTask]; - } - }]; - - [completionSource setResult:allUploadTasks]; - }]; - + } + [completionSource setResult:allTasks]; return completionSource.task; } - (AWSTask *)getDownloadTasks { AWSTaskCompletionSource *completionSource = [AWSTaskCompletionSource new]; + NSMutableArray *allTasks = [NSMutableArray new]; - NSMutableArray *allDownloadTasks = [NSMutableArray new]; - __weak AWSS3TransferUtility *weakSelf = self; - [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { - if ([dataTasks count] != 0) { - AWSDDLogError(@"The underlying NSURLSession contains data tasks. This should not happen."); + for (id key in [self.taskDictionary allKeys]) { + id value = [self.taskDictionary objectForKey:key]; + if ( [value isKindOfClass:[AWSS3TransferUtilityDownloadTask class]]) { + [allTasks addObject:value]; } - - [downloadTasks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - NSURLSessionDownloadTask *downloadTask = obj; - AWSS3TransferUtilityDownloadTask *transferUtilityDownloadTask = [weakSelf getDownloadTask:downloadTask]; - if (transferUtilityDownloadTask) { - [allDownloadTasks addObject:transferUtilityDownloadTask]; - } - }]; - - [completionSource setResult:allDownloadTasks]; - }]; - + } + [completionSource setResult:allTasks]; return completionSource.task; } -- (void)cleanUpTemporaryDirectory { - NSError *error = nil; - NSArray *contentsOfDirectory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.temporaryDirectoryPath - error:&error]; - if (!contentsOfDirectory) { - AWSDDLogError(@"Failed to retrieve the contents of the tempoprary directory: %@", error); - } +- (AWSTask *)getMultiPartUploadTasks { + AWSTaskCompletionSource *completionSource = [AWSTaskCompletionSource new]; + NSMutableArray *allTasks = [NSMutableArray new]; - // Goes through the temporary directory. - __weak AWSS3TransferUtility *weakSelf = self; - [contentsOfDirectory enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - NSString *fileName = (NSString *)obj; - NSString *filePath = [weakSelf.temporaryDirectoryPath stringByAppendingPathComponent:fileName]; - NSError *error = nil; - NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath - error:&error]; - if (!attributes) { - AWSDDLogError(@"Failed to load temporary file attributes: %@", error); - } - NSDate *fileCreationDate = [attributes objectForKey:NSFileCreationDate]; - // Removes an 'expired' temporary file. - // Adds 60 seconds buffer to detemine if a file was 'expired'. e.g. Removes files older than 51 minutes old. - if ([fileCreationDate timeIntervalSince1970] < [[NSDate date] timeIntervalSince1970] - AWSS3TransferUtilityTimeoutIntervalForResource - 60) { - BOOL result = [[NSFileManager defaultManager] removeItemAtPath:filePath - error:&error]; - if (!result) { - AWSDDLogError(@"Failed to remove a temporary file: %@", error); - } + for (id key in [self.taskDictionary allKeys]) { + id value = [self.taskDictionary objectForKey:key]; + if ( [value isKindOfClass:[AWSS3TransferUtilityMultiPartUploadTask class]]) { + [allTasks addObject:value]; } - }]; + } + [completionSource setResult:allTasks]; + return completionSource.task; } + #pragma mark - Internal helper methods +- (AWSTask *)callFinishMultiPartForUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)uploadTask { + + NSMutableArray *completedParts = [NSMutableArray arrayWithCapacity:[uploadTask.completedPartsDictionary count]]; + NSMutableDictionary *tempDictionary = [NSMutableDictionary new]; + + //Create a new Dictionary with the partNumber as the Key + for(id key in uploadTask.completedPartsDictionary) { + AWSS3TransferUtilityUploadSubTask *subTask = [uploadTask.completedPartsDictionary objectForKey:key]; + [tempDictionary setObject:subTask forKey:subTask.partNumber]; + } + + //Compose the request. + for(int i = 1; i <= [uploadTask.completedPartsDictionary count]; i++) { + AWSS3TransferUtilityUploadSubTask *subTask = [tempDictionary objectForKey: [NSNumber numberWithInt:i]]; + AWSS3CompletedPart *completedPart = [AWSS3CompletedPart new]; + completedPart.partNumber = subTask.partNumber; + completedPart.ETag = subTask.eTag; + [completedParts addObject:completedPart]; + } + + AWSS3CompletedMultipartUpload *multipartUpload = [AWSS3CompletedMultipartUpload new]; + multipartUpload.parts = completedParts; + + AWSS3CompleteMultipartUploadRequest *compReq = [AWSS3CompleteMultipartUploadRequest new]; + compReq.bucket = uploadTask.bucket; + compReq.key = uploadTask.key; + compReq.uploadId = uploadTask.uploadID; + compReq.multipartUpload = multipartUpload; + + return [self.s3 completeMultipartUpload:compReq]; +} + +- (AWSTask *) callAbortMultiPartForUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *) uploadTask { + AWSS3AbortMultipartUploadRequest *abortReq = [AWSS3AbortMultipartUploadRequest new]; + abortReq.bucket = uploadTask.bucket; + abortReq.uploadId = uploadTask.uploadID; + abortReq.key = uploadTask.key; + return [self.s3 abortMultipartUpload:abortReq]; +} + - (AWSS3TransferUtilityUploadTask *)getUploadTask:(NSURLSessionUploadTask *)uploadTask { if (![uploadTask isKindOfClass:[NSURLSessionUploadTask class]]) { AWSDDLogError(@"uploadTask is not an instance of NSURLSessionUploadTask."); @@ -689,7 +1409,10 @@ - (AWSS3TransferUtilityUploadTask *)getUploadTask:(NSURLSessionUploadTask *)uplo AWSS3TransferUtilityUploadTask *transferUtilityUploadTask = [self.taskDictionary objectForKey:@(uploadTask.taskIdentifier)]; if (!transferUtilityUploadTask) { + AWSDDLogDebug(@"Unable to find TransferUtilityUploadTask for key %@ ", @(uploadTask.taskIdentifier)); + AWSDDLogDebug(@"Creating new object"); transferUtilityUploadTask = [AWSS3TransferUtilityUploadTask new]; + transferUtilityUploadTask.responseData = @""; transferUtilityUploadTask.sessionTask = uploadTask; [self.taskDictionary setObject:transferUtilityUploadTask @@ -755,44 +1478,189 @@ - (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *) - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { - if (!error) { - NSAssert([task.response isKindOfClass:[NSHTTPURLResponse class]], @"Expected response of type NSHTTPURLResponse"); + + NSHTTPURLResponse *HTTPResponse = nil; + NSMutableDictionary *userInfo = nil; + + if (!error ) { if (![task.response isKindOfClass:[NSHTTPURLResponse class]]) { error = [NSError errorWithDomain:AWSS3TransferUtilityErrorDomain code:AWSS3TransferUtilityErrorUnknown userInfo:nil]; } - - NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)task.response; - NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[HTTPResponse allHeaderFields]]; - if (HTTPResponse.statusCode / 100 == 3 - && HTTPResponse.statusCode != 304) { // 304 Not Modified is a valid response. - error = [NSError errorWithDomain:AWSS3TransferUtilityErrorDomain - code:AWSS3TransferUtilityErrorRedirection - userInfo:userInfo]; + else { + HTTPResponse = (NSHTTPURLResponse *) task.response; + userInfo = [NSMutableDictionary dictionaryWithDictionary:[HTTPResponse allHeaderFields]]; } - - if (HTTPResponse.statusCode / 100 == 4) { - error = [NSError errorWithDomain:AWSS3TransferUtilityErrorDomain - code:AWSS3TransferUtilityErrorClientError - userInfo:userInfo]; - } - - if (HTTPResponse.statusCode / 100 == 5) { - error = [NSError errorWithDomain:AWSS3TransferUtilityErrorDomain - code:AWSS3TransferUtilityErrorServerError - userInfo:userInfo]; + + if (!error) { + if (HTTPResponse.statusCode / 100 == 3 + && HTTPResponse.statusCode != 304) { // 304 Not Modified is a valid response. + error = [NSError errorWithDomain:AWSS3TransferUtilityErrorDomain + code:AWSS3TransferUtilityErrorRedirection + userInfo:userInfo]; + } + + if (HTTPResponse.statusCode / 100 == 4) { + error = [NSError errorWithDomain:AWSS3TransferUtilityErrorDomain + code:AWSS3TransferUtilityErrorClientError + userInfo:userInfo]; + } + + if (HTTPResponse.statusCode / 100 == 5) { + error = [NSError errorWithDomain:AWSS3TransferUtilityErrorDomain + code:AWSS3TransferUtilityErrorServerError + userInfo:userInfo]; + } } } - if ([task isKindOfClass:[NSURLSessionUploadTask class]]) { - AWSS3TransferUtilityUploadTask *uploadTask = [self getUploadTask:(NSURLSessionUploadTask *)task]; - if (uploadTask.expression.completionHandler) { - uploadTask.expression.completionHandler(uploadTask, - error); + + if( [task isKindOfClass:[NSURLSessionUploadTask class]]) { + + AWSS3TransferUtilityTask *transferUtilityTask = [self.taskDictionary objectForKey:@(task.taskIdentifier)]; + + if ([transferUtilityTask isKindOfClass:[AWSS3TransferUtilityUploadTask class]]) { + AWSS3TransferUtilityUploadTask *uploadTask = [self getUploadTask:(NSURLSessionUploadTask *)task]; + + //Check if the task was cancelled. + if ( uploadTask.cancelled ) { + [self cleanupForUploadTask:uploadTask]; + return; + } + + if (error && HTTPResponse) { + if ([self isErrorRetriable:HTTPResponse.statusCode responseFromServer:uploadTask.responseData] ) { + AWSDDLogDebug(@"Received a 500, 503 or 400 error. Response Data is [%@]", uploadTask.responseData ); + if (uploadTask.retryCount < self.transferUtilityConfiguration.retryLimit ) { + AWSDDLogDebug(@"Retry count is below limit and error is retriable. "); + [self retryUpload:uploadTask]; + return; + } + } + uploadTask.error = error; + } + + if(uploadTask.expression.completionHandler) { + uploadTask.expression.completionHandler(uploadTask,error); + } + [self cleanupForUploadTask:uploadTask]; + return; + } + else if ([transferUtilityTask isKindOfClass:[AWSS3TransferUtilityMultiPartUploadTask class]]) { + + //Get the multipart upload task + AWSS3TransferUtilityMultiPartUploadTask *transferUtilityMultiPartUploadTask = [self.taskDictionary objectForKey:@(task.taskIdentifier)]; + + //Check if the task was cancelled. + if ( transferUtilityMultiPartUploadTask.cancelled ) { + //Abort the request, so the server can clean up any partials. + [self callAbortMultiPartForUploadTask:transferUtilityMultiPartUploadTask]; + + //Clean up. + [self cleanupForMultiPartUploadTask:transferUtilityMultiPartUploadTask]; + return; + } + + //Check if there was an error. + if (error) { + + AWSS3TransferUtilityUploadSubTask *subTask = [transferUtilityMultiPartUploadTask.inProgressPartsDictionary objectForKey:@(task.taskIdentifier)]; + + //Retrying if a 500, 503 or 400 RequestTimeout error occured. + if ([self isErrorRetriable:HTTPResponse.statusCode responseFromServer:subTask.responseData] ) { + AWSDDLogDebug(@"Received a 500, 503 or 400 error. Response Data is [%@]", subTask.responseData ); + if (transferUtilityMultiPartUploadTask.retryCount < self.transferUtilityConfiguration.retryLimit ) { + AWSDDLogDebug(@"Retry count is below limit and error is retriable. "); + [self retryUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask]; + return; + } + } + + //Error is not retriable. + transferUtilityMultiPartUploadTask.error = error; + + //Execute call back if provided. + if(transferUtilityMultiPartUploadTask.expression.completionHandler) { + transferUtilityMultiPartUploadTask.expression.completionHandler(transferUtilityMultiPartUploadTask,error); + } + + //Make sure all the parts are canceled. + [transferUtilityMultiPartUploadTask cancel]; + + //Abort the request, so the server can clean up any partials. + [self callAbortMultiPartForUploadTask:transferUtilityMultiPartUploadTask]; + + //clean up. + [self cleanupForMultiPartUploadTask:transferUtilityMultiPartUploadTask]; + return; + } + + //Get multipart upload sub task + AWSS3TransferUtilityUploadSubTask *subTask = [transferUtilityMultiPartUploadTask.inProgressPartsDictionary objectForKey:@(task.taskIdentifier)]; + NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *) task.response; + subTask.eTag = (NSString *) HTTPResponse.allHeaderFields[@"ETAG"]; + + //Add it to completed parts and remove it from remaining parts. + [transferUtilityMultiPartUploadTask.completedPartsDictionary setObject:subTask forKey:@(subTask.taskIdentifier)]; + [transferUtilityMultiPartUploadTask.inProgressPartsDictionary removeObjectForKey:@(subTask.taskIdentifier)]; + + //Delete the temporary upload file for this subTask + [self removeFile:subTask.file]; + + //Update Database + [self updateTransferRequestInDB:subTask.transferID taskIdentifier:subTask.taskIdentifier eTag:subTask.eTag status:AWSS3TransferUtilityCompletedStatus databaseQueue:_databaseQueue]; + + //If there are parts waiting to be uploaded, pick one from the list and move it to inProgress + if ([transferUtilityMultiPartUploadTask.waitingPartsDictionary count] != 0 ) { + //Get a part from the waitingList + AWSS3TransferUtilityUploadSubTask *nextSubTask = [[transferUtilityMultiPartUploadTask.waitingPartsDictionary allValues] objectAtIndex:0]; + + //Move it inProgress List + [transferUtilityMultiPartUploadTask.waitingPartsDictionary removeObjectForKey:@(nextSubTask.taskIdentifier)]; + [transferUtilityMultiPartUploadTask.inProgressPartsDictionary setObject:nextSubTask forKey:@(nextSubTask.taskIdentifier)]; + AWSDDLogDebug(@"Upload started %lu", (unsigned long)nextSubTask.taskIdentifier); + //Start the transfer + [nextSubTask.sessionTask resume]; + } + //If there are no more inProgress parts, then we are done. + else if ( [transferUtilityMultiPartUploadTask.inProgressPartsDictionary count] == 0 ) { + //Call the Multipart completion step here. + [[ self callFinishMultiPartForUploadTask:transferUtilityMultiPartUploadTask] continueWithBlock:^id (AWSTask *task) { + if (task.error) { + AWSDDLogError(@"Error finishing up MultiPartForUpload Task[%@]", task.error); + transferUtilityMultiPartUploadTask.error = error; + } + //Call the callback function is specified. + if(transferUtilityMultiPartUploadTask.expression.completionHandler) { + transferUtilityMultiPartUploadTask.expression.completionHandler(transferUtilityMultiPartUploadTask,error); + } + + AWSDDLogInfo(@"Completed Multipart Transfer: %@", transferUtilityMultiPartUploadTask.uploadID); + [self.taskDictionary removeObjectForKey:@(subTask.taskIdentifier)]; + [self deleteTransferRequestFromDB:transferUtilityMultiPartUploadTask.transferID databaseQueue:_databaseQueue]; + return nil; + }]; + } } } + if ([task isKindOfClass:[NSURLSessionDownloadTask class]]) { AWSS3TransferUtilityDownloadTask *downloadTask = [self getDownloadTask:(NSURLSessionDownloadTask *)task]; - if (error) { + + //Check if the task was cancelled. + if ( downloadTask.cancelled ) { + [self.taskDictionary removeObjectForKey:@(downloadTask.sessionTask.taskIdentifier)]; + [self deleteTransferRequestFromDB:downloadTask.transferID databaseQueue:_databaseQueue]; + return; + } + + if (error && HTTPResponse ) { + if ( [self isErrorRetriable:HTTPResponse.statusCode responseFromServer:downloadTask.responseData] ) { + if (downloadTask.retryCount < self.transferUtilityConfiguration.retryLimit) { + AWSDDLogDebug(@"Retry count is below limit and error is retriable. "); + [self retryDownload:downloadTask]; + return; + } + } downloadTask.error = error; } if (downloadTask.expression.completionHandler) { @@ -801,27 +1669,104 @@ - (void)URLSession:(NSURLSession *)session downloadTask.data, downloadTask.error); } + [self.taskDictionary removeObjectForKey:@(downloadTask.sessionTask.taskIdentifier)]; + [self deleteTransferRequestFromDB:downloadTask.transferID databaseQueue:_databaseQueue]; } +} + +- (void) cleanupForMultiPartUploadTask: (AWSS3TransferUtilityMultiPartUploadTask *) task { + //Remove data from the Database. + [self deleteTransferRequestFromDB:task.transferID databaseQueue:_databaseQueue]; - [self.taskDictionary removeObjectForKey:@(task.taskIdentifier)]; + //Remove all temporary files and entries from taskDictionary. + for ( AWSS3TransferUtilityUploadSubTask *subTask in [task.inProgressPartsDictionary allValues] ) { + [self.taskDictionary removeObjectForKey:@(subTask.taskIdentifier)]; + [self removeFile:subTask.file]; + } + for ( AWSS3TransferUtilityUploadSubTask *subTask in [task.waitingPartsDictionary allValues] ) { + [self.taskDictionary removeObjectForKey:@(subTask.taskIdentifier)]; + [self removeFile:subTask.file]; + } } +- (void) cleanupForUploadTask: (AWSS3TransferUtilityUploadTask *) uploadTask { + [self.taskDictionary removeObjectForKey:@(uploadTask.taskIdentifier)]; + if ( uploadTask.temporaryFileCreated) { + [self removeFile:uploadTask.file]; + } + [self deleteTransferRequestFromDB:uploadTask.transferID databaseQueue:_databaseQueue]; +} + +- (BOOL) isErrorRetriable:(NSInteger) HTTPStatusCode + responseFromServer:(NSString *) responseFromServer { + + // See https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html for S3 error responses + + //500 and 503 are retriable. + if (HTTPStatusCode == 500 || HTTPStatusCode == 503 ) { + return YES; + } + //If not 5XX or 400, error is not retriable. + if (HTTPStatusCode != 400 ) { + return NO; + } + + //If we didn't get any more info from the server, error is retriable + if (!responseFromServer ||[responseFromServer isEqualToString:@""] ) { + return YES; + } + + if ([responseFromServer containsString:@"RequestTimeout"] || + [responseFromServer containsString:@"ExpiredToken" ] || + [responseFromServer containsString:@"TokenRefreshRequired" ]) { + return YES; + } + return NO; +} + + - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { - if ([task isKindOfClass:[NSURLSessionUploadTask class]]) { - AWSS3TransferUtilityUploadTask *transferUtilityUploadTask = [self getUploadTask:(NSURLSessionUploadTask *)task]; + + //Check if the task is an uploadTask. + if (![task isKindOfClass:[NSURLSessionUploadTask class]]) { + return; + } + + //Handle the update differently based on whether it is a single part or multipart upload. + AWSS3TransferUtilityTask *transferUtilityTask = [self.taskDictionary objectForKey:@(task.taskIdentifier)]; + if( [transferUtilityTask isKindOfClass:[AWSS3TransferUtilityUploadTask class]] ) { + AWSS3TransferUtilityUploadTask *transferUtilityUploadTask = [self.taskDictionary objectForKey:@(task.taskIdentifier)]; if (transferUtilityUploadTask.progress.totalUnitCount != totalBytesExpectedToSend) { transferUtilityUploadTask.progress.totalUnitCount = totalBytesExpectedToSend; } - if (transferUtilityUploadTask.progress.completedUnitCount != totalBytesSent) { + + if (transferUtilityUploadTask.progress.completedUnitCount < totalBytesSent) { transferUtilityUploadTask.progress.completedUnitCount = totalBytesSent; + + if (transferUtilityUploadTask.expression.progressBlock) { + transferUtilityUploadTask.expression.progressBlock(transferUtilityUploadTask, transferUtilityUploadTask.progress); + } } + } + else if( [transferUtilityTask isKindOfClass:[AWSS3TransferUtilityMultiPartUploadTask class]]) { + //Get the multipart upload task + AWSS3TransferUtilityMultiPartUploadTask *transferUtilityMultiPartUploadTask = [self.taskDictionary objectForKey:@(task.taskIdentifier)]; + //Get multipart upload sub task + AWSS3TransferUtilityUploadSubTask *subTask = [transferUtilityMultiPartUploadTask.inProgressPartsDictionary objectForKey:@(task.taskIdentifier)]; - if (transferUtilityUploadTask.expression.progressBlock) { - transferUtilityUploadTask.expression.progressBlock(transferUtilityUploadTask, transferUtilityUploadTask.progress); + if (subTask.totalBytesSent < totalBytesSent) { + //Calculate and update the running total + transferUtilityMultiPartUploadTask.progress.completedUnitCount = transferUtilityMultiPartUploadTask.progress.completedUnitCount - subTask.totalBytesSent + totalBytesSent; + subTask.totalBytesSent = totalBytesSent; + + //execute the callback to the progressblock if present. + if (transferUtilityMultiPartUploadTask.expression.progressBlock) { + transferUtilityMultiPartUploadTask.expression.progressBlock(transferUtilityMultiPartUploadTask, transferUtilityMultiPartUploadTask.progress); + } } } } @@ -860,12 +1805,404 @@ - (void)URLSession:(NSURLSession *)session if (transferUtilityDownloadTask.progress.totalUnitCount != totalBytesExpectedToWrite) { transferUtilityDownloadTask.progress.totalUnitCount = totalBytesExpectedToWrite; } - if (transferUtilityDownloadTask.progress.completedUnitCount != totalBytesWritten) { + if (transferUtilityDownloadTask.progress.completedUnitCount < totalBytesWritten) { transferUtilityDownloadTask.progress.completedUnitCount = totalBytesWritten; + + if (transferUtilityDownloadTask.expression.progressBlock) { + transferUtilityDownloadTask.expression.progressBlock(transferUtilityDownloadTask, transferUtilityDownloadTask.progress); + } + } +} + +#pragma mark - NSURLSessionDataDelegate +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + didReceiveData:(NSData *)data { + + if (data && [data length] != 0 ) { + //Get the response into a string + NSString *response = [NSString stringWithUTF8String:[data bytes]]; + AWSDDLogDebug(@"Got response from Server: %@", response); + if (!response ) { + //If response is null, no more work to do. Return + return; + } + //Put it into the responseData property of the transferUtilityUploadTask or the transferUtilityUploadSubTask + AWSS3TransferUtilityTask *transferUtilityTask = [self.taskDictionary objectForKey:@(dataTask.taskIdentifier)]; + if ([transferUtilityTask isKindOfClass:[AWSS3TransferUtilityUploadTask class]]) { + AWSS3TransferUtilityUploadTask *uploadTask = [self.taskDictionary objectForKey:@(dataTask.taskIdentifier)]; + uploadTask.responseData = [uploadTask.responseData stringByAppendingString:response]; + } + else if ([transferUtilityTask isKindOfClass:[AWSS3TransferUtilityMultiPartUploadTask class]]) { + //Get the multipart upload task + AWSS3TransferUtilityMultiPartUploadTask *transferUtilityMultiPartUploadTask = [self.taskDictionary objectForKey:@(dataTask.taskIdentifier)]; + AWSS3TransferUtilityUploadSubTask *subTask = [transferUtilityMultiPartUploadTask.inProgressPartsDictionary objectForKey:@(dataTask.taskIdentifier)]; + subTask.responseData = [subTask.responseData stringByAppendingString:response]; + } + else if ([transferUtilityTask isKindOfClass:[AWSS3TransferUtilityDownloadTask class]] ) { + AWSS3TransferUtilityDownloadTask *downloadTask = [self.taskDictionary objectForKey:@(dataTask.taskIdentifier)]; + downloadTask.responseData = [downloadTask.responseData stringByAppendingString:response]; + } + } +} + +#pragma mark - Database Methods +//Database Layer Constants +NSString *const AWSS3TransferUtilityCreateAWSTransfer = @"CREATE TABLE IF NOT EXISTS awstransfer (" +@"transfer_id TEXT NOT NULL," +@"ns_url_session_id TEXT NOT NULL," +@"session_task_id INTEGER NOT NULL," +@"transfer_type TEXT NOT NULL, " +@"bucket_name TEXT NOT NULL, " +@"key TEXT NOT NULL, " +@"part_number INTEGER, " +@"multi_part_id TEXT, " +@"etag TEXT, " +@"file TEXT NOT NULL," +@"temporary_file_created INTEGER, " +@"content_length INTEGER," +@"status TEXT NOT NULL," +@"retry_count INTEGER NOT NULL," +@"request_headers TEXT," +@"request_parameters TEXT)"; + + +NSString *const AWSS3TransferUtilityQueryAWSTransfer = @"Select transfer_id, session_task_id, " +@"transfer_type, bucket_name, key, part_number, multi_part_id, etag, file, temporary_file_created, content_length, " +@"status, retry_count, request_headers, request_parameters " +@"From awstransfer " +@"Where ns_url_session_id=:ns_url_session_id order by transfer_id, part_number"; + + +NSString *const AWSS3TransferUtiltyInsertIntoAWSTransfer = @"INSERT INTO awstransfer (" +@"transfer_id,ns_url_session_id, session_task_id, transfer_type, bucket_name, key, part_number, multi_part_id, etag, file, " +@"temporary_file_created, content_length, status, retry_count, request_headers, request_parameters" +@") VALUES (" +@":transfer_id,:ns_url_session_id, :session_task_id, :transfer_type, :bucket_name, :key, :part_number, :multi_part_id, :etag, :file, :temporary_file_created, :content_length, " +@":status, :retry_count, :request_headers, :request_parameters" +@")"; + + +NSString *const AWSS3TransferUtilityDeleteATask = @"DELETE FROM awstransfer " +@"WHERE transfer_id=:transfer_id and " +@" session_task_id=:session_task_id "; + +NSString *const AWSS3TransferUtilityDeleteTransfer = @"DELETE FROM awstransfer " +@"WHERE transfer_id=:transfer_id"; + +NSString *const AWSS3TransferUtilityUpdateTransferUtilityStatus = @"UPDATE awstransfer " +@"SET status=:status " +@"WHERE transfer_id=:transfer_id and " +@" session_task_id=:session_task_id "; + +NSString *const AWSS3TransferUtilityUpdateTransferUtilityStatusAndETag = @"UPDATE awstransfer " +@"SET status=:status, etag = :etag " +@"WHERE transfer_id=:transfer_id and " +@" session_task_id=:session_task_id "; + +NSString *const AWSS3TransferUtilityDatabaseDirectory = @"/com/amazonaws/AWSS3TransferUtility/"; +NSString *const AWSS3TransferUtilityDatabaseName = @"transfer_utility_database"; +NSString *const AWSS3TransferUtilityInProgressStatus = @"IN_PROGRESS"; +NSString *const AWSS3TransferUtilityPausedStatus = @"PAUSED"; +NSString *const AWSS3TransferUtilityCompletedStatus = @"COMPLETED"; +NSString *const AWSS3TransferUtilityWaitingStatus = @"WAITING"; + + +- (AWSFMDatabaseQueue *) createDatabase { + //Create temporary Dir to hold DB + NSString *dbDirPath = [self.cacheDirectoryPath stringByAppendingString:AWSS3TransferUtilityDatabaseDirectory]; + BOOL fileExistsAtPath = [[NSFileManager defaultManager] fileExistsAtPath:dbDirPath]; + if (!fileExistsAtPath) { + NSError *error = nil; + BOOL success = [[NSFileManager defaultManager] createDirectoryAtPath:dbDirPath withIntermediateDirectories:YES attributes:nil error:&error]; + if (!success) { + AWSDDLogError(@"Failed to create a directory for the transfer utility database. [%@]", error); + AWSDDLogError(@"Will proceed without using database"); + return nil; + } + } + + NSString * databasePath = [dbDirPath stringByAppendingString:AWSS3TransferUtilityDatabaseName]; + //Open the database if the directory exists + AWSDDLogInfo(@"Transfer Utility Database Path: [%@]", databasePath); + AWSFMDatabaseQueue *databaseQueue = [AWSFMDatabaseQueue databaseQueueWithPath: databasePath]; + + if (!databaseQueue ) { + AWSDDLogError(@"Unable to create Database Queue for [%@]", databasePath); + return nil; + } + + [databaseQueue inDatabase:^(AWSFMDatabase *db) { + if (! [db executeUpdate: AWSS3TransferUtilityCreateAWSTransfer]) { + AWSDDLogError(@"Failed to create awstransfer Database table. [%@]", db.lastError); + } + }]; + return databaseQueue; +} + + +- (void) insertUploadTransferRequestInDB:(AWSS3TransferUtilityUploadTask *) task + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { + + [self insertTransferRequestInDB:task.transferID + nsURLSessionID:task.nsURLSessionID + taskIdentifier:@(task.sessionTask.taskIdentifier) + transferType:@"UPLOAD" + bucket:task.bucket + key:task.key + partNumber:@0 + multiPartID:@"" + eTag:@"" + file:task.file + temporaryFileCreated:task.temporaryFileCreated + contentLength:@0 + status:AWSS3TransferUtilityInProgressStatus + retryCount:@(task.retryCount) + requestHeadersJSON:[self getJSONRepresentation:task.expression.requestHeaders] + requestParametersJSON:[self getJSONRepresentation:task.expression.requestParameters] + databaseQueue:databaseQueue]; +} + +- (NSString *) getJSONRepresentation: (NSDictionary *) dict { + NSError *error = nil; + NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:kNilOptions error:&error]; + if (error) { + AWSDDLogError(@"Error converting dictionary to JSON:%@", error); + return @"{}"; + } + return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; +} + +- (NSDictionary*) getDictionaryFromJson: (NSString *)json { + NSError *error = nil; + NSData *data = [json dataUsingEncoding:NSUTF8StringEncoding]; + NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error]; + if (error) { + AWSDDLogError(@"Error converting JSON to Dictionary:%@", error); + return [NSDictionary new]; + } + return dict; +} + + +- (void) insertDownloadTransferRequestInDB:(AWSS3TransferUtilityDownloadTask *) task + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { + NSString *file = task.file; + if(!file) { + file = @""; + } + [self insertTransferRequestInDB:task.transferID + nsURLSessionID:task.nsURLSessionID + taskIdentifier:@(task.sessionTask.taskIdentifier) + transferType:@"DOWNLOAD" + bucket:task.bucket + key:task.key + partNumber:@0 + multiPartID:@"" + eTag:@"" + file:file + temporaryFileCreated: NO + contentLength:@0 + status:AWSS3TransferUtilityInProgressStatus + retryCount:@(task.retryCount) + requestHeadersJSON:[self getJSONRepresentation:task.expression.requestHeaders] + requestParametersJSON:[self getJSONRepresentation:task.expression.requestParameters] + databaseQueue:databaseQueue]; +} + +- (void) insertMultiPartUploadRequestInDB:(AWSS3TransferUtilityMultiPartUploadTask *) task + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { + [self insertTransferRequestInDB:task.transferID + nsURLSessionID:task.nsURLSessionID + taskIdentifier:@0 + transferType:@"MULTI_PART_UPLOAD" + bucket:task.bucket + key:task.key + partNumber:@0 + multiPartID:task.uploadID + eTag:@"" + file:task.file + temporaryFileCreated: task.temporaryFileCreated + contentLength:task.contentLength + status:AWSS3TransferUtilityInProgressStatus + retryCount:@(task.retryCount) + requestHeadersJSON:[self getJSONRepresentation:task.expression.requestHeaders] + requestParametersJSON:[self getJSONRepresentation:task.expression.requestParameters] + databaseQueue:databaseQueue]; +} + +- (void) insertMultiPartUploadRequestSubTaskInDB:(AWSS3TransferUtilityMultiPartUploadTask *) task + subTask:(AWSS3TransferUtilityUploadSubTask *) subTask + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { + [self insertTransferRequestInDB:task.transferID + nsURLSessionID:task.nsURLSessionID + taskIdentifier:@(subTask.taskIdentifier) + transferType:@"MULTI_PART_UPLOAD_SUB_TASK" + bucket:task.bucket + key:task.key + partNumber:subTask.partNumber + multiPartID:task.uploadID + eTag:@"" + file:subTask.file + temporaryFileCreated: YES + contentLength:@(subTask.totalBytesExpectedToSend) + status:subTask.status + retryCount:@(0) + requestHeadersJSON:[self getJSONRepresentation:task.expression.requestHeaders] + requestParametersJSON:[self getJSONRepresentation:task.expression.requestParameters] + databaseQueue:databaseQueue]; +} + + +- (void) insertTransferRequestInDB: (NSString *) transferID + nsURLSessionID: (NSString *) nsURLSessionID + taskIdentifier: (NSNumber *) taskIdentifier + transferType: (NSString *) transferType + bucket: (NSString *) bucket + key: (NSString *) key + partNumber: (NSNumber *) partNumber + multiPartID: (NSString *) multiPartID + eTag: (NSString *) eTag + file: (NSString *) file + temporaryFileCreated: (BOOL) temporaryFileCreated + contentLength: (NSNumber *) contentLength + status: (NSString *) status + retryCount: (NSNumber *) retryCount + requestHeadersJSON: (NSString *) requestHeadersJSON + requestParametersJSON: (NSString *) requestParametersJSON + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { + NSNumber *tempFileCreated = [NSNumber numberWithInt:0]; + if (temporaryFileCreated ) { + tempFileCreated = [NSNumber numberWithInt:1]; } - if (transferUtilityDownloadTask.expression.progressBlock) { - transferUtilityDownloadTask.expression.progressBlock(transferUtilityDownloadTask, transferUtilityDownloadTask.progress); + [databaseQueue inDatabase:^(AWSFMDatabase *db) { + BOOL result = [db executeUpdate: AWSS3TransferUtiltyInsertIntoAWSTransfer + withParameterDictionary:@{ + @"transfer_id": transferID, + @"ns_url_session_id": nsURLSessionID, + @"session_task_id":taskIdentifier, + @"transfer_type": transferType, + @"bucket_name": bucket, + @"key": key, + @"part_number": partNumber, + @"multi_part_id": multiPartID, + @"etag": eTag, + @"file": file, + @"temporary_file_created": tempFileCreated, + @"content_length": contentLength, + @"status": status, + @"request_headers": requestHeadersJSON, + @"request_parameters": requestParametersJSON, + @"retry_count": retryCount + }]; + + if (!result ) { + AWSDDLogError(@"Failed to save Transfer [%@] in awstransfer database table. [%@]", transferID, db.lastError); + } + }]; +} + + +- (void) deleteTransferRequestFromDB:(NSString *) transferID + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { + [databaseQueue inDatabase:^(AWSFMDatabase *db) { + BOOL result = [db executeUpdate: AWSS3TransferUtilityDeleteTransfer + withParameterDictionary:@{ + @"transfer_id": transferID + }]; + + if (!result ) { + AWSDDLogError(@"Failed to delete transfer_request [%@] in Database. [%@]", transferID, + db.lastError); + return; + } + + }]; +} + +- (void) deleteTransferRequestFromDB:(NSString *) transferID + taskIdentifier: (NSUInteger) taskIdentifier + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { + [databaseQueue inDatabase:^(AWSFMDatabase *db) { + BOOL result = [db executeUpdate:AWSS3TransferUtilityDeleteATask + withParameterDictionary:@{ + @"transfer_id": transferID, + @"session_task_id": @(taskIdentifier) + }]; + if (!result ) { + AWSDDLogError(@"Failed to delete transfer_request [%@] in Database. [%@]", transferID, + db.lastError); + } + }]; +} + +- (void) updateTransferRequestInDB: (NSString *) transferID + taskIdentifier: (NSUInteger) taskIdentifier + eTag: (NSString *) eTag + status: (NSString *) status + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { + [databaseQueue inDatabase:^(AWSFMDatabase *db) { + BOOL result = [db executeUpdate: AWSS3TransferUtilityUpdateTransferUtilityStatusAndETag + withParameterDictionary:@{ + @"transfer_id": transferID, + @"session_task_id": @(taskIdentifier), + @"etag": eTag, + @"status": status + }]; + + if (!result ) { + AWSDDLogError(@"Failed to update transfer_request [%@] in Database. [%@]", transferID, + db.lastError); + } + }]; +} + +- (NSMutableArray *) getTransferTaskDataFromDB:(NSString *)nsURLSessionID { + + NSMutableArray *tasks = [NSMutableArray new]; + //Read from DB + [_databaseQueue inDatabase:^(AWSFMDatabase *db) { + //Get all AWSTransferRecords + AWSFMResultSet *rs = [db executeQuery:AWSS3TransferUtilityQueryAWSTransfer + withParameterDictionary:@{ + @"ns_url_session_id": nsURLSessionID + }]; + while( [rs next]) { + NSMutableDictionary *transfer = [NSMutableDictionary new]; + [transfer setObject:[rs stringForColumn:@"transfer_id"] forKey:@"transfer_id"]; + [transfer setObject:@([rs intForColumn:@"session_task_id"]) forKey:@"session_task_id"]; + [transfer setObject:[rs stringForColumn:@"transfer_type"] forKey:@"transfer_type"]; + [transfer setObject:[rs stringForColumn:@"bucket_name"] forKey:@"bucket_name"]; + [transfer setObject:[rs stringForColumn:@"key"] forKey:@"key"]; + [transfer setObject:@([rs intForColumn:@"part_number"]) forKey:@"part_number"]; + [transfer setObject:[rs stringForColumn:@"multi_part_id"] forKey:@"multi_part_id"]; + [transfer setObject:[rs stringForColumn:@"etag"] forKey:@"etag"]; + [transfer setObject:[rs stringForColumn:@"file"] forKey:@"file"]; + [transfer setObject:@([rs intForColumn:@"temporary_file_created"]) forKey:@"temporary_file_created"]; + [transfer setObject:@([rs intForColumn:@"content_length"]) forKey:@"content_length"]; + [transfer setObject:[rs stringForColumn:@"status"] forKey:@"status"]; + [transfer setObject:@([rs intForColumn:@"retry_count"]) forKey:@"retry_count"]; + [transfer setObject:[rs stringForColumn:@"request_headers"] forKey:@"request_headers"]; + [transfer setObject:[rs stringForColumn:@"request_parameters"] forKey:@"request_parameters"]; + [tasks addObject:transfer]; + } + rs = nil; + }]; + return tasks; +} + + +- (void) removeFile: (NSString *) absolutePath +{ + if (!absolutePath || ![[NSFileManager defaultManager ] fileExistsAtPath:absolutePath] ) { + return; + } + + NSError *error = nil; + [[NSFileManager defaultManager] removeItemAtPath:absolutePath error:&error]; + if (error) { + AWSDDLogError(@"Error deleting file[%@]: [%@]", absolutePath, error); } } @@ -877,15 +2214,20 @@ @implementation AWSS3TransferUtilityConfiguration - (instancetype)init { if (self = [super init]) { + //set defaults. _accelerateModeEnabled = NO; + _retryLimit = 0; + _multiPartConcurrencyLimit = @(AWSS3TransferUtilityMultiPartDefaultConcurrencyLimit); } - return self; } - (id)copyWithZone:(NSZone *)zone { AWSS3TransferUtilityConfiguration *configuration = [[[self class] allocWithZone:zone] init]; configuration.accelerateModeEnabled = self.isAccelerateModeEnabled; + configuration.bucket = self.bucket; + configuration.retryLimit = self.retryLimit; + configuration.multiPartConcurrencyLimit = self.multiPartConcurrencyLimit; return configuration; } @@ -909,15 +2251,17 @@ - (NSUInteger)taskIdentifier { } - (void)cancel { - [self.sessionTask cancel]; } - (void)resume { [self.sessionTask resume]; + [self updateStatus:self.transferID taskIdentifier:self.taskIdentifier status:AWSS3TransferUtilityInProgressStatus databaseQueue:self.databaseQueue]; + } - (void)suspend { [self.sessionTask suspend]; + [self updateStatus:self.transferID taskIdentifier:self.sessionTask.taskIdentifier status:AWSS3TransferUtilityPausedStatus databaseQueue:self.databaseQueue]; } - (NSURLRequest *)request { @@ -928,10 +2272,26 @@ - (NSHTTPURLResponse *)response { if ([self.sessionTask.response isKindOfClass:[NSHTTPURLResponse class]]) { return (NSHTTPURLResponse *)self.sessionTask.response; } - return nil; } +- (void) updateStatus: (NSString *) transferID + taskIdentifier: (NSUInteger) taskIdentifier + status: (NSString *) status + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { + [databaseQueue inDatabase:^(AWSFMDatabase *db) { + BOOL result = [db executeUpdate: AWSS3TransferUtilityUpdateTransferUtilityStatus + withParameterDictionary:@{ + @"transfer_id": transferID, + @"session_task_id": @(taskIdentifier), + @"status": status + }]; + if (!result ) { + AWSDDLogError(@"Failed to update transfer_request [%@] in Database. [%@]", transferID, + db.lastError); + } + }]; +} @end @implementation AWSS3TransferUtilityUploadTask @@ -943,6 +2303,81 @@ - (AWSS3TransferUtilityUploadExpression *)expression { return _expression; } +-(void) cancel { + self.cancelled = YES; + [self.sessionTask cancel]; +} + +@end + +@implementation AWSS3TransferUtilityMultiPartUploadTask + +- (void) updateStatus: (NSString *) transferID + taskIdentifier: (NSUInteger) taskIdentifier + status: (NSString *) status + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { + [databaseQueue inDatabase:^(AWSFMDatabase *db) { + BOOL result = [db executeUpdate: AWSS3TransferUtilityUpdateTransferUtilityStatus + withParameterDictionary:@{ + @"transfer_id": transferID, + @"session_task_id": @(taskIdentifier), + @"status": status + }]; + + if (!result ) { + AWSDDLogError(@"Failed to update transfer_request [%@] in Database. [%@]", transferID, + db.lastError); + } + }]; +} + +- (instancetype)init { + if (self = [super init]) { + _progress = [NSProgress new]; + _waitingPartsDictionary = [NSMutableDictionary new]; + _inProgressPartsDictionary = [NSMutableDictionary new]; + _completedPartsDictionary = [NSMutableDictionary new]; + } + return self; +} + +- (AWSS3TransferUtilityMultiPartUploadExpression *)expression { + if (!_expression) { + _expression = [AWSS3TransferUtilityMultiPartUploadExpression new]; + } + return _expression; +} + +- (void)cancel { + self.cancelled = YES; + for (NSNumber *key in [self.inProgressPartsDictionary allKeys]) { + AWSS3TransferUtilityUploadSubTask *subTask = [self.inProgressPartsDictionary objectForKey:key]; + [subTask.sessionTask cancel]; + } + for (NSNumber *key in [self.waitingPartsDictionary allKeys]) { + AWSS3TransferUtilityUploadSubTask *subTask = [self.waitingPartsDictionary objectForKey:key]; + [subTask.sessionTask cancel]; + } +} + +- (void)resume { + for (NSNumber *key in [self.inProgressPartsDictionary allKeys]) { + AWSS3TransferUtilityUploadSubTask *subTask = [self.inProgressPartsDictionary objectForKey:key]; + subTask.status = AWSS3TransferUtilityInProgressStatus; + [self updateStatus:subTask.transferID taskIdentifier:subTask.taskIdentifier status:AWSS3TransferUtilityInProgressStatus databaseQueue:self.databaseQueue]; + [subTask.sessionTask resume]; + } +} + +- (void)suspend { + for (NSNumber *key in [self.inProgressPartsDictionary allKeys]) { + AWSS3TransferUtilityUploadSubTask *subTask = [self.inProgressPartsDictionary objectForKey:key]; + [subTask.sessionTask suspend]; + subTask.status = AWSS3TransferUtilityPausedStatus; + [self updateStatus:subTask.transferID taskIdentifier:subTask.taskIdentifier status:AWSS3TransferUtilityPausedStatus databaseQueue:self.databaseQueue]; + } +} + @end @implementation AWSS3TransferUtilityDownloadTask @@ -954,6 +2389,11 @@ - (AWSS3TransferUtilityDownloadExpression *)expression { return _expression; } +-(void) cancel { + self.cancelled = YES; + [self.sessionTask cancel]; +} + @end #pragma mark - AWSS3TransferUtilityExpressions @@ -1002,7 +2442,6 @@ - (void)assignRequestParameters:(AWSS3GetPreSignedURLRequest *)getPreSignedURLRe @end @implementation AWSS3TransferUtilityUploadExpression - - (NSString *)contentMD5 { return [self.internalRequestHeaders valueForKey:@"Content-MD5"]; } @@ -1010,9 +2449,54 @@ - (NSString *)contentMD5 { - (void)setContentMD5:(NSString *)contentMD5 { [self setValue:contentMD5 forRequestHeader:@"Content-MD5"]; } +@end + +@implementation AWSS3TransferUtilityMultiPartUploadExpression + +- (instancetype)init { + if (self = [super init]) { + _internalRequestHeaders = [NSMutableDictionary new]; + _internalRequestParameters = [NSMutableDictionary new]; + } + return self; +} + +- (NSDictionary *)requestHeaders { + return [NSDictionary dictionaryWithDictionary:self.internalRequestHeaders]; +} + +- (NSDictionary *)requestParameters { + return [NSDictionary dictionaryWithDictionary:self.internalRequestParameters]; +} + +- (void)setValue:(NSString *)value forRequestHeader:(NSString *)requestHeader { + [self.internalRequestHeaders setValue:value forKey:requestHeader]; +} + +- (void)setValue:(NSString *)value forRequestParameter:(NSString *)requestParameter { + [self.internalRequestParameters setValue:value forKey:requestParameter]; +} + +- (void)assignRequestHeaders:(AWSS3GetPreSignedURLRequest *)getPreSignedURLRequest { + for (NSString *key in self.internalRequestHeaders) { + [getPreSignedURLRequest setValue:self.internalRequestHeaders[key] + forRequestHeader:key]; + } +} + +- (void)assignRequestParameters:(AWSS3GetPreSignedURLRequest *)getPreSignedURLRequest { + for (NSString *key in self.internalRequestParameters) { + [getPreSignedURLRequest setValue:self.internalRequestParameters[key] + forRequestParameter:key]; + } +} @end @implementation AWSS3TransferUtilityDownloadExpression +@end +@implementation AWSS3TransferUtilityUploadSubTask @end + + diff --git a/AWSS3/Info.plist b/AWSS3/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSS3/Info.plist +++ b/AWSS3/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSS3Tests/AWSS3TransferUtilityTests.swift b/AWSS3Tests/AWSS3TransferUtilityTests.swift index 5ab76903ba2..e2ec06b17f6 100644 --- a/AWSS3Tests/AWSS3TransferUtilityTests.swift +++ b/AWSS3Tests/AWSS3TransferUtilityTests.swift @@ -23,6 +23,10 @@ class AWSS3TransferUtilityTests: XCTestCase { override class func setUp() { super.setUp() + //Setup Log level + AWSDDLog.sharedInstance.logLevel = .verbose + AWSDDLog.add(AWSDDTTYLogger.sharedInstance) + AWSTestUtility.setupCognitoCredentialsProvider() let serviceConfiguration = AWSServiceConfiguration( @@ -38,7 +42,19 @@ class AWSS3TransferUtilityTests: XCTestCase { transferUtilityConfiguration: transferUtilityConfiguration, forKey: "transfer-acceleration" ) - + + let serviceConfiguration2 = AWSServiceManager.default().defaultServiceConfiguration + let transferUtilityConfigurationWithRetry = AWSS3TransferUtilityConfiguration() + transferUtilityConfigurationWithRetry.isAccelerateModeEnabled = false + transferUtilityConfigurationWithRetry.retryLimit = 3 + transferUtilityConfigurationWithRetry.multiPartConcurrencyLimit = 2 + + AWSS3TransferUtility.register( + with: serviceConfiguration2!, + transferUtilityConfiguration: transferUtilityConfigurationWithRetry, + forKey: "with-retry" + ) + var dataString = "1234567890" for _ in 1...5 { dataString += dataString @@ -61,12 +77,15 @@ class AWSS3TransferUtilityTests: XCTestCase { let password = "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=" let passwordMD5 = "dnF5x6K/8ZZRzpfSlMMM+w==" - let transferUtility = AWSS3TransferUtility.default() + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") let uploadExpression = AWSS3TransferUtilityUploadExpression() uploadExpression.setValue("AES256", forRequestHeader: "x-amz-server-side-encryption-customer-algorithm") uploadExpression.setValue(password, forRequestHeader: "x-amz-server-side-encryption-customer-key") uploadExpression.setValue(passwordMD5, forRequestHeader: "x-amz-server-side-encryption-customer-key-MD5") - + uploadExpression.progressBlock = {(task, progress) in + print("Upload progress: ", progress.fractionCompleted) + } + let uploadCompletionHandler = { (task: AWSS3TransferUtilityUploadTask, error: Error?) -> Void in XCTAssertNil(error) if let HTTPResponse = task.response { @@ -76,7 +95,9 @@ class AWSS3TransferUtilityTests: XCTestCase { downloadExpression.setValue("AES256", forRequestHeader: "x-amz-server-side-encryption-customer-algorithm") downloadExpression.setValue(password, forRequestHeader: "x-amz-server-side-encryption-customer-key") downloadExpression.setValue(passwordMD5, forRequestHeader: "x-amz-server-side-encryption-customer-key-MD5") - + downloadExpression.progressBlock = {(task, progress) in + print("Download progress: ", progress.fractionCompleted) + } let downloadCompletionHandler = { (task: AWSS3TransferUtilityDownloadTask, URL: Foundation.URL?, data: Data?, error: Error?) in if let HTTPResponse = task.response { XCTAssertEqual(HTTPResponse.statusCode, 200) @@ -120,13 +141,354 @@ class AWSS3TransferUtilityTests: XCTestCase { } } + func testUploadAndDownloadLargeData() { + let expectation = self.expectation(description: "The completion handler called.") + var dataString = "Test1234" + for _ in 1...21 { + dataString = dataString + dataString; + } + + // the test key is 1234567890123456 + let password = "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=" + let passwordMD5 = "dnF5x6K/8ZZRzpfSlMMM+w==" + + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") + let uploadExpression = AWSS3TransferUtilityUploadExpression() + uploadExpression.setValue("AES256", forRequestHeader: "x-amz-server-side-encryption-customer-algorithm") + uploadExpression.setValue(password, forRequestHeader: "x-amz-server-side-encryption-customer-key") + uploadExpression.setValue(passwordMD5, forRequestHeader: "x-amz-server-side-encryption-customer-key-MD5") + uploadExpression.progressBlock = {(task, progress) in + print("Upload progress: ", progress.fractionCompleted) + } + + let uploadCompletionHandler = { (task: AWSS3TransferUtilityUploadTask, error: Error?) -> Void in + XCTAssertNil(error) + if let HTTPResponse = task.response { + XCTAssertEqual(HTTPResponse.statusCode, 200) + + let downloadExpression = AWSS3TransferUtilityDownloadExpression() + downloadExpression.setValue("AES256", forRequestHeader: "x-amz-server-side-encryption-customer-algorithm") + downloadExpression.setValue(password, forRequestHeader: "x-amz-server-side-encryption-customer-key") + downloadExpression.setValue(passwordMD5, forRequestHeader: "x-amz-server-side-encryption-customer-key-MD5") + downloadExpression.progressBlock = {(task, progress) in + print("Download progress: ", progress.fractionCompleted) + } + let downloadCompletionHandler = { (task: AWSS3TransferUtilityDownloadTask, URL: Foundation.URL?, data: Data?, error: Error?) in + if let HTTPResponse = task.response { + XCTAssertEqual(HTTPResponse.statusCode, 200) + XCTAssertEqual(data, dataString.data(using: String.Encoding.utf8)!) + XCTAssertEqual(HTTPResponse.allHeaderFields["Content-Type"] as? String, "text/plain") + } else { + XCTFail() + } + + expectation.fulfill() + } + + transferUtility.downloadData( + fromBucket: "ios-v2-s3.periods", + key: "testUploadAndDownloadLargeData.txt", + expression: downloadExpression, + completionHandler: downloadCompletionHandler).continueWith(block: { (task) -> Any? in + XCTAssertNil(task.error) + return nil + }) + } else { + XCTFail() + } + } + + transferUtility.uploadData( + dataString.data(using: String.Encoding.utf8)!, + bucket: "ios-v2-s3.periods", + key: "testUploadAndDownloadLargeData.txt", + contentType: "text/plain", + expression: uploadExpression, + completionHandler: uploadCompletionHandler + ).continueWith (block: { (task) -> AnyObject? in + XCTAssertNil(task.error) + + return nil + }) + + waitForExpectations(timeout: 30) { (error) in + XCTAssertNil(error) + } + } + + func testSuspendResumeUploadAndDownloadLargeData() { + let expectation = self.expectation(description: "The completion handler called.") + var dataString = "Test1234" + for _ in 1...22 { + dataString = dataString + dataString; + } + + // the test key is 1234567890123456 + let password = "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=" + let passwordMD5 = "dnF5x6K/8ZZRzpfSlMMM+w==" + + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") + let uploadExpression = AWSS3TransferUtilityUploadExpression() + uploadExpression.setValue("AES256", forRequestHeader: "x-amz-server-side-encryption-customer-algorithm") + uploadExpression.setValue(password, forRequestHeader: "x-amz-server-side-encryption-customer-key") + uploadExpression.setValue(passwordMD5, forRequestHeader: "x-amz-server-side-encryption-customer-key-MD5") + uploadExpression.progressBlock = {(task, progress) in + print("Upload progress: ", progress.fractionCompleted) + } + + let uploadCompletionHandler = { (task: AWSS3TransferUtilityUploadTask, error: Error?) -> Void in + XCTAssertNil(error) + if let HTTPResponse = task.response { + XCTAssertEqual(HTTPResponse.statusCode, 200) + + let downloadExpression = AWSS3TransferUtilityDownloadExpression() + downloadExpression.setValue("AES256", forRequestHeader: "x-amz-server-side-encryption-customer-algorithm") + downloadExpression.setValue(password, forRequestHeader: "x-amz-server-side-encryption-customer-key") + downloadExpression.setValue(passwordMD5, forRequestHeader: "x-amz-server-side-encryption-customer-key-MD5") + downloadExpression.progressBlock = {(task, progress) in + print("Download progress: ", progress.fractionCompleted) + } + let downloadCompletionHandler = { (task: AWSS3TransferUtilityDownloadTask, URL: Foundation.URL?, data: Data?, error: Error?) in + if let HTTPResponse = task.response { + XCTAssertEqual(HTTPResponse.statusCode, 200) + XCTAssertEqual(data, dataString.data(using: String.Encoding.utf8)!) + XCTAssertEqual(HTTPResponse.allHeaderFields["Content-Type"] as? String, "text/plain") + } else { + XCTFail() + } + + expectation.fulfill() + } + + var refDownloadTask:AWSS3TransferUtilityDownloadTask? + + transferUtility.downloadData( + fromBucket: "ios-v2-s3.periods", + key: "testSuspendResumeUploadAndDownloadLargeData.txt", + expression: downloadExpression, + completionHandler: downloadCompletionHandler).continueWith(block: { (task) -> Any? in + XCTAssertNil(task.error) + refDownloadTask = task.result + return nil + }) + + sleep(2) + refDownloadTask?.suspend() + sleep(5) + refDownloadTask?.resume() + + } else { + XCTFail() + } + } + + var refUploadTask: AWSS3TransferUtilityUploadTask? + + transferUtility.uploadData( + dataString.data(using: String.Encoding.utf8)!, + bucket: "ios-v2-s3.periods", + key: "testSuspendResumeUploadAndDownloadLargeData.txt", + contentType: "text/plain", + expression: uploadExpression, + completionHandler: uploadCompletionHandler + ).continueWith (block: { (task) -> AnyObject? in + XCTAssertNil(task.error) + refUploadTask = task.result + return nil + }) + + sleep(2) + refUploadTask?.suspend() + sleep(5) + refUploadTask?.resume() + + waitForExpectations(timeout: 90) { (error) in + XCTAssertNil(error) + } + } + + + func testCancelDownload() { + let expectation = self.expectation(description: "Cancel called.") + var dataString = "Test1234" + for _ in 1...20 { + dataString = dataString + dataString; + } + + // the test key is 1234567890123456 + let password = "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=" + let passwordMD5 = "dnF5x6K/8ZZRzpfSlMMM+w==" + + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") + let uploadExpression = AWSS3TransferUtilityUploadExpression() + uploadExpression.setValue("AES256", forRequestHeader: "x-amz-server-side-encryption-customer-algorithm") + uploadExpression.setValue(password, forRequestHeader: "x-amz-server-side-encryption-customer-key") + uploadExpression.setValue(passwordMD5, forRequestHeader: "x-amz-server-side-encryption-customer-key-MD5") + uploadExpression.progressBlock = {(task, progress) in + print("Upload progress: ", progress.fractionCompleted) + } + + let uploadCompletionHandler = { (task: AWSS3TransferUtilityUploadTask, error: Error?) -> Void in + XCTAssertNil(error) + if let HTTPResponse = task.response { + XCTAssertEqual(HTTPResponse.statusCode, 200) + + let downloadExpression = AWSS3TransferUtilityDownloadExpression() + downloadExpression.setValue("AES256", forRequestHeader: "x-amz-server-side-encryption-customer-algorithm") + downloadExpression.setValue(password, forRequestHeader: "x-amz-server-side-encryption-customer-key") + downloadExpression.setValue(passwordMD5, forRequestHeader: "x-amz-server-side-encryption-customer-key-MD5") + downloadExpression.progressBlock = {(task, progress) in + print("Download progress: ", progress.fractionCompleted) + } + let downloadCompletionHandler = { (task: AWSS3TransferUtilityDownloadTask, URL: Foundation.URL?, data: Data?, error: Error?) in + if let HTTPResponse = task.response { + XCTAssertEqual(HTTPResponse.statusCode, 200) + XCTAssertEqual(data, dataString.data(using: String.Encoding.utf8)!) + XCTAssertEqual(HTTPResponse.allHeaderFields["Content-Type"] as? String, "text/plain") + } else { + XCTFail() + } + + } + + var refDownloadTask:AWSS3TransferUtilityDownloadTask? + + transferUtility.downloadData( + fromBucket: "ios-v2-s3.periods", + key: "testCancelDownload.txt", + expression: downloadExpression, + completionHandler: downloadCompletionHandler).continueWith(block: { (task) -> Any? in + XCTAssertNil(task.error) + refDownloadTask = task.result + return nil + }) + + sleep(2) + refDownloadTask?.cancel() + expectation.fulfill() + + } else { + XCTFail() + } + } + + + transferUtility.uploadData( + dataString.data(using: String.Encoding.utf8)!, + bucket: "ios-v2-s3.periods", + key: "testCancelDownload.txt", + contentType: "text/plain", + expression: uploadExpression, + completionHandler: uploadCompletionHandler + ).continueWith (block: { (task) -> AnyObject? in + XCTAssertNil(task.error) + return nil + }) + waitForExpectations(timeout: 60) { (error) in + XCTAssertNil(error) + } + } + + func testSuspendResumeUploadAndDownloadLargeDataLongDelay() { + let expectation = self.expectation(description: "The completion handler called.") + var dataString = "Test12" + for _ in 1...22 { + dataString = dataString + dataString; + } + + // the test key is 1234567890123456 + let password = "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=" + let passwordMD5 = "dnF5x6K/8ZZRzpfSlMMM+w==" + + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") + let uploadExpression = AWSS3TransferUtilityUploadExpression() + uploadExpression.setValue("AES256", forRequestHeader: "x-amz-server-side-encryption-customer-algorithm") + uploadExpression.setValue(password, forRequestHeader: "x-amz-server-side-encryption-customer-key") + uploadExpression.setValue(passwordMD5, forRequestHeader: "x-amz-server-side-encryption-customer-key-MD5") + uploadExpression.progressBlock = {(task, progress) in + print("Upload progress: ", progress.fractionCompleted) + } + + let uploadCompletionHandler = { (task: AWSS3TransferUtilityUploadTask, error: Error?) -> Void in + XCTAssertNil(error) + if let HTTPResponse = task.response { + XCTAssertEqual(HTTPResponse.statusCode, 200) + + let downloadExpression = AWSS3TransferUtilityDownloadExpression() + downloadExpression.setValue("AES256", forRequestHeader: "x-amz-server-side-encryption-customer-algorithm") + downloadExpression.setValue(password, forRequestHeader: "x-amz-server-side-encryption-customer-key") + downloadExpression.setValue(passwordMD5, forRequestHeader: "x-amz-server-side-encryption-customer-key-MD5") + downloadExpression.progressBlock = {(task, progress) in + print("Download progress: ", progress.fractionCompleted) + } + let downloadCompletionHandler = { (task: AWSS3TransferUtilityDownloadTask, URL: Foundation.URL?, data: Data?, error: Error?) in + if let HTTPResponse = task.response { + XCTAssertEqual(HTTPResponse.statusCode, 200) + XCTAssertEqual(data, dataString.data(using: String.Encoding.utf8)!) + XCTAssertEqual(HTTPResponse.allHeaderFields["Content-Type"] as? String, "text/plain") + } else { + XCTFail() + } + + expectation.fulfill() + } + + var refDownloadTask:AWSS3TransferUtilityDownloadTask? + + transferUtility.downloadData( + fromBucket: "ios-v2-s3.periods", + key: "testSuspendResumeUploadAndDownloadLargeDataLongDelay.txt", + expression: downloadExpression, + completionHandler: downloadCompletionHandler).continueWith(block: { (task) -> Any? in + XCTAssertNil(task.error) + refDownloadTask = task.result + return nil + }) + + sleep(2) + refDownloadTask?.suspend() + print("Sleeping for 30 seconds to allow server to Timeout on the download") + sleep(30) + refDownloadTask?.resume() + + } else { + XCTFail() + } + } + + var refUploadTask: AWSS3TransferUtilityUploadTask? + + transferUtility.uploadData( + dataString.data(using: String.Encoding.utf8)!, + bucket: "ios-v2-s3.periods", + key: "testSuspendResumeUploadAndDownloadLargeDataLongDelay.txt", + contentType: "text/plain", + expression: uploadExpression, + completionHandler: uploadCompletionHandler + ).continueWith (block: { (task) -> AnyObject? in + XCTAssertNil(task.error) + refUploadTask = task.result + return nil + }) + + sleep(2) + refUploadTask?.suspend() + print("Sleeping for 30 seconds to allow server to Timeout on the upload") + sleep(30) + refUploadTask?.resume() + + waitForExpectations(timeout: 300) { (error) in + XCTAssertNil(error) + } + } + func testUploadFailure() { let expectation = self.expectation(description: "The completion handler called.") let password = "InvalidPassword" let passwordMD5 = "InvalidPasswordMD5" - let transferUtility = AWSS3TransferUtility.default() + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") let uploadExpression = AWSS3TransferUtilityUploadExpression() uploadExpression.setValue("AES256", forRequestHeader: "x-amz-server-side-encryption-customer-algorithm") uploadExpression.setValue(password, forRequestHeader: "x-amz-server-side-encryption-customer-key") @@ -145,7 +507,7 @@ class AWSS3TransferUtilityTests: XCTestCase { expectation .fulfill() } - + transferUtility.uploadData( testData, bucket: "ios-v2-s3.periods", @@ -232,6 +594,7 @@ class AWSS3TransferUtilityTests: XCTestCase { expression: nil, completionHandler: nil ).continueWith (block: { (task) -> AnyObject? in + sleep(2) XCTAssertNotNil(task.error) XCTAssertEqual(task.error?._domain, AWSS3PresignedURLErrorDomain) XCTAssertEqual(task.error?._code, AWSS3PresignedURLErrorType.presignedURLErrorInvalidBucketNameForAccelerateModeEnabled.rawValue) @@ -299,7 +662,7 @@ class AWSS3TransferUtilityTests: XCTestCase { } func testBadFilePathUpload() { - let transferUtility = AWSS3TransferUtility.default() + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") transferUtility.uploadFile(URL(fileURLWithPath: "~/abc.txt"), bucket: "ios-v2-s3.periods", @@ -318,17 +681,22 @@ class AWSS3TransferUtilityTests: XCTestCase { } func testGoodFilePathUpload() { - let transferUtility = AWSS3TransferUtility.default() + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") let filePath = NSTemporaryDirectory() + "testGoodFilePathUpload.tmp" let fileURL = URL(fileURLWithPath: filePath) - FileManager.default.createFile(atPath: filePath, contents: "Test".data(using: .utf8), attributes: nil) + FileManager.default.createFile(atPath: filePath, contents: "Test1234".data(using: .utf8), attributes: nil) + + let expression = AWSS3TransferUtilityUploadExpression() + expression.progressBlock = {(task, progress) in + print("Upload progress: ", progress.fractionCompleted) + } transferUtility.uploadFile(fileURL, bucket: "ios-v2-s3.periods", key: "testGoodFilePathUpload.txt", contentType: "text/plain", - expression: nil, + expression: expression, completionHandler: nil) .continueWith { (task: AWSTask) -> Any? in XCTAssertNil(task.error) @@ -337,8 +705,182 @@ class AWSS3TransferUtilityTests: XCTestCase { }.waitUntilFinished() } + func testLargeFileUpload() { + let expectation = self.expectation(description: "The completion handler called.") + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") + //Create a large temp file; + let filePath = NSTemporaryDirectory() + "testLargeFileUpload.tmp" + var testData = "Test1234" + for _ in 1...20 { + testData = testData + testData; + } + let fileURL = URL(fileURLWithPath: filePath) + FileManager.default.createFile(atPath: filePath, contents: testData.data(using: .utf8), attributes: nil) + + let expression = AWSS3TransferUtilityUploadExpression() + expression.progressBlock = {(task, progress) in + print("Upload progress: ", progress.fractionCompleted) + } + + //Create Completion Handler + let uploadCompletionHandler = { (task: AWSS3TransferUtilityUploadTask, error: Error?) -> Void in + XCTAssertNil(error) + expectation.fulfill() + } + + transferUtility.uploadFile(fileURL, + bucket: "ios-v2-s3.periods", + key: "testLargeFileUpload.txt", + contentType: "text/plain", + expression: expression, + completionHandler: uploadCompletionHandler) + .continueWith { (task: AWSTask) -> Any? in + XCTAssertNil(task.error) + XCTAssertNotNil(task.result) + return nil + }.waitUntilFinished() + + waitForExpectations(timeout: 30) { (error) in + XCTAssertNil(error) + } + } + + func testLargeFileUploadCancel() { + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") + //Create a large temp file; + let filePath = NSTemporaryDirectory() + "testLargeFileUploadCancel.tmp" + var testData = "Test1234" + for _ in 1...20 { + testData = testData + testData; + } + let fileURL = URL(fileURLWithPath: filePath) + FileManager.default.createFile(atPath: filePath, contents: testData.data(using: .utf8), attributes: nil) + + let expression = AWSS3TransferUtilityUploadExpression() + expression.progressBlock = {(task, progress) in + print("Upload progress: ", progress.fractionCompleted) + } + + //Create Completion Handler + let uploadCompletionHandler = { (task: AWSS3TransferUtilityUploadTask, error: Error?) -> Void in + XCTAssertNil(error) + } + + var refUploadTask: AWSS3TransferUtilityUploadTask? + + transferUtility.uploadFile(fileURL, + bucket: "ios-v2-s3.periods", + key: "testLargeFileUploadCancel.txt", + contentType: "text/plain", + expression: expression, + completionHandler: uploadCompletionHandler) + .continueWith { (task: AWSTask) -> Any? in + XCTAssertNil(task.error) + XCTAssertNotNil(task.result) + refUploadTask = task.result + return nil + }.waitUntilFinished() + sleep(2) + refUploadTask?.cancel() + } + + func testSuspendResumeLargeFileUpload() { + let expectation = self.expectation(description: "The completion handler called.") + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") + //Create a large temp file; + let filePath = NSTemporaryDirectory() + "testSuspendResumeLargeFileUpload.tmp" + var testData = "Test1234" + for _ in 1...20 { + testData = testData + testData; + } + let fileURL = URL(fileURLWithPath: filePath) + FileManager.default.createFile(atPath: filePath, contents: testData.data(using: .utf8), attributes: nil) + + let expression = AWSS3TransferUtilityUploadExpression() + expression.progressBlock = {(task, progress) in + print("Upload progress: ", progress.fractionCompleted) + } + + //Create Completion Handler + let uploadCompletionHandler = { (task: AWSS3TransferUtilityUploadTask, error: Error?) -> Void in + XCTAssertNil(error) + expectation.fulfill() + } + var refUploadTask: AWSS3TransferUtilityUploadTask? + + transferUtility.uploadFile(fileURL, + bucket: "ios-v2-s3.periods", + key: "testSuspendResumeLargeFileUpload.txt", + contentType: "text/plain", + expression: expression, + completionHandler: uploadCompletionHandler) + .continueWith { (task: AWSTask) -> Any? in + XCTAssertNil(task.error) + XCTAssertNotNil(task.result) + refUploadTask = task.result + return nil + }.waitUntilFinished() + + sleep(2) + refUploadTask?.suspend() + + sleep(5) + refUploadTask?.resume() + waitForExpectations(timeout: 30) { (error) in + XCTAssertNil(error) + } + } + + func testSuspendResumeLargeFileUploadLongDelay() { + let expectation = self.expectation(description: "The completion handler called.") + + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") + //Create a large temp file; + let filePath = NSTemporaryDirectory() + "testSuspendResumeLargeFileUploadLongDelay.tmp" + var testData = "Test1234" + for _ in 1...21 { + testData = testData + testData; + } + let fileURL = URL(fileURLWithPath: filePath) + FileManager.default.createFile(atPath: filePath, contents: testData.data(using: .utf8), attributes: nil) + + let expression = AWSS3TransferUtilityUploadExpression() + expression.progressBlock = {(task, progress) in + print("Upload progress: ", progress.fractionCompleted) + } + + //Create Completion Handler + let uploadCompletionHandler = { (task: AWSS3TransferUtilityUploadTask, error: Error?) -> Void in + XCTAssertNil(error) + expectation.fulfill() + } + var refUploadTask: AWSS3TransferUtilityUploadTask? + + transferUtility.uploadFile(fileURL, + bucket: "ios-v2-s3.periods", + key: "testSuspendResumeLargeFileUploadLongDelay.txt", + contentType: "text/plain", + expression: expression, + completionHandler: uploadCompletionHandler) + .continueWith { (task: AWSTask) -> Any? in + XCTAssertNil(task.error) + XCTAssertNotNil(task.result) + refUploadTask = task.result + return nil + }.waitUntilFinished() + + sleep(2) + refUploadTask?.suspend() + print("Sleeping for 30 seconds to allow server to Timeout") + sleep(30) + refUploadTask?.resume() + waitForExpectations(timeout: 300) { (error) in + XCTAssertNil(error) + } + } + func testGoodFilePathUploadDefaultBucket() { - let transferUtility = AWSS3TransferUtility.default() + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") let transferUtilityCongiguration = AWSS3TransferUtilityConfiguration() @@ -365,7 +907,7 @@ class AWSS3TransferUtilityTests: XCTestCase { } func testGoodFilePathWithSpacesUpload() { - let transferUtility = AWSS3TransferUtility.default() + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") let filePath = NSTemporaryDirectory() + "test Good File Path Upload.tmp" let fileURL = URL(fileURLWithPath: filePath) @@ -383,4 +925,327 @@ class AWSS3TransferUtilityTests: XCTestCase { return nil }.waitUntilFinished() } + + func testMultiPartUploadSmallFile() { + let expectation = self.expectation(description: "The completion handler called.") + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") + + let filePath = NSTemporaryDirectory() + "testMultiPartUploadSmallFile.tmp" + let fileURL = URL(fileURLWithPath: filePath) + FileManager.default.createFile(atPath: filePath, contents: "This is a test".data(using: .utf8), attributes: nil) + + let uploadCompletionHandler = { (task: AWSS3TransferUtilityMultiPartUploadTask, error: Error?) -> Void in + XCTAssertNil(error) + expectation.fulfill() + } + + transferUtility.uploadUsingMultiPart(fileURL:fileURL, + bucket: "ios-v2-s3.periods", + key: "testMultiPartUploadSmallFile.txt", + contentType: "text/plain", + expression: nil, + completionHandler: uploadCompletionHandler) + .continueWith { (task: AWSTask) -> Any? in + XCTAssertNil(task.error) + XCTAssertNotNil(task.result) + return nil + }.waitUntilFinished() + + waitForExpectations(timeout: 30) { (error) in + XCTAssertNil(error) + } + } + + func testMultiPartUploadLargeFile() { + //Create a large temp file; + let filePath = NSTemporaryDirectory() + "testMultiPartUploadLargeFile.tmp" + var testData = "Test123456789" + for _ in 1...21 { + testData = testData + testData; + } + let fileURL = URL(fileURLWithPath: filePath) + FileManager.default.createFile(atPath: filePath, contents: testData.data(using: .utf8), attributes: nil) + + let expectation = self.expectation(description: "The completion handler called.") + + //Create Completion Handler + let uploadCompletionHandler = { (task: AWSS3TransferUtilityMultiPartUploadTask, error: Error?) -> Void in + XCTAssertNil(error) + expectation.fulfill() + } + + let expression = AWSS3TransferUtilityMultiPartUploadExpression() + expression.progressBlock = {(task, progress) in + print("Upload progress: ", progress.fractionCompleted) + } + + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") + transferUtility.uploadUsingMultiPart(fileURL: fileURL, bucket: "ios-v2-s3.periods", + key: "testMultiPartUploadLargeFile.txt", + contentType: "text/plain", + expression: expression, + completionHandler: uploadCompletionHandler) + .continueWith { (task: AWSTask) -> Any? in + XCTAssertNil(task.error) + XCTAssertNotNil(task.result) + return nil + }.waitUntilFinished() + + waitForExpectations(timeout: 240) { (error) in + XCTAssertNil(error) + } + } + + func testMultiPartSinglePartEdgeCase() { + //Create a large temp file that is exactly 5 MB; + let filePath = NSTemporaryDirectory() + "testMultiPartSinglePartEdgeCase.tmp" + var testData = "Test1" + for _ in 1...20 { + testData = testData + testData; + } + let fileURL = URL(fileURLWithPath: filePath) + FileManager.default.createFile(atPath: filePath, contents: testData.data(using: .utf8), attributes: nil) + + let expectation = self.expectation(description: "The completion handler called.") + + //Create Completion Handler + let uploadCompletionHandler = { (task: AWSS3TransferUtilityMultiPartUploadTask, error: Error?) -> Void in + XCTAssertNil(error) + expectation.fulfill() + } + + let expression = AWSS3TransferUtilityMultiPartUploadExpression() + expression.progressBlock = {(task, progress) in + print("Upload progress: ", progress.fractionCompleted) + } + + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") + transferUtility.uploadUsingMultiPart(fileURL:fileURL, bucket: "ios-v2-s3.periods", + key: "testMultiPartSinglePartEdgeCase.txt", + contentType: "text/plain", + expression: expression, + completionHandler: uploadCompletionHandler) + .continueWith { (task: AWSTask) -> Any? in + XCTAssertNil(task.error) + XCTAssertNotNil(task.result) + return nil + }.waitUntilFinished() + + waitForExpectations(timeout: 120) { (error) in + XCTAssertNil(error) + } + } + + func testMultiPartEdgeCase() { + //Create a large temp file that is exactly 10 MB; + let filePath = NSTemporaryDirectory() + "testMultiPartEdgeCase.tmp" + var testData = "Test1" + for _ in 1...21 { + testData = testData + testData; + } + let fileURL = URL(fileURLWithPath: filePath) + FileManager.default.createFile(atPath: filePath, contents: testData.data(using: .utf8), attributes: nil) + + let expectation = self.expectation(description: "The completion handler called.") + + //Create Completion Handler + let uploadCompletionHandler = { (task: AWSS3TransferUtilityMultiPartUploadTask, error: Error?) -> Void in + XCTAssertNil(error) + expectation.fulfill() + } + + let expression = AWSS3TransferUtilityMultiPartUploadExpression() + expression.progressBlock = {(task, progress) in + print("Upload progress: ", progress.fractionCompleted) + } + + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") + transferUtility.uploadUsingMultiPart(fileURL: fileURL, bucket: "ios-v2-s3.periods", + key: "testMultiPartEdgeCase.txt", + contentType: "text/plain", + expression: expression, + completionHandler: uploadCompletionHandler) + .continueWith { (task: AWSTask) -> Any? in + XCTAssertNil(task.error) + XCTAssertNotNil(task.result) + return nil + }.waitUntilFinished() + + waitForExpectations(timeout: 120) { (error) in + XCTAssertNil(error) + } + } + + func testCancelMultipartUpload() { + //Create a large temp file; + let filePath = NSTemporaryDirectory() + "testCancelMultipartUpload.tmp" + var testData = "CancelT" + for _ in 1...24 { + testData = testData + testData; + } + let fileURL = URL(fileURLWithPath: filePath) + FileManager.default.createFile(atPath: filePath, contents: testData.data(using: .utf8), attributes: nil) + + + let expression = AWSS3TransferUtilityMultiPartUploadExpression() + expression.progressBlock = {(task, progress) in + print("Upload progress: ", progress.fractionCompleted) + } + + var refUploadTask: AWSS3TransferUtilityMultiPartUploadTask? + + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") + transferUtility.uploadUsingMultiPart(fileURL: fileURL, bucket: "ios-v2-s3.periods", + key: "testCancelMultipartUpload.txt", + contentType: "text/plain", + expression: expression, + completionHandler: nil) + .continueWith { (task: AWSTask) -> Any? in + XCTAssertNil(task.error) + XCTAssertNotNil(task.result) + refUploadTask = task.result + return nil; + } + sleep(2) + refUploadTask?.cancel() + + } + + func testSuspendResumeMultipartUpload() { + //Create a large temp file; + let filePath = NSTemporaryDirectory() + "testSuspendResumeMultipartUpload.tmp" + var testData = "PauseResumeTest" + for _ in 1...20 { + testData = testData + testData; + } + let fileURL = URL(fileURLWithPath: filePath) + FileManager.default.createFile(atPath: filePath, contents: testData.data(using: .utf8), attributes: nil) + + let expectation = self.expectation(description: "The completion handler called.") + + //Create Completion Handler + let uploadCompletionHandler = { (task: AWSS3TransferUtilityMultiPartUploadTask, error: Error?) -> Void in + XCTAssertNil(error) + expectation.fulfill() + } + + let expression = AWSS3TransferUtilityMultiPartUploadExpression() + expression.progressBlock = {(task, progress) in + print("Upload progress: ", progress.fractionCompleted) + } + + var refUploadTask: AWSS3TransferUtilityMultiPartUploadTask? + + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") + transferUtility.uploadUsingMultiPart(fileURL: fileURL, bucket: "ios-v2-s3.periods", + key: "testSuspendResumeMultipartUpload.txt", + contentType: "text/plain", + expression: expression, + completionHandler: uploadCompletionHandler) + .continueWith { (task: AWSTask) -> Any? in + XCTAssertNil(task.error) + XCTAssertNotNil(task.result) + refUploadTask = task.result + return nil; + } + sleep(2) + refUploadTask?.suspend() + sleep(5) + refUploadTask?.resume() + + waitForExpectations(timeout: 120) { (error) in + XCTAssertNil(error) + } + } + + func testSuspendResumeMultipartUploadLongDelay() { + //Create a large temp file; + let filePath = NSTemporaryDirectory() + "testPauseResumeUploadLongDelay.tmp" + var testData = "PauseResumeTest" + for _ in 1...22 { + testData = testData + testData; + } + let fileURL = URL(fileURLWithPath: filePath) + FileManager.default.createFile(atPath: filePath, contents: testData.data(using: .utf8), attributes: nil) + + let expectation = self.expectation(description: "The completion handler called.") + + //Create Completion Handler + let uploadCompletionHandler = { (task: AWSS3TransferUtilityMultiPartUploadTask, error: Error?) -> Void in + XCTAssertNil(error) + expectation.fulfill() + } + + let expression = AWSS3TransferUtilityMultiPartUploadExpression() + expression.progressBlock = {(task, progress) in + print("Upload progress: ", progress.fractionCompleted) + } + + var refUploadTask: AWSS3TransferUtilityMultiPartUploadTask? + + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") + + transferUtility.uploadUsingMultiPart(fileURL: fileURL, bucket: "ios-v2-s3.periods", + key: "testPauseResumeUploadLongDelay.txt", + contentType: "text/plain", + expression: expression, + completionHandler: uploadCompletionHandler) + .continueWith { (task: AWSTask) -> Any? in + XCTAssertNil(task.error) + XCTAssertNotNil(task.result) + refUploadTask = task.result + return nil; + } + sleep(2) + refUploadTask?.suspend() + print("Sleeping for 30 seconds to allow server to Timeout") + sleep(30) + refUploadTask?.resume() + + waitForExpectations(timeout: 300) { (error) in + XCTAssertNil(error) + } + } + + func testMultiPartUploadTransferAcceleration() { + //Create a large temp file; + let filePath = NSTemporaryDirectory() + "testMultiPartUploadTransferAcceleration.tmp" + var testData = "Test1234" + for _ in 1...22 { + testData = testData + testData; + } + let fileURL = URL(fileURLWithPath: filePath) + FileManager.default.createFile(atPath: filePath, contents: testData.data(using: .utf8), attributes: nil) + + let expectation = self.expectation(description: "The completion handler called.") + + //Create Completion Handler + let uploadCompletionHandler = { (task: AWSS3TransferUtilityMultiPartUploadTask, error: Error?) -> Void in + XCTAssertNil(error) + expectation.fulfill() + } + + let expression = AWSS3TransferUtilityMultiPartUploadExpression() + expression.progressBlock = {(task, progress) in + print("Upload progress: ", progress.fractionCompleted) + } + + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "transfer-acceleration") + + transferUtility.uploadUsingMultiPart(fileURL:fileURL, bucket: "ios-v2-s3-transfer-acceleration", + key: "testMultiPartUploadTransferAcceleration.txt", + contentType: "text/plain", + expression: expression, + completionHandler: uploadCompletionHandler) + .continueWith { (task: AWSTask) -> Any? in + XCTAssertNil(task.error) + XCTAssertNotNil(task.result) + return nil + }.waitUntilFinished() + + waitForExpectations(timeout: 120) { (error) in + XCTAssertNil(error) + } + } + } diff --git a/AWSSES.podspec b/AWSSES.podspec index f5d02e03cae..73550906196 100644 --- a/AWSSES.podspec +++ b/AWSSES.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSSES' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,6 +12,6 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSSES/*.{h,m}' end diff --git a/AWSSES/AWSSESService.m b/AWSSES/AWSSESService.m index 4ff2ed56420..60fdef062de 100644 --- a/AWSSES/AWSSESService.m +++ b/AWSSES/AWSSESService.m @@ -26,7 +26,7 @@ #import "AWSSESResources.h" static NSString *const AWSInfoSES = @"SES"; -static NSString *const AWSSESSDKVersion = @"2.6.12"; +static NSString *const AWSSESSDKVersion = @"2.6.13"; @interface AWSSESResponseSerializer : AWSXMLResponseSerializer diff --git a/AWSSES/Info.plist b/AWSSES/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSSES/Info.plist +++ b/AWSSES/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSSNS.podspec b/AWSSNS.podspec index 9079b2a6a84..fcd3015eb32 100644 --- a/AWSSNS.podspec +++ b/AWSSNS.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSSNS' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,6 +12,6 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSSNS/*.{h,m}' end diff --git a/AWSSNS/AWSSNSService.m b/AWSSNS/AWSSNSService.m index d776e99121a..6be9dc7ba42 100644 --- a/AWSSNS/AWSSNSService.m +++ b/AWSSNS/AWSSNSService.m @@ -26,7 +26,7 @@ #import "AWSSNSResources.h" static NSString *const AWSInfoSNS = @"SNS"; -static NSString *const AWSSNSSDKVersion = @"2.6.12"; +static NSString *const AWSSNSSDKVersion = @"2.6.13"; @interface AWSSNSResponseSerializer : AWSXMLResponseSerializer diff --git a/AWSSNS/Info.plist b/AWSSNS/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSSNS/Info.plist +++ b/AWSSNS/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSSQS.podspec b/AWSSQS.podspec index 3acf55139ab..de476a5a591 100644 --- a/AWSSQS.podspec +++ b/AWSSQS.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSSQS' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,6 +12,6 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSSQS/*.{h,m}' end diff --git a/AWSSQS/AWSSQSService.m b/AWSSQS/AWSSQSService.m index 1e05fea2823..6d42a6ab5d9 100644 --- a/AWSSQS/AWSSQSService.m +++ b/AWSSQS/AWSSQSService.m @@ -26,7 +26,7 @@ #import "AWSSQSResources.h" static NSString *const AWSInfoSQS = @"SQS"; -static NSString *const AWSSQSSDKVersion = @"2.6.12"; +static NSString *const AWSSQSSDKVersion = @"2.6.13"; @interface AWSSQSResponseSerializer : AWSXMLResponseSerializer diff --git a/AWSSQS/Info.plist b/AWSSQS/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSSQS/Info.plist +++ b/AWSSQS/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSSimpleDB.podspec b/AWSSimpleDB.podspec index 0e6428979bc..871e3426e1e 100644 --- a/AWSSimpleDB.podspec +++ b/AWSSimpleDB.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSSimpleDB' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,6 +12,6 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCore', '2.6.12' + s.dependency 'AWSCore', '2.6.13' s.source_files = 'AWSSimpleDB/*.{h,m}' end diff --git a/AWSSimpleDB/AWSSimpleDBService.m b/AWSSimpleDB/AWSSimpleDBService.m index fbcc4f11962..a14ea1b307c 100644 --- a/AWSSimpleDB/AWSSimpleDBService.m +++ b/AWSSimpleDB/AWSSimpleDBService.m @@ -26,7 +26,7 @@ #import "AWSSimpleDBResources.h" static NSString *const AWSInfoSimpleDB = @"SimpleDB"; -static NSString *const AWSSimpleDBSDKVersion = @"2.6.12"; +static NSString *const AWSSimpleDBSDKVersion = @"2.6.13"; @interface AWSSimpleDBResponseSerializer : AWSXMLResponseSerializer diff --git a/AWSSimpleDB/Info.plist b/AWSSimpleDB/Info.plist index 4b42c10313a..f33ea515c35 100644 --- a/AWSSimpleDB/Info.plist +++ b/AWSSimpleDB/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.12 + 2.6.13 CFBundleSignature ???? CFBundleVersion diff --git a/AWSUserPoolsSignIn.podspec b/AWSUserPoolsSignIn.podspec index d35b8848b33..ebc8cacc450 100644 --- a/AWSUserPoolsSignIn.podspec +++ b/AWSUserPoolsSignIn.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSUserPoolsSignIn' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -12,8 +12,8 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.dependency 'AWSCognitoIdentityProvider', '2.6.12' - s.dependency 'AWSAuthCore', '2.6.12' + s.dependency 'AWSCognitoIdentityProvider', '2.6.13' + s.dependency 'AWSAuthCore', '2.6.13' s.source_files = 'AWSAuthSDK/Sources/AWSUserPoolsSignIn/**/*.{h,m}' s.public_header_files = 'AWSAuthSDK/Sources/AWSUserPoolsSignIn/*.{h}' s.private_header_files = 'AWSAuthSDK/Sources/AWSUserPoolsSignIn/UserPoolsUI/*.{h}' diff --git a/AWSiOSSDKv2.podspec b/AWSiOSSDKv2.podspec index dec8ddaac47..eebf7a8fdf4 100644 --- a/AWSiOSSDKv2.podspec +++ b/AWSiOSSDKv2.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'AWSiOSSDKv2' - s.version = '2.6.12' + s.version = '2.6.13' 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.' @@ -15,103 +15,103 @@ Pod::Spec.new do |s| s.requires_arc = true s.subspec 'AWSCore' do |aws| - aws.dependency 'AWSCore', '2.6.12' + aws.dependency 'AWSCore', '2.6.13' end s.subspec 'AWSAPIGateway' do |apigateway| - apigateway.dependency 'AWSAPIGateway', '2.6.12' + apigateway.dependency 'AWSAPIGateway', '2.6.13' end s.subspec 'AutoScaling' do |autoscaling| - autoscaling.dependency 'AWSAutoScaling', '2.6.12' + autoscaling.dependency 'AWSAutoScaling', '2.6.13' end s.subspec 'CloudWatch' do |cloudwatch| - cloudwatch.dependency 'AWSCloudWatch', '2.6.12' + cloudwatch.dependency 'AWSCloudWatch', '2.6.13' end s.subspec 'Pinpoint' do |pp| - pp.dependency 'AWSPinpoint', '2.6.12' + pp.dependency 'AWSPinpoint', '2.6.13' end s.subspec 'AWSCognito' do |cognito| - cognito.dependency 'AWSCognito', '2.6.12' + cognito.dependency 'AWSCognito', '2.6.13' end s.subspec 'AWSCognitoIdentityProvider' do |cognitoidentityprovider| - cognitoidentityprovider.dependency 'AWSCognitoIdentityProvider', '2.6.12' + cognitoidentityprovider.dependency 'AWSCognitoIdentityProvider', '2.6.13' end s.subspec 'DynamoDB' do |ddb| - ddb.dependency 'AWSDynamoDB', '2.6.12' + ddb.dependency 'AWSDynamoDB', '2.6.13' end s.subspec 'EC2' do |ec2| - ec2.dependency 'AWSEC2', '2.6.12' + ec2.dependency 'AWSEC2', '2.6.13' end s.subspec 'ElasticLoadBalancing' do |elasticloadbalancing| - elasticloadbalancing.dependency 'AWSElasticLoadBalancing', '2.6.12' + elasticloadbalancing.dependency 'AWSElasticLoadBalancing', '2.6.13' end s.subspec 'AWSIoT' do |iot| - iot.dependency 'AWSIoT', '2.6.12' + iot.dependency 'AWSIoT', '2.6.13' end s.subspec 'Kinesis' do |kinesis| - kinesis.dependency 'AWSKinesis', '2.6.12' + kinesis.dependency 'AWSKinesis', '2.6.13' end s.subspec 'AWSKMS' do |kms| - kms.dependency 'AWSKMS', '2.6.12' + kms.dependency 'AWSKMS', '2.6.13' end s.subspec 'AWSLambda' do |lambda| - lambda.dependency 'AWSLambda', '2.6.12' + lambda.dependency 'AWSLambda', '2.6.13' end s.subspec 'AWSLex' do |lex| - lex.dependency 'AWSLex', '2.6.12' + lex.dependency 'AWSLex', '2.6.13' end s.subspec 'AWSLogs' do |log| - log.dependency 'AWSLogs', '2.6.12' + log.dependency 'AWSLogs', '2.6.13' end s.subspec 'AWSMachineLearning' do |machinelearning| - machinelearning.dependency 'AWSMachineLearning', '2.6.12' + machinelearning.dependency 'AWSMachineLearning', '2.6.13' end s.subspec 'AWSPolly' do |polly| - polly.dependency 'AWSPolly', '2.6.12' + polly.dependency 'AWSPolly', '2.6.13' end s.subspec 'MobileAnalytics' do |mobileanalytics| - mobileanalytics.dependency 'AWSMobileAnalytics', '2.6.12' + mobileanalytics.dependency 'AWSMobileAnalytics', '2.6.13' end s.subspec 'AWSRekognition' do |rekognition| - rekognition.dependency 'AWSRekognition', '2.6.12' + rekognition.dependency 'AWSRekognition', '2.6.13' end s.subspec 'AWSS3' do |s3| - s3.dependency 'AWSS3', '2.6.12' + s3.dependency 'AWSS3', '2.6.13' end s.subspec 'AWSSES' do |ses| - ses.dependency 'AWSSES', '2.6.12' + ses.dependency 'AWSSES', '2.6.13' end s.subspec 'AWSSimpleDB' do |simpledb| - simpledb.dependency 'AWSSimpleDB', '2.6.12' + simpledb.dependency 'AWSSimpleDB', '2.6.13' end s.subspec 'AWSSNS' do |sns| - sns.dependency 'AWSSNS', '2.6.12' + sns.dependency 'AWSSNS', '2.6.13' end s.subspec 'AWSSQS' do |sqs| - sqs.dependency 'AWSSQS', '2.6.12' + sqs.dependency 'AWSSQS', '2.6.13' end end diff --git a/AWSiOSSDKv2.xcodeproj/project.pbxproj b/AWSiOSSDKv2.xcodeproj/project.pbxproj index cd96f0ab0b6..4ff55ec1637 100644 --- a/AWSiOSSDKv2.xcodeproj/project.pbxproj +++ b/AWSiOSSDKv2.xcodeproj/project.pbxproj @@ -266,6 +266,8 @@ 18F938D21DE5148E00034221 /* AWSLexVoiceButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 18F938C11DE5148E00034221 /* AWSLexVoiceButton.m */; }; 18F938D41DE5193F00034221 /* AWSGeneralLexTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 18F938D31DE5193F00034221 /* AWSGeneralLexTests.m */; }; 18F938D71DE520C500034221 /* AWSLexClientTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 18F938D61DE520C500034221 /* AWSLexClientTests.m */; }; + 9A293CF0203885A300A12241 /* AWSS3TransferUtility+Validation.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A293CEE203885A300A12241 /* AWSS3TransferUtility+Validation.h */; }; + 9A293CF1203885A300A12241 /* AWSS3TransferUtility+Validation.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A293CEF203885A300A12241 /* AWSS3TransferUtility+Validation.m */; }; B52FBE921F17414A000F0C00 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B52FBE911F17414A000F0C00 /* Media.xcassets */; }; CE0D41701C6A66E5006B91B5 /* AWSCore.h in Headers */ = {isa = PBXBuildFile; fileRef = CE0D416F1C6A66E5006B91B5 /* AWSCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; CE0D417C1C6A66E5006B91B5 /* AWSCoreTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CE0D417B1C6A66E5006B91B5 /* AWSCoreTests.m */; }; @@ -2710,6 +2712,8 @@ 18F938C11DE5148E00034221 /* AWSLexVoiceButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AWSLexVoiceButton.m; sourceTree = ""; }; 18F938D31DE5193F00034221 /* AWSGeneralLexTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AWSGeneralLexTests.m; sourceTree = ""; }; 18F938D61DE520C500034221 /* AWSLexClientTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AWSLexClientTests.m; sourceTree = ""; }; + 9A293CEE203885A300A12241 /* AWSS3TransferUtility+Validation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AWSS3TransferUtility+Validation.h"; sourceTree = ""; }; + 9A293CEF203885A300A12241 /* AWSS3TransferUtility+Validation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "AWSS3TransferUtility+Validation.m"; sourceTree = ""; }; B52FBE911F17414A000F0C00 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; }; CE0D416D1C6A66E5006B91B5 /* AWSCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AWSCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CE0D416F1C6A66E5006B91B5 /* AWSCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AWSCore.h; sourceTree = ""; }; @@ -5571,6 +5575,8 @@ 18CDFB261D66561F0021B1DE /* AWSS3Serializer.h */, 18CDFB271D66561F0021B1DE /* AWSS3Serializer.m */, CE9DE9C11C6A7C2E0060793F /* Info.plist */, + 9A293CEE203885A300A12241 /* AWSS3TransferUtility+Validation.h */, + 9A293CEF203885A300A12241 /* AWSS3TransferUtility+Validation.m */, ); path = AWSS3; sourceTree = ""; @@ -6520,6 +6526,7 @@ buildActionMask = 2147483647; files = ( CE9DE9E31C6A7C5E0060793F /* AWSS3PreSignedURL.h in Headers */, + 9A293CF0203885A300A12241 /* AWSS3TransferUtility+Validation.h in Headers */, CE9DE9E91C6A7C5E0060793F /* AWSS3TransferManager.h in Headers */, 18CDFB281D66561F0021B1DE /* AWSS3Serializer.h in Headers */, CE9DE9E11C6A7C5E0060793F /* AWSS3Model.h in Headers */, @@ -9905,6 +9912,7 @@ 18DF08E61D349137004C7D19 /* AWSS3RequestRetryHandler.m in Sources */, CE9DE9E81C6A7C5E0060793F /* AWSS3Service.m in Sources */, CE9DE9EC1C6A7C5E0060793F /* AWSS3TransferUtility.m in Sources */, + 9A293CF1203885A300A12241 /* AWSS3TransferUtility+Validation.m in Sources */, 18CDFB291D66561F0021B1DE /* AWSS3Serializer.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -13059,6 +13067,8 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; + "CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES[arch=*]" = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -13078,6 +13088,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; diff --git a/CHANGELOG.md b/CHANGELOG.md index bdf421e41ff..5fb0bff6938 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # AWS Mobile SDK for iOS CHANGELOG +## 2.6.13 + +### New Features + +* **Amazon S3** + * Added support for MultiPart uploads in Transfer Utility + * Included error retry logic for Transfer Utility + ## 2.6.12 ### New Features diff --git a/Scripts/GenerateAppleDocs.sh b/Scripts/GenerateAppleDocs.sh index b4d868809ff..ee0414f0d5e 100644 --- a/Scripts/GenerateAppleDocs.sh +++ b/Scripts/GenerateAppleDocs.sh @@ -9,7 +9,7 @@ function cleanup } -VERSION="2.6.12" +VERSION="2.6.13" if [ -n $1 ] && [ "$1" == "clean" ]; then cleanup