From d7752b0ea288e733e19ec423adc1d8b8d7c757f7 Mon Sep 17 00:00:00 2001 From: AWS Date: Fri, 6 Jul 2018 04:17:05 +0000 Subject: [PATCH] The AWS Mobile SDK for iOS 2.6.23. --- AWSAPIGateway.podspec | 4 +- AWSAPIGateway/AWSAPIGatewayClient.m | 2 +- AWSAPIGateway/Info.plist | 2 +- AWSAuth.podspec | 12 +- AWSAuthCore.podspec | 4 +- AWSAuthSDK/Sources/AWSAuthCore/Info.plist | 2 +- AWSAuthSDK/Sources/AWSAuthUI/Info.plist | 2 +- .../Sources/AWSFacebookSignIn/Info.plist | 2 +- AWSAuthSDK/Sources/AWSGoogleSignIn/Info.plist | 2 +- AWSAuthSDK/Sources/AWSMobileClient/Info.plist | 2 +- .../Sources/AWSUserPoolsSignIn/Info.plist | 2 +- AWSAuthUI.podspec | 6 +- AWSAutoScaling.podspec | 4 +- AWSAutoScaling/AWSAutoScalingService.m | 2 +- AWSAutoScaling/Info.plist | 2 +- AWSCloudWatch.podspec | 4 +- AWSCloudWatch/AWSCloudWatchService.m | 2 +- AWSCloudWatch/Info.plist | 2 +- AWSCloudWatchTests/AWSCloudWatchTests.m | 2 +- AWSCognito.podspec | 4 +- AWSCognito/AWSCognitoService.m | 2 +- .../CognitoSync/AWSCognitoSyncService.m | 2 +- AWSCognito/Info.plist | 2 +- AWSCognitoAuth.podspec | 2 +- AWSCognitoAuth/AWSCognitoAuth.m | 2 +- AWSCognitoAuth/Info.plist | 2 +- AWSCognitoIdentityProvider.podspec | 7 +- .../AWSCognitoIdentityProviderService.m | 2 +- AWSCognitoIdentityProvider/Info.plist | 2 +- AWSCognitoSync.podspec | 4 +- AWSComprehend.podspec | 4 +- AWSComprehend/AWSComprehendService.m | 2 +- AWSComprehend/Info.plist | 2 +- AWSCore.podspec | 2 +- AWSCore/Info.plist | 2 +- AWSCore/Service/AWSService.m | 2 +- AWSDynamoDB.podspec | 4 +- AWSDynamoDB/AWSDynamoDBService.m | 2 +- AWSDynamoDB/Info.plist | 2 +- AWSEC2.podspec | 4 +- AWSEC2/AWSEC2Service.m | 2 +- AWSEC2/Info.plist | 2 +- AWSElasticLoadBalancing.podspec | 4 +- .../AWSElasticLoadBalancingService.m | 2 +- AWSElasticLoadBalancing/Info.plist | 2 +- AWSFacebookSignIn.podspec | 4 +- AWSGoogleSignIn.podspec | 4 +- AWSIoT.podspec | 4 +- AWSIoT/AWSIoTDataService.m | 2 +- AWSIoT/AWSIoTService.m | 2 +- AWSIoT/Info.plist | 2 +- AWSIoT/Internal/AWSIoTMQTTClient.m | 127 +- AWSIoT/Internal/MQTTSDK/MQTTSession.h | 1 + AWSIoT/Internal/MQTTSDK/MQTTSession.m | 26 +- AWSIoTTests/AWSIoTDataManagerTests.swift | 71 +- AWSKMS.podspec | 4 +- AWSKMS/AWSKMSService.m | 2 +- AWSKMS/Info.plist | 2 +- AWSKinesis.podspec | 4 +- AWSKinesis/AWSFirehoseService.m | 2 +- AWSKinesis/AWSKinesisService.m | 2 +- AWSKinesis/Info.plist | 2 +- AWSLambda.podspec | 4 +- AWSLambda/AWSLambdaService.m | 2 +- AWSLambda/Info.plist | 2 +- AWSLex.podspec | 4 +- AWSLex/AWSLexInteractionKit.m | 2 +- AWSLex/AWSLexService.m | 2 +- AWSLex/Info.plist | 2 +- AWSLogs.podspec | 4 +- AWSLogs/AWSLogsService.m | 2 +- AWSLogs/Info.plist | 2 +- AWSMachineLearning.podspec | 4 +- .../AWSMachineLearningService.m | 2 +- AWSMachineLearning/Info.plist | 2 +- AWSMobileAnalytics.podspec | 4 +- .../AWSMobileAnalyticsERSService.m | 2 +- AWSMobileAnalytics/Info.plist | 2 +- AWSMobileClient.podspec | 4 +- AWSPinpoint.podspec | 4 +- .../AWSPinpointAnalyticsService.m | 2 +- .../AWSPinpointTargetingService.m | 2 +- AWSPinpoint/Info.plist | 2 +- AWSPolly.podspec | 4 +- AWSPolly/AWSPollyService.m | 2 +- AWSPolly/AWSPollySynthesizeSpeechURLBuilder.m | 2 +- AWSPolly/Info.plist | 2 +- AWSRekognition.podspec | 4 +- AWSRekognition/AWSRekognitionService.m | 2 +- AWSRekognition/Info.plist | 2 +- AWSS3.podspec | 4 +- AWSS3/AWSS3PreSignedURL.m | 2 +- AWSS3/AWSS3Service.m | 2 +- AWSS3/AWSS3TransferUtility+HeaderHelper.h | 19 + AWSS3/AWSS3TransferUtility+HeaderHelper.m | 152 ++ AWSS3/AWSS3TransferUtility+Validation.h | 1 - AWSS3/AWSS3TransferUtility+Validation.m | 1 - AWSS3/AWSS3TransferUtility.h | 278 +--- AWSS3/AWSS3TransferUtility.m | 1432 ++++++----------- AWSS3/AWSS3TransferUtilityDatabaseHelper.h | 19 + AWSS3/AWSS3TransferUtilityDatabaseHelper.m | 452 ++++++ AWSS3/AWSS3TransferUtilityTasks.h | 342 ++++ AWSS3/AWSS3TransferUtilityTasks.m | 426 +++++ AWSS3/Info.plist | 2 +- AWSS3Tests/AWSS3Tests.m | 78 +- AWSS3Tests/AWSS3TransferUtilityTests.swift | 91 +- AWSSES.podspec | 4 +- AWSSES/AWSSESService.m | 2 +- AWSSES/Info.plist | 2 +- AWSSNS.podspec | 4 +- AWSSNS/AWSSNSService.m | 2 +- AWSSNS/Info.plist | 2 +- AWSSQS.podspec | 4 +- AWSSQS/AWSSQSService.m | 2 +- AWSSQS/Info.plist | 2 +- AWSSimpleDB.podspec | 4 +- AWSSimpleDB/AWSSimpleDBService.m | 2 +- AWSSimpleDB/Info.plist | 2 +- AWSTranscribe.podspec | 4 +- AWSTranscribe/AWSTranscribeService.m | 2 +- AWSTranscribe/Info.plist | 2 +- AWSTranslate.podspec | 4 +- AWSTranslate/AWSTranslateService.m | 2 +- AWSTranslate/Info.plist | 2 +- AWSUserPoolsSignIn.podspec | 6 +- AWSiOSSDKv2.podspec | 58 +- AWSiOSSDKv2.xcodeproj/project.pbxproj | 24 + CHANGELOG.md | 14 +- README.md | 2 +- Scripts/APIReference.sh | 113 -- Scripts/DocsCleanup.sh | 4 - Scripts/GenerateAppleDocs.sh | 2 +- docs/awstask.md | 1 - docs/index.md | 40 - docs/logging.md | 65 - docs/readme-images/cocoapods-setup-01.png | Bin 47163 -> 0 bytes docs/readme-images/cocoapods-setup-02.png | Bin 41370 -> 0 bytes docs/readme-images/cocoapods-setup-03.png | Bin 42427 -> 0 bytes docs/s3/s3-downloads.md | 119 -- docs/s3/s3-setup.md | 105 -- docs/s3/s3-uploads.md | 117 -- docs/samples-info.md | 55 - docs/service-clients.md | 89 - docs/setup/setup-application.md | 43 - docs/setup/setup-dependencies.md | 122 -- docs/setup/setup-sdk-update.md | 25 - mkdocs.yml | 34 - 147 files changed, 2474 insertions(+), 2377 deletions(-) create mode 100644 AWSS3/AWSS3TransferUtility+HeaderHelper.h create mode 100644 AWSS3/AWSS3TransferUtility+HeaderHelper.m create mode 100644 AWSS3/AWSS3TransferUtilityDatabaseHelper.h create mode 100644 AWSS3/AWSS3TransferUtilityDatabaseHelper.m create mode 100644 AWSS3/AWSS3TransferUtilityTasks.h create mode 100644 AWSS3/AWSS3TransferUtilityTasks.m delete mode 100644 Scripts/APIReference.sh delete mode 100644 Scripts/DocsCleanup.sh delete mode 100644 docs/awstask.md delete mode 100644 docs/index.md delete mode 100644 docs/logging.md delete mode 100644 docs/readme-images/cocoapods-setup-01.png delete mode 100644 docs/readme-images/cocoapods-setup-02.png delete mode 100644 docs/readme-images/cocoapods-setup-03.png delete mode 100644 docs/s3/s3-downloads.md delete mode 100644 docs/s3/s3-setup.md delete mode 100644 docs/s3/s3-uploads.md delete mode 100644 docs/samples-info.md delete mode 100644 docs/service-clients.md delete mode 100644 docs/setup/setup-application.md delete mode 100644 docs/setup/setup-dependencies.md delete mode 100644 docs/setup/setup-sdk-update.md delete mode 100644 mkdocs.yml diff --git a/AWSAPIGateway.podspec b/AWSAPIGateway.podspec index 1385986e87d..4aeb18fbac3 100644 --- a/AWSAPIGateway.podspec +++ b/AWSAPIGateway.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'AWSAPIGateway' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSAPIGateway/*.{h,m}' end diff --git a/AWSAPIGateway/AWSAPIGatewayClient.m b/AWSAPIGateway/AWSAPIGatewayClient.m index 618bd458e77..97ea97f9692 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.22"; +static NSString *const AWSAPIGatewaySDKVersion = @"2.6.23"; static int defaultChunkSize = 1024; diff --git a/AWSAPIGateway/Info.plist b/AWSAPIGateway/Info.plist index 328ef1456c0..fed97625f5c 100644 --- a/AWSAPIGateway/Info.plist +++ b/AWSAPIGateway/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSAuth.podspec b/AWSAuth.podspec index 3f644d819a4..91f8fdccf2e 100644 --- a/AWSAuth.podspec +++ b/AWSAuth.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSAuth' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + authcore.dependency 'AWSAuthCore', '2.6.23' end s.subspec 'FacebookSignIn' do |facebook| - facebook.dependency 'AWSFacebookSignIn', '2.6.22' + facebook.dependency 'AWSFacebookSignIn', '2.6.23' end s.subspec 'GoogleSignIn' do |google| - google.dependency 'AWSGoogleSignIn', '2.6.22' + google.dependency 'AWSGoogleSignIn', '2.6.23' end s.subspec 'UserPoolsSignIn' do |up| - up.dependency 'AWSUserPoolsSignIn', '2.6.22' + up.dependency 'AWSUserPoolsSignIn', '2.6.23' end s.subspec 'UI' do |ui| - ui.dependency 'AWSAuthUI', '2.6.22' + ui.dependency 'AWSAuthUI', '2.6.23' end end diff --git a/AWSAuthCore.podspec b/AWSAuthCore.podspec index 2bc15e9534d..86e828e8ea9 100644 --- a/AWSAuthCore.podspec +++ b/AWSAuthCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSAuthCore' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' 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 7409dee6ecc..ad2d2f28416 100644 --- a/AWSAuthSDK/Sources/AWSAuthCore/Info.plist +++ b/AWSAuthSDK/Sources/AWSAuthCore/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/AWSAuthSDK/Sources/AWSAuthUI/Info.plist b/AWSAuthSDK/Sources/AWSAuthUI/Info.plist index 7409dee6ecc..ad2d2f28416 100644 --- a/AWSAuthSDK/Sources/AWSAuthUI/Info.plist +++ b/AWSAuthSDK/Sources/AWSAuthUI/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/AWSAuthSDK/Sources/AWSFacebookSignIn/Info.plist b/AWSAuthSDK/Sources/AWSFacebookSignIn/Info.plist index 7409dee6ecc..ad2d2f28416 100644 --- a/AWSAuthSDK/Sources/AWSFacebookSignIn/Info.plist +++ b/AWSAuthSDK/Sources/AWSFacebookSignIn/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/AWSAuthSDK/Sources/AWSGoogleSignIn/Info.plist b/AWSAuthSDK/Sources/AWSGoogleSignIn/Info.plist index 7409dee6ecc..ad2d2f28416 100644 --- a/AWSAuthSDK/Sources/AWSGoogleSignIn/Info.plist +++ b/AWSAuthSDK/Sources/AWSGoogleSignIn/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/AWSAuthSDK/Sources/AWSMobileClient/Info.plist b/AWSAuthSDK/Sources/AWSMobileClient/Info.plist index 581fb0745b6..4bebe45e804 100644 --- a/AWSAuthSDK/Sources/AWSMobileClient/Info.plist +++ b/AWSAuthSDK/Sources/AWSMobileClient/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/AWSAuthSDK/Sources/AWSUserPoolsSignIn/Info.plist b/AWSAuthSDK/Sources/AWSUserPoolsSignIn/Info.plist index 7409dee6ecc..ad2d2f28416 100644 --- a/AWSAuthSDK/Sources/AWSUserPoolsSignIn/Info.plist +++ b/AWSAuthSDK/Sources/AWSUserPoolsSignIn/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/AWSAuthUI.podspec b/AWSAuthUI.podspec index 32469928481..6f93cfc63d7 100644 --- a/AWSAuthUI.podspec +++ b/AWSAuthUI.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSAuthUI' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' - s.dependency 'AWSAuthCore', '2.6.22' + s.dependency 'AWSCore', '2.6.23' + s.dependency 'AWSAuthCore', '2.6.23' 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 3309c7d53a0..ab7b4ecbe63 100644 --- a/AWSAutoScaling.podspec +++ b/AWSAutoScaling.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSAutoScaling' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSAutoScaling/*.{h,m}' end diff --git a/AWSAutoScaling/AWSAutoScalingService.m b/AWSAutoScaling/AWSAutoScalingService.m index 976728915f2..384e8fcb3c6 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.22"; +static NSString *const AWSAutoScalingSDKVersion = @"2.6.23"; @interface AWSAutoScalingResponseSerializer : AWSXMLResponseSerializer diff --git a/AWSAutoScaling/Info.plist b/AWSAutoScaling/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSAutoScaling/Info.plist +++ b/AWSAutoScaling/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSCloudWatch.podspec b/AWSCloudWatch.podspec index 2d5d9d81792..41708574778 100644 --- a/AWSCloudWatch.podspec +++ b/AWSCloudWatch.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSCloudWatch' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSCloudWatch/*.{h,m}' end diff --git a/AWSCloudWatch/AWSCloudWatchService.m b/AWSCloudWatch/AWSCloudWatchService.m index e1a234927cc..146cb62f14b 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.22"; +static NSString *const AWSCloudWatchSDKVersion = @"2.6.23"; @interface AWSCloudWatchResponseSerializer : AWSXMLResponseSerializer diff --git a/AWSCloudWatch/Info.plist b/AWSCloudWatch/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSCloudWatch/Info.plist +++ b/AWSCloudWatch/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSCloudWatchTests/AWSCloudWatchTests.m b/AWSCloudWatchTests/AWSCloudWatchTests.m index 661d8ecd604..cca30723a77 100644 --- a/AWSCloudWatchTests/AWSCloudWatchTests.m +++ b/AWSCloudWatchTests/AWSCloudWatchTests.m @@ -110,7 +110,7 @@ - (void)testGetMetricStatisticsFailed { [[[cloudWatch getMetricStatistics:statisticsInput] continueWithBlock:^id(AWSTask *task) { XCTAssertNotNil(task.error, @"Expected InvalidParameterCombination error not found."); - XCTAssertEqual(task.error.code, 4); + XCTAssertEqual(task.error.code, AWSCloudWatchErrorInvalidParameterCombination); XCTAssertTrue([@"InvalidParameterCombination" isEqualToString:task.error.userInfo[@"Code"]]); XCTAssertTrue([@"At least one of the parameters Statistics and ExtendedStatistics must be specified." isEqualToString:task.error.userInfo[@"Message"]]); return nil; diff --git a/AWSCognito.podspec b/AWSCognito.podspec index ad8fc82a32b..e0cc42b0420 100644 --- a/AWSCognito.podspec +++ b/AWSCognito.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSCognito' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' 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 b16df11b9be..12011e3b5b7 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.22"; +static NSString *const AWSCognitoSDKVersion = @"2.6.23"; 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 de3be3b1811..3b2a4def879 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.22"; +static NSString *const AWSCognitoSyncSDKVersion = @"2.6.23"; @interface AWSCognitoSyncResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSCognito/Info.plist b/AWSCognito/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSCognito/Info.plist +++ b/AWSCognito/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSCognitoAuth.podspec b/AWSCognitoAuth.podspec index 4556f047b52..d8319fe9abc 100644 --- a/AWSCognitoAuth.podspec +++ b/AWSCognitoAuth.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSCognitoAuth' - s.version = '2.6.22' + s.version = '2.6.23' 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 3df6f091906..761a06b15e3 100644 --- a/AWSCognitoAuth/AWSCognitoAuth.m +++ b/AWSCognitoAuth/AWSCognitoAuth.m @@ -42,7 +42,7 @@ @interface AWSCognitoAuth()CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSCognitoIdentityProvider.podspec b/AWSCognitoIdentityProvider.podspec index 86b1c5f1fce..36f59e1c39f 100644 --- a/AWSCognitoIdentityProvider.podspec +++ b/AWSCognitoIdentityProvider.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSCognitoIdentityProvider' - s.version = '2.6.22' + s.version = '2.6.23' 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' @@ -12,9 +12,10 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/aws/aws-sdk-ios.git', :tag => s.version} s.requires_arc = true - s.frameworks = 'Security', 'UIKit' + s.frameworks = 'Security', 'UIKit' + s.dependency 'AWSCore', '2.6.23' s.dependency 'AWSCognitoIdentityProviderASF', '1.0.1' - s.dependency 'AWSCore', '2.6.22' + 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 61fdfa8da1e..fce9a338568 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.22"; +static NSString *const AWSCognitoIdentityProviderSDKVersion = @"2.6.23"; @interface AWSCognitoIdentityProviderResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSCognitoIdentityProvider/Info.plist b/AWSCognitoIdentityProvider/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSCognitoIdentityProvider/Info.plist +++ b/AWSCognitoIdentityProvider/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSCognitoSync.podspec b/AWSCognitoSync.podspec index 5346a9522ab..579ba06b272 100644 --- a/AWSCognitoSync.podspec +++ b/AWSCognitoSync.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'AWSCognitoSync' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCognito', '2.6.23' s.deprecated = true s.deprecated_in_favor_of = 'AWSCognito' diff --git a/AWSComprehend.podspec b/AWSComprehend.podspec index 7b3b08e6de2..327951c3b72 100644 --- a/AWSComprehend.podspec +++ b/AWSComprehend.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSComprehend' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSComprehend/*.{h,m}' end diff --git a/AWSComprehend/AWSComprehendService.m b/AWSComprehend/AWSComprehendService.m index 31b08cc1cca..0b388612f55 100644 --- a/AWSComprehend/AWSComprehendService.m +++ b/AWSComprehend/AWSComprehendService.m @@ -26,7 +26,7 @@ #import "AWSComprehendResources.h" static NSString *const AWSInfoComprehend = @"Comprehend"; -static NSString *const AWSComprehendSDKVersion = @"2.6.22"; +static NSString *const AWSComprehendSDKVersion = @"2.6.23"; @interface AWSComprehendResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSComprehend/Info.plist b/AWSComprehend/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSComprehend/Info.plist +++ b/AWSComprehend/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSCore.podspec b/AWSCore.podspec index c6ed4823e28..741547497ae 100644 --- a/AWSCore.podspec +++ b/AWSCore.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'AWSCore' - s.version = '2.6.22' + s.version = '2.6.23' 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 ed624ca8f6f..52bdaa70347 100644 --- a/AWSCore/Info.plist +++ b/AWSCore/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSCore/Service/AWSService.m b/AWSCore/Service/AWSService.m index 6623f3c5656..45f8bd4d35b 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.22"; +NSString *const AWSiOSSDKVersion = @"2.6.23"; NSString *const AWSServiceErrorDomain = @"com.amazonaws.AWSServiceErrorDomain"; static NSString *const AWSServiceConfigurationUnknown = @"Unknown"; diff --git a/AWSDynamoDB.podspec b/AWSDynamoDB.podspec index 6c78f5b109a..b418a997be5 100644 --- a/AWSDynamoDB.podspec +++ b/AWSDynamoDB.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSDynamoDB' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSDynamoDB/*.{h,m}' end diff --git a/AWSDynamoDB/AWSDynamoDBService.m b/AWSDynamoDB/AWSDynamoDBService.m index ec3b1c3afbe..e06b93b5259 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.22"; +static NSString *const AWSDynamoDBSDKVersion = @"2.6.23"; @interface AWSDynamoDBResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSDynamoDB/Info.plist b/AWSDynamoDB/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSDynamoDB/Info.plist +++ b/AWSDynamoDB/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSEC2.podspec b/AWSEC2.podspec index 92865d77b3a..7d106ac0333 100644 --- a/AWSEC2.podspec +++ b/AWSEC2.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSEC2' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSEC2/*.{h,m}' end diff --git a/AWSEC2/AWSEC2Service.m b/AWSEC2/AWSEC2Service.m index a9ba3d0f52d..9fc2be0fc17 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.22"; +static NSString *const AWSEC2SDKVersion = @"2.6.23"; @interface AWSEC2ResponseSerializer : AWSXMLResponseSerializer diff --git a/AWSEC2/Info.plist b/AWSEC2/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSEC2/Info.plist +++ b/AWSEC2/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSElasticLoadBalancing.podspec b/AWSElasticLoadBalancing.podspec index 25436dd41d0..d9f07679815 100644 --- a/AWSElasticLoadBalancing.podspec +++ b/AWSElasticLoadBalancing.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSElasticLoadBalancing' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSElasticLoadBalancing/*.{h,m}' end diff --git a/AWSElasticLoadBalancing/AWSElasticLoadBalancingService.m b/AWSElasticLoadBalancing/AWSElasticLoadBalancingService.m index 42da1c4875b..921f12956c2 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.22"; +static NSString *const AWSElasticLoadBalancingSDKVersion = @"2.6.23"; @interface AWSElasticLoadBalancingResponseSerializer : AWSXMLResponseSerializer diff --git a/AWSElasticLoadBalancing/Info.plist b/AWSElasticLoadBalancing/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSElasticLoadBalancing/Info.plist +++ b/AWSElasticLoadBalancing/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSFacebookSignIn.podspec b/AWSFacebookSignIn.podspec index d87bbe2974f..f9874fc5d4c 100644 --- a/AWSFacebookSignIn.podspec +++ b/AWSFacebookSignIn.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSFacebookSignIn' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSAuthCore', '2.6.23' 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 55d6ec633e6..f6bb113d447 100644 --- a/AWSGoogleSignIn.podspec +++ b/AWSGoogleSignIn.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSGoogleSignIn' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSAuthCore', '2.6.23' 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 cbcc0453e57..8f95d09dd12 100644 --- a/AWSIoT.podspec +++ b/AWSIoT.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSIoT' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' 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 b4008be1fe9..5c191ac2a0f 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.22"; +static NSString *const AWSIoTDataSDKVersion = @"2.6.23"; @interface AWSIoTDataResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSIoT/AWSIoTService.m b/AWSIoT/AWSIoTService.m index 0f5e6ce277c..da02462f316 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.22"; +static NSString *const AWSIoTSDKVersion = @"2.6.23"; @interface AWSIoTResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSIoT/Info.plist b/AWSIoT/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSIoT/Info.plist +++ b/AWSIoT/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSIoT/Internal/AWSIoTMQTTClient.m b/AWSIoT/Internal/AWSIoTMQTTClient.m index 715c2c61b1b..ba93392b3a0 100644 --- a/AWSIoT/Internal/AWSIoTMQTTClient.m +++ b/AWSIoT/Internal/AWSIoTMQTTClient.m @@ -87,6 +87,8 @@ @interface AWSIoTMQTTClient() >: MQTTSession.send msg to server", [NSThread currentThread]); [encoder encodeMessage:msg]; } @@ -651,5 +665,15 @@ - (BOOL)isReadyToPublish { return encoder && [encoder status] == MQTTEncoderStatusReady; } +-(void) drainSenderQueue { + int count = 0; + while ([self.queue count] > 0 && count < _publishRetryThrottle && [self isReadyToPublish]) { + AWSDDLogDebug(@"Sending message from session queue" ); + MQTTMessage *msg = [self.queue objectAtIndex:0]; + [self.queue removeObjectAtIndex:0]; + [encoder encodeMessage:msg]; + count = count + 1; + } +} @end diff --git a/AWSIoTTests/AWSIoTDataManagerTests.swift b/AWSIoTTests/AWSIoTDataManagerTests.swift index e71bbdc7763..e0f4976c33c 100644 --- a/AWSIoTTests/AWSIoTDataManagerTests.swift +++ b/AWSIoTTests/AWSIoTDataManagerTests.swift @@ -28,12 +28,20 @@ class AWSIoTDataManagerTests: XCTestCase { let PolicyName = "AWSiOSSDKv2Test" //Setup Log level - AWSDDLog.sharedInstance.logLevel = .debug + AWSDDLog.sharedInstance.logLevel = .verbose AWSDDLog.add(AWSDDTTYLogger.sharedInstance) //Setup creds AWSTestUtility.setupCognitoCredentialsProvider() + //Create MQTT Config + let lwt = AWSIoTMQTTLastWillAndTestament() + lwt.topic = "will-topic"; + lwt.qos = AWSIoTMQTTQoS.messageDeliveryAttemptedAtLeastOnce + + let mqttConfig = AWSIoTMQTTConfiguration.init(keepAliveTimeInterval: 30.0, + baseReconnectTimeInterval: 1.0, minimumConnectionTimeInterval: 20, maximumReconnectTimeInterval: 8, runLoop: RunLoop.current , runLoopMode: RunLoopMode.defaultRunLoopMode.rawValue, autoResubscribe: true, lastWillAndTestament: lwt) + //Setup iOT Manager for Broker 1 let iotConfigurationBroker1 = AWSServiceConfiguration(region: .USEast1 , credentialsProvider:AWSServiceManager.default().defaultServiceConfiguration.credentialsProvider) @@ -53,15 +61,15 @@ class AWSIoTDataManagerTests: XCTestCase { let iotDataManagerConfigurationBroker1 = AWSServiceConfiguration(region: .USEast1, endpoint: AWSTestUtility.getIoTEndPoint("iot-us-east1-endpoint"), credentialsProvider: AWSServiceManager.default().defaultServiceConfiguration.credentialsProvider) - AWSIoTDataManager.register(with:iotDataManagerConfigurationBroker1!, forKey:"iot-data-manager-broker1") - AWSIoTDataManager.register(with:iotDataManagerConfigurationBroker1!, forKey:"iot-data-manager-broker") + AWSIoTDataManager.register(with:iotDataManagerConfigurationBroker1!, with: mqttConfig, forKey:"iot-data-manager-broker1") + AWSIoTDataManager.register(with:iotDataManagerConfigurationBroker1!, with:mqttConfig, forKey:"iot-data-manager-broker") //Setup iOT Data Manager for Broker 2 let iotDataManagerConfigurationBroker2 = AWSServiceConfiguration(region: .USEast2, endpoint: AWSTestUtility.getIoTEndPoint("iot-us-east2-endpoint"), credentialsProvider: AWSServiceManager.default().defaultServiceConfiguration.credentialsProvider) - AWSIoTDataManager.register(with:iotDataManagerConfigurationBroker2!, forKey:"iot-data-manager-broker2") + AWSIoTDataManager.register(with:iotDataManagerConfigurationBroker2!, with: mqttConfig, forKey:"iot-data-manager-broker2") func createCertAndAttachPolicy(certName: String, iotManager:AWSIoTManager, iot:AWSIoT) { @@ -702,34 +710,33 @@ class AWSIoTDataManagerTests: XCTestCase { func mqttEventCallback( _ status: AWSIoTMQTTStatus ) { - print("connection status = \(status.rawValue)") switch(status) { case .connecting: - print ("Connecting...") + print ("Event Received: Connecting...") case .connected: - print("Connected") + print("Event Received: Connected") connected = true (hasConnected[iteration] as! XCTestExpectation).fulfill() case .disconnected: + print("Event Received: Disconnected") if (disconnectIssued ) { - print("Disconnected") connected = false (hasDisconnected[iteration] as! XCTestExpectation).fulfill() } case .connectionRefused: - print("Connection Refused") + print("Event Received: Connection Refused") case .connectionError: - print("Connection Error") + print("Event Received: Connection Error") case .protocolError: - print("Protocol Error") + print("Event Received: Protocol Error") default: - print("Unknown state: \(status.rawValue)") + print("Event Received: Unknown state: \(status.rawValue)") } } @@ -788,7 +795,7 @@ class AWSIoTDataManagerTests: XCTestCase { disconnectIssued = true wait(for:[hasDisconnected[iteration] as! XCTestExpectation], timeout:90) XCTAssertFalse(connected) - + sleep(1) iteration = iteration + 1 } @@ -810,6 +817,7 @@ class AWSIoTDataManagerTests: XCTestCase { func publishSubscribeMultipleConsecutiveConnectionsWithManualDisconnect(useWebSocket: Bool) { var messageCount = 0 var connected = false + var disconnected = false let numberOfAttempts = 50 var iteration = 0; let hasConnected:(NSMutableArray) = NSMutableArray() @@ -827,37 +835,37 @@ class AWSIoTDataManagerTests: XCTestCase { func mqttEventCallback( _ status: AWSIoTMQTTStatus ) { - print("connection status = \(status.rawValue)") switch(status) { case .connecting: - print ("Connecting...") + print ("Event Received: Connecting...") case .connected: - print("Connected") + print("Event Received: Connected") if ( !connected) { connected = true (hasConnected[iteration] as! XCTestExpectation).fulfill() } case .disconnected: - if (disconnectIssued && connected) { - print("Disconnected") + print("Event Received: Disconnected") + if (disconnectIssued && !disconnected) { connected = false + disconnected = true (hasDisconnected[iteration] as! XCTestExpectation).fulfill() } case .connectionRefused: - print("Connection Refused") + print("Event Received: Connection Refused") case .connectionError: - print("Connection Error") + print("Event Received: Connection Error") case .protocolError: - print("Protocol Error") + print("Event Received: Protocol Error") default: - print("Unknown state: \(status.rawValue)") + print("Event Received: Unknown state: \(status.rawValue)") } } @@ -867,22 +875,27 @@ class AWSIoTDataManagerTests: XCTestCase { let certificateID:String? = defaults.string(forKey: "TestCertBroker1") while (iteration < numberOfAttempts ) { + //Set state + connected = false + disconnected = false disconnectIssued = false + messageCount = 0 var subscriptionVerified = false connected = false + print ("Test: Issued connect") if (useWebSocket) { iotDataManager.connectUsingWebSocket(withClientId: UUID().uuidString, cleanSession: true, statusCallback: mqttEventCallback) } else { iotDataManager.connect( withClientId: UUID().uuidString, cleanSession:true, certificateId:certificateID!, statusCallback: mqttEventCallback) } - print("Connect call completed") wait(for:[hasConnected[iteration] as! XCTestExpectation], timeout: 120) if (!connected) { return } + print("Test: Connected Successfully") XCTAssertTrue(connected, "Successfully established MQTT Connection") let testMessage = "Test Message\(iteration)" @@ -916,11 +929,12 @@ class AWSIoTDataManagerTests: XCTestCase { wait(for:[gotMessages[iteration] as! XCTestExpectation], timeout:120) //Disconnect - iotDataManager.disconnect() disconnectIssued = true + iotDataManager.disconnect() + print("Test: Issued Disconnect") wait(for:[hasDisconnected[iteration] as! XCTestExpectation], timeout:120) - XCTAssertFalse(connected) - + XCTAssertTrue(disconnected) + print("Test: Disconnected successfully" ) iteration = iteration + 1 } @@ -939,7 +953,6 @@ class AWSIoTDataManagerTests: XCTestCase { func mqttEventCallback( _ status: AWSIoTMQTTStatus ) { - print("connection status = \(status.rawValue)") switch(status) { case .connecting: @@ -1024,9 +1037,9 @@ class AWSIoTDataManagerTests: XCTestCase { disconnectIssued = true wait(for:[hasDisconnected], timeout: 30) XCTAssertFalse(connected) + } - - + } diff --git a/AWSKMS.podspec b/AWSKMS.podspec index f610503e529..7f3431c502a 100644 --- a/AWSKMS.podspec +++ b/AWSKMS.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSKMS' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSKMS/*.{h,m}' end diff --git a/AWSKMS/AWSKMSService.m b/AWSKMS/AWSKMSService.m index 13491e46da1..fb47244f8c5 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.22"; +static NSString *const AWSKMSSDKVersion = @"2.6.23"; @interface AWSKMSResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSKMS/Info.plist b/AWSKMS/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSKMS/Info.plist +++ b/AWSKMS/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSKinesis.podspec b/AWSKinesis.podspec index 256fa9be4b4..930fcdd390b 100644 --- a/AWSKinesis.podspec +++ b/AWSKinesis.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSKinesis' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSKinesis/*.{h,m}' end diff --git a/AWSKinesis/AWSFirehoseService.m b/AWSKinesis/AWSFirehoseService.m index 449f6a24d50..829d3a3f46e 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.22"; +static NSString *const AWSFirehoseSDKVersion = @"2.6.23"; @interface AWSFirehoseResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSKinesis/AWSKinesisService.m b/AWSKinesis/AWSKinesisService.m index 5c4e6502273..ebbe766cd2f 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.22"; +static NSString *const AWSKinesisSDKVersion = @"2.6.23"; @interface AWSKinesisResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSKinesis/Info.plist b/AWSKinesis/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSKinesis/Info.plist +++ b/AWSKinesis/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSLambda.podspec b/AWSLambda.podspec index 1d4e78b789d..fa3c8535f16 100644 --- a/AWSLambda.podspec +++ b/AWSLambda.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSLambda' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSLambda/*.{h,m}' end diff --git a/AWSLambda/AWSLambdaService.m b/AWSLambda/AWSLambdaService.m index 5513e88a4cb..304f93f38e0 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.22"; +static NSString *const AWSLambdaSDKVersion = @"2.6.23"; @interface AWSLambdaResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSLambda/Info.plist b/AWSLambda/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSLambda/Info.plist +++ b/AWSLambda/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSLex.podspec b/AWSLex.podspec index 2166c1146fc..ce5d31fc1eb 100644 --- a/AWSLex.podspec +++ b/AWSLex.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSLex' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' 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 4f6742c95da..08d497c726d 100644 --- a/AWSLex/AWSLexInteractionKit.m +++ b/AWSLex/AWSLexInteractionKit.m @@ -22,7 +22,7 @@ #import NSString *const AWSInfoInteractionKit = @"LexInteractionKit"; -NSString *const AWSInteractionKitSDKVersion = @"2.6.22"; +NSString *const AWSInteractionKitSDKVersion = @"2.6.23"; 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 3a02bacc14b..56532cb3a1a 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.22"; +static NSString *const AWSLexSDKVersion = @"2.6.23"; @interface AWSLexResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSLex/Info.plist b/AWSLex/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSLex/Info.plist +++ b/AWSLex/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSLogs.podspec b/AWSLogs.podspec index 4e678d41104..c81ceed870b 100644 --- a/AWSLogs.podspec +++ b/AWSLogs.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSLogs' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSLogs/*.{h,m}' end diff --git a/AWSLogs/AWSLogsService.m b/AWSLogs/AWSLogsService.m index 26136e6dac8..8a7a538ba56 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.22"; +static NSString *const AWSLogsSDKVersion = @"2.6.23"; @interface AWSLogsResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSLogs/Info.plist b/AWSLogs/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSLogs/Info.plist +++ b/AWSLogs/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSMachineLearning.podspec b/AWSMachineLearning.podspec index 56fcab6e09a..e9c5a4d10d5 100644 --- a/AWSMachineLearning.podspec +++ b/AWSMachineLearning.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSMachineLearning' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSMachineLearning/*.{h,m}' end diff --git a/AWSMachineLearning/AWSMachineLearningService.m b/AWSMachineLearning/AWSMachineLearningService.m index 8e480def476..20bcbdb46c7 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.22"; +static NSString *const AWSMachineLearningSDKVersion = @"2.6.23"; diff --git a/AWSMachineLearning/Info.plist b/AWSMachineLearning/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSMachineLearning/Info.plist +++ b/AWSMachineLearning/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSMobileAnalytics.podspec b/AWSMobileAnalytics.podspec index 02a85f9c8b5..74b041c08bb 100644 --- a/AWSMobileAnalytics.podspec +++ b/AWSMobileAnalytics.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSMobileAnalytics' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' 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 42ca2703f9d..76924d32bc3 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.22"; +static NSString *const AWSMobileAnalyticsERSSDKVersion = @"2.6.23"; @interface AWSMobileAnalyticsERSResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSMobileAnalytics/Info.plist b/AWSMobileAnalytics/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSMobileAnalytics/Info.plist +++ b/AWSMobileAnalytics/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSMobileClient.podspec b/AWSMobileClient.podspec index 426f7334596..982597a628d 100644 --- a/AWSMobileClient.podspec +++ b/AWSMobileClient.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSMobileClient' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSAuthCore', '2.6.23' 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 ad43858b10f..0619f739c6e 100644 --- a/AWSPinpoint.podspec +++ b/AWSPinpoint.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSPinpoint' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' 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 55e69bf68cf..a5ca199ddb4 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.22"; +static NSString *const AWSPinpointAnalyticsSDKVersion = @"2.6.23"; @interface AWSPinpointAnalyticsResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSPinpoint/AWSPinpointTargeting/AWSPinpointTargetingService.m b/AWSPinpoint/AWSPinpointTargeting/AWSPinpointTargetingService.m index 98d85e3a30a..4c5f64a1b6e 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.22"; +static NSString *const AWSPinpointTargetingSDKVersion = @"2.6.23"; @interface AWSPinpointTargetingResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSPinpoint/Info.plist b/AWSPinpoint/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSPinpoint/Info.plist +++ b/AWSPinpoint/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSPolly.podspec b/AWSPolly.podspec index 14866f2b409..2ac15aeb621 100644 --- a/AWSPolly.podspec +++ b/AWSPolly.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSPolly' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSPolly/*.{h,m}' end diff --git a/AWSPolly/AWSPollyService.m b/AWSPolly/AWSPollyService.m index e850aff1c2d..559f9f54089 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.22"; +static NSString *const AWSPollySDKVersion = @"2.6.23"; @interface AWSPollyResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSPolly/AWSPollySynthesizeSpeechURLBuilder.m b/AWSPolly/AWSPollySynthesizeSpeechURLBuilder.m index 491bae444e2..a44b33382c7 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.22"; +static NSString *const AWSPollySDKVersion = @"2.6.23"; NSString *const AWSPollySynthesizeSpeechURLBuilderErrorDomain = @"com.amazonaws.AWSPollySynthesizeSpeechURLBuilderErrorDomain"; NSString *const AWSPollyPresignedUrlPath = @"v1/speech"; diff --git a/AWSPolly/Info.plist b/AWSPolly/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSPolly/Info.plist +++ b/AWSPolly/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSRekognition.podspec b/AWSRekognition.podspec index 1ee1ff21512..7f38268c9b8 100644 --- a/AWSRekognition.podspec +++ b/AWSRekognition.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSRekognition' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSRekognition/*.{h,m}' end diff --git a/AWSRekognition/AWSRekognitionService.m b/AWSRekognition/AWSRekognitionService.m index 22f7bb5e7d6..f80e1d87f5b 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.22"; +static NSString *const AWSRekognitionSDKVersion = @"2.6.23"; @interface AWSRekognitionResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSRekognition/Info.plist b/AWSRekognition/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSRekognition/Info.plist +++ b/AWSRekognition/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSS3.podspec b/AWSS3.podspec index 73b4935b1db..df2e7b14aa4 100644 --- a/AWSS3.podspec +++ b/AWSS3.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSS3' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSS3/*.{h,m}' end diff --git a/AWSS3/AWSS3PreSignedURL.m b/AWSS3/AWSS3PreSignedURL.m index 707bddc67b3..5547d0b0919 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.22"; +static NSString *const AWSS3PreSignedURLBuilderSDKVersion = @"2.6.23"; @interface AWSS3PreSignedURLBuilder() diff --git a/AWSS3/AWSS3Service.m b/AWSS3/AWSS3Service.m index 91f5865a5bc..67b6f538bfc 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.22"; +static NSString *const AWSS3SDKVersion = @"2.6.23"; diff --git a/AWSS3/AWSS3TransferUtility+HeaderHelper.h b/AWSS3/AWSS3TransferUtility+HeaderHelper.h new file mode 100644 index 00000000000..ef52784e1ec --- /dev/null +++ b/AWSS3/AWSS3TransferUtility+HeaderHelper.h @@ -0,0 +1,19 @@ +// +// 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 (HeaderHelper) + +@end diff --git a/AWSS3/AWSS3TransferUtility+HeaderHelper.m b/AWSS3/AWSS3TransferUtility+HeaderHelper.m new file mode 100644 index 00000000000..e74ab317fb0 --- /dev/null +++ b/AWSS3/AWSS3TransferUtility+HeaderHelper.m @@ -0,0 +1,152 @@ +// +// 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+HeaderHelper.h" +#import "AWSS3TransferUtilityTasks.h" + +@interface AWSS3CreateMultipartUploadRequest() ++ (NSValueTransformer *)ACLJSONTransformer; ++ (NSValueTransformer *)storageClassJSONTransformer; ++ (NSValueTransformer *)serverSideEncryptionJSONTransformer; ++ (NSValueTransformer *)requestPayerJSONTransformer; ++ (NSValueTransformer *)expiresJSONTransformer; +@end + +@interface AWSS3TransferUtilityExpression() + +@property (strong, nonatomic) NSMutableDictionary *internalRequestHeaders; + +@property (strong, nonatomic) NSMutableDictionary *internalRequestParameters; + +- (void)assignRequestParameters:(AWSS3GetPreSignedURLRequest *)getPreSignedURLRequest; +- (void)assignRequestHeaders:(AWSS3GetPreSignedURLRequest *)getPreSignedURLRequest; + +@end + +@interface AWSS3TransferUtilityUploadExpression() + +@property (copy, atomic) AWSS3TransferUtilityUploadCompletionHandlerBlock completionHandler; + +@end + +@interface AWSS3TransferUtilityMultiPartUploadExpression() + +@property (strong, nonatomic) NSMutableDictionary *internalRequestHeaders; +@property (strong, nonatomic) NSMutableDictionary *internalRequestParameters; +- (void)assignRequestParameters:(AWSS3GetPreSignedURLRequest *)getPreSignedURLRequest; +@property (copy, atomic) AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock completionHandler; + +@end + +@implementation AWSS3TransferUtility (HeaderHelper) + +-(void) propagateHeaderInformation: (AWSS3CreateMultipartUploadRequest *) uploadRequest + expression: (AWSS3TransferUtilityMultiPartUploadExpression *) expression { + + //Propagate header info and add custom metadata + NSMutableDictionary *metadata = [NSMutableDictionary new]; + for (NSString *key in expression.requestHeaders) { + NSString *lKey = [key lowercaseString]; + if ([lKey hasPrefix:@"x-amz-meta"]) { + [metadata setValue:expression.requestHeaders[key] forKey:[key stringByReplacingOccurrencesOfString:@"x-amz-meta-" withString:@""]]; + } + else if ([lKey isEqualToString:@"x-amz-acl"]) { + NSValueTransformer *transformer = [AWSS3CreateMultipartUploadRequest ACLJSONTransformer]; + uploadRequest.ACL = (AWSS3ObjectCannedACL)[[transformer transformedValue:expression.requestHeaders[key]] integerValue]; + } + else if ([lKey isEqualToString:@"x-amz-grant-read" ]) { + uploadRequest.grantRead = expression.requestHeaders[key]; + } + else if ([lKey isEqualToString:@"x-amz-grant-read-acp" ]) { + uploadRequest.grantReadACP = expression.requestHeaders[key]; + } + else if ([lKey isEqualToString:@"x-amz-grant-read-acp" ]) { + uploadRequest.grantReadACP = expression.requestHeaders[key]; + } + else if ([lKey isEqualToString:@"x-amz-grant-write-acp" ]) { + uploadRequest.grantWriteACP = expression.requestHeaders[key]; + } + else if ([lKey isEqualToString:@"x-amz-grant-full-control" ]) { + uploadRequest.grantFullControl = expression.requestHeaders[key]; + } + else if ([lKey isEqualToString:@"x-amz-server-side-encryption" ]) { + NSValueTransformer *transformer = [AWSS3CreateMultipartUploadRequest serverSideEncryptionJSONTransformer]; + uploadRequest.serverSideEncryption = (AWSS3ServerSideEncryption)[[transformer transformedValue:expression.requestHeaders[key]] integerValue]; + } + else if ([lKey isEqualToString:@"x-amz-server-side-encryption-aws-kms-key-id" ]) { + uploadRequest.SSEKMSKeyId = expression.requestHeaders[key]; + } + else if ([lKey isEqualToString:@"x-amz-server-side​-encryption​-customer-algorithm" ]) { + uploadRequest.SSECustomerAlgorithm = expression.requestHeaders[key]; + } + else if ([lKey isEqualToString:@"x-amz-server-side​-encryption​-customer-key" ]) { + uploadRequest.SSECustomerKey = expression.requestHeaders[key]; + } + else if ([lKey isEqualToString:@"x-amz-server-side​-encryption​-customer-key-MD5" ]) { + uploadRequest.SSECustomerKeyMD5 = expression.requestHeaders[key]; + } + else if ([lKey isEqualToString:@"content-encoding" ]) { + uploadRequest.contentEncoding = expression.requestHeaders[key]; + } + else if ([lKey isEqualToString:@"content-type" ]) { + uploadRequest.contentType = expression.requestHeaders[key]; + } + else if([lKey isEqualToString:@"cache-control"]) { + uploadRequest.cacheControl = expression.requestHeaders[key]; + } + else if ([lKey isEqualToString:@"x-amz-request-payer" ]) { + NSValueTransformer *transformer = [AWSS3CreateMultipartUploadRequest requestPayerJSONTransformer]; + uploadRequest.requestPayer = (AWSS3RequestPayer)[[transformer transformedValue:expression.requestHeaders[key]] integerValue]; + } + else if ([lKey isEqualToString:@"expires" ]) { + NSValueTransformer *transformer = [AWSS3CreateMultipartUploadRequest expiresJSONTransformer]; + uploadRequest.expires = [transformer transformedValue:expression.requestHeaders[key]]; + } + else if ([lKey isEqualToString:@"x-amz-storage-class" ]) { + NSValueTransformer *transformer = [AWSS3CreateMultipartUploadRequest storageClassJSONTransformer]; + uploadRequest.storageClass = (AWSS3StorageClass)[[transformer transformedValue:expression.requestHeaders[key]] integerValue]; + } + else if ([lKey isEqualToString:@"x-amz-website-redirect-location" ]) { + uploadRequest.websiteRedirectLocation = expression.requestHeaders[key]; + } + else if ([lKey isEqualToString:@"x-amz-tagging" ]) { + uploadRequest.tagging = expression.requestHeaders[key]; + } + } + uploadRequest.metadata = metadata; +} + +-(void) filterAndAssignHeaders:(NSDictionary *) requestHeaders + getPresignedURLRequest:(AWSS3GetPreSignedURLRequest *) getPresignedURLRequest + URLRequest: (NSMutableURLRequest *) URLRequest { + + NSSet *disallowedHeaders = [[NSSet alloc] initWithArray: + @[@"x-amz-acl", @"x-amz-tagging", @"x-amz-storage-class", @"x-amz-server-side-encryption"]]; + + for (NSString *key in requestHeaders) { + //Do not include custom metadata or custom grants + NSString *lKey = [key lowercaseString]; + if ([ lKey hasPrefix:@"x-amz-meta"] || [lKey hasPrefix:@"x-amz-grant"]) { + continue; + } + if ([disallowedHeaders containsObject:lKey]) { + continue; + } + [getPresignedURLRequest setValue:requestHeaders[key] forRequestHeader:key]; + [URLRequest setValue:requestHeaders[key] forHTTPHeaderField:key]; + } +} + +@end diff --git a/AWSS3/AWSS3TransferUtility+Validation.h b/AWSS3/AWSS3TransferUtility+Validation.h index 4624bda37c3..d320356f381 100644 --- a/AWSS3/AWSS3TransferUtility+Validation.h +++ b/AWSS3/AWSS3TransferUtility+Validation.h @@ -16,6 +16,5 @@ #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 index b12df40a922..bcd8ba3f68f 100644 --- a/AWSS3/AWSS3TransferUtility+Validation.m +++ b/AWSS3/AWSS3TransferUtility+Validation.m @@ -37,7 +37,6 @@ - (AWSTask *) validateParameters: (NSString * )bucket fileURL:(NSURL *)fileURL a // 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]]; diff --git a/AWSS3/AWSS3TransferUtility.h b/AWSS3/AWSS3TransferUtility.h index 023272341be..ff7d45714c2 100644 --- a/AWSS3/AWSS3TransferUtility.h +++ b/AWSS3/AWSS3TransferUtility.h @@ -1,5 +1,5 @@ // -// Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// 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. @@ -15,8 +15,8 @@ #import #import - #import "AWSS3Service.h" +#import "AWSS3TransferUtilityTasks.h" NS_ASSUME_NONNULL_BEGIN @@ -30,6 +30,7 @@ typedef NS_ENUM(NSInteger, AWSS3TransferUtilityErrorType) { }; + FOUNDATION_EXPORT NSString *const AWSS3TransferUtilityURLSessionDidBecomeInvalidNotification; @class AWSS3TransferUtilityConfiguration; @@ -42,59 +43,6 @@ FOUNDATION_EXPORT NSString *const AWSS3TransferUtilityURLSessionDidBecomeInvalid @class AWSS3TransferUtilityMultiPartUploadExpression; @class AWSS3TransferUtilityDownloadExpression; - -/** - The upload completion handler. - - @param task The upload task object. - @param error Returns the error object when the download failed. - */ -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. - - @param task The download task object. - @param location When downloading an Amazon S3 object to a file, returns a file URL of the returned object. Otherwise, returns `nil`. - @param data When downloading an Amazon S3 object as an `NSData`, returns the returned object as an instance of `NSData`. Otherwise, returns `nil`. - @param error Returns the error object when the download failed. Returns `nil` on successful downlaod. - */ -typedef void (^AWSS3TransferUtilityDownloadCompletionHandlerBlock) (AWSS3TransferUtilityDownloadTask *task, - NSURL * _Nullable location, - NSData * _Nullable data, - NSError * _Nullable error); - -/** - The transfer progress feedback block. - - @param task The upload task object. - @param progress The progress object. - - @note Refer to `- URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:` in `NSURLSessionTaskDelegate` for more details on upload progress and `- URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:` in `NSURLSessionDownloadDelegate` for more details on download progress. - */ -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 /** @@ -580,224 +528,4 @@ handleEventsForBackgroundURLSession:(NSString *)identifier @end - -#pragma mark - AWSS3TransferUtilityTasks - -/** - The task object to represent a upload or download task. - */ -@interface AWSS3TransferUtilityTask : NSObject - -/** - An identifier uniquely identifies the transferID. - */ - -@property (readonly) NSString *transferID; - -/** - An identifier uniquely identifies the task within a given `AWSS3TransferUtility` instance. - */ -@property (readonly) NSUInteger taskIdentifier; - -/** - 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; - -/** - The underlying `NSURLSessionTask` object. - */ -@property (readonly) NSURLSessionTask *sessionTask; - -/** - The HTTP request object. - */ -@property (nullable, readonly) NSURLRequest *request; - -/** - The HTTP response object. May be nil if no response has been received. - */ -@property (nullable, readonly) NSHTTPURLResponse *response; - -/** - 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 upload task. - */ -@interface AWSS3TransferUtilityUploadTask : AWSS3TransferUtilityTask - -@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. - */ -@interface AWSS3TransferUtilityDownloadTask : AWSS3TransferUtilityTask - -@end - -#pragma mark - AWSS3TransferUtilityExpressions - -/** - The expression object for configuring a upload or download task. - */ -@interface AWSS3TransferUtilityExpression : 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) AWSS3TransferUtilityProgressBlock 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 upload task. - */ -@interface AWSS3TransferUtilityUploadExpression : AWSS3TransferUtilityExpression - -/** - The upload request header for `Content-MD5`. - */ -@property (nonatomic, nullable) NSString *contentMD5; - -@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. - */ -@interface AWSS3TransferUtilityDownloadExpression : AWSS3TransferUtilityExpression - -@end - NS_ASSUME_NONNULL_END diff --git a/AWSS3/AWSS3TransferUtility.m b/AWSS3/AWSS3TransferUtility.m index 16155c52a25..b8f74bd8350 100644 --- a/AWSS3/AWSS3TransferUtility.m +++ b/AWSS3/AWSS3TransferUtility.m @@ -1,5 +1,5 @@ // -// Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// 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. @@ -18,9 +18,10 @@ #import "AWSS3Service.h" #import "AWSSynchronizedMutableDictionary.h" #import "AWSXMLDictionary.h" +#import "AWSS3TransferUtilityDatabaseHelper.h" +#import "AWSS3TransferUtilityTasks.h" #import "AWSFMDB.h" -#import "AWSS3TransferUtility+Validation.h" // Public constants NSString *const AWSS3TransferUtilityErrorDomain = @"com.amazonaws.AWSS3TransferUtilityErrorDomain"; @@ -38,10 +39,8 @@ static NSString *const AWSS3TransferUtiltityRequestTimeoutErrorCode = @"RequestTimeout"; static int const AWSS3TransferUtilityMultiPartDefaultConcurrencyLimit = 5; -#pragma mark - Private classes -@interface AWSS3TransferUtilityUploadSubTask: NSObject -@end +#pragma mark - Private classes @interface AWSS3TransferUtilityUploadSubTask() @property (strong, nonatomic) NSURLSessionTask *sessionTask; @@ -53,10 +52,10 @@ @interface AWSS3TransferUtilityUploadSubTask() @property NSString *responseData; @property NSString *file; @property NSString *transferID; -@property NSString *status; +@property AWSS3TransferUtilityTransferStatusType status; @property NSString *uploadID; -@end +@end @interface AWSS3TransferUtility() @@ -68,6 +67,7 @@ @interface AWSS3TransferUtility() *) requestHeaders + getPresignedURLRequest:(AWSS3GetPreSignedURLRequest *) getPresignedURLRequest + URLRequest: (NSMutableURLRequest *) URLRequest; @end + + #pragma mark - AWSS3TransferUtility @implementation AWSS3TransferUtility @@ -351,189 +399,148 @@ - (instancetype)initWithConfiguration:(AWSServiceConfiguration *)serviceConfigur } //Instantiate the Database Helper - self.databaseQueue = [self createDatabase]; + self.databaseQueue = [AWSS3TransferUtilityDatabaseHelper createDatabase:_cacheDirectoryPath]; + + //Recover the state from the previous time this was instantiated + [self recover]; } 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 { +- (void) recover { + AWSDDLogDebug(@"In Recovery for TU Session [%@]", _sessionIdentifier); //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]; + //This dictionary will contain the master level info for a multipart transfer + NSMutableDictionary *tempMultiPartMasterTaskDictionary = [NSMutableDictionary new]; + //This dictionary will contain details of indvidual transfers ( upload, downloads and subtasks) + NSMutableDictionary *tempTransferDictionary = [NSMutableDictionary new]; + + //Hydrate from DB + [self hydrateFromDB:tempMultiPartMasterTaskDictionary + tempTransferDictionary:tempTransferDictionary]; + //Link Transfers to NSURL Session. + [self linkTransfersToNSURLSession:tempMultiPartMasterTaskDictionary tempTransferDictionary:tempTransferDictionary]; +} + +- (void) hydrateFromDB:(NSMutableDictionary *) tempMultiPartMasterTaskDictionary + tempTransferDictionary: (NSMutableDictionary *) tempTransferDictionary +{ + //Get All Tasks from DB + NSMutableArray *tasks = [AWSS3TransferUtilityDatabaseHelper getTransferTaskDataFromDB:_sessionIdentifier databaseQueue:_databaseQueue]; - //Iterate through the tasks and populate + //Iterate through the tasks and populate transferRequests and Multipart dictionary. 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_name"]; - 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"]; + AWSS3TransferUtilityUploadTask *transferUtilityUploadTask = [self hydrateUploadTask:task sessionIdentifier:self.sessionIdentifier databaseQueue:self.databaseQueue]; - //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; - } + //If task is completed, no more processing is required. + if (transferUtilityUploadTask.status == AWSS3TransferUtilityTransferStatusCompleted ) { + [self.completedTaskDictionary setObject:transferUtilityUploadTask forKey:transferUtilityUploadTask.transferID]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityUploadTask.transferID databaseQueue:self->_databaseQueue]; + continue; } - //Lodge in temporary Dictionary - [transferRequests setObject:transferUtilityUploadTask forKey:@(sessionTaskID)]; + [tempTransferDictionary setObject:transferUtilityUploadTask forKey:@(sessionTaskID)]; + AWSDDLogDebug(@"Found upload [%@] with taskIdentifier [%d]",transferUtilityUploadTask.transferID,sessionTaskID ); } else if ([transferType isEqualToString:@"DOWNLOAD"]) { - AWSS3TransferUtilityDownloadTask *transferUtilityDownloadTask = [AWSS3TransferUtilityDownloadTask new]; - transferUtilityDownloadTask.nsURLSessionID = self.sessionIdentifier; - transferUtilityDownloadTask.databaseQueue = self.databaseQueue; - transferUtilityDownloadTask.bucket = [task objectForKey:@"bucket_name"]; - 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"]; + AWSS3TransferUtilityDownloadTask *transferUtilityDownloadTask = [self hydrateDownloadTask:task sessionIdentifier:self.sessionIdentifier databaseQueue:self.databaseQueue]; - //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; - } + //If task is completed, no more processing is required. + if (transferUtilityDownloadTask.status == AWSS3TransferUtilityTransferStatusCompleted ) { + [self.completedTaskDictionary setObject:transferUtilityDownloadTask forKey:transferUtilityDownloadTask.transferID]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityDownloadTask.transferID databaseQueue:self->_databaseQueue]; + continue; } - - //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); + //Lodge in temporary Dictionary for linking + [tempTransferDictionary setObject:transferUtilityDownloadTask forKey:@(sessionTaskID)]; + AWSDDLogDebug(@"Found download [%@] with taskIdentifier [%d]",transferUtilityDownloadTask.transferID,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_name"]; - 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"]; + AWSS3TransferUtilityMultiPartUploadTask *transferUtilityMultiPartUploadTask = [self hydrateMultiPartUploadTask:task sessionIdentifier:self.sessionIdentifier databaseQueue:self.databaseQueue]; - //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; - } + //If task is completed, no more processing is required. + if (transferUtilityMultiPartUploadTask.status == AWSS3TransferUtilityTransferStatusCompleted ) { + [self.completedTaskDictionary setObject:transferUtilityMultiPartUploadTask forKey:transferUtilityMultiPartUploadTask.transferID]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityMultiPartUploadTask.transferID databaseQueue:self->_databaseQueue]; + continue; } - [multiPartUploads setObject:transferUtilityMultiPartUploadTask forKey:transferUtilityMultiPartUploadTask.uploadID]; + //Lodge in temporary Dictionary for linking + [tempMultiPartMasterTaskDictionary setObject:transferUtilityMultiPartUploadTask forKey:transferUtilityMultiPartUploadTask.uploadID]; + AWSDDLogDebug(@"Found MultiPartUpload [%@] with taskIdentifier [%d]",transferUtilityMultiPartUploadTask.transferID,sessionTaskID ); } 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]; + AWSS3TransferUtilityUploadSubTask *subTask = [self hydrateMultiPartUploadSubTask:task sessionTaskID:sessionTaskID]; + AWSDDLogDebug(@"Found MultiPartUpload SubTask [%@] with taskIdentifier [%d]",subTask.transferID,sessionTaskID ); - //Lodge in temporary Dictionary - [transferRequests setObject:subTask forKey:@(sessionTaskID)]; - + //Get the Master MultiPart record from the Dictionary. + AWSS3TransferUtilityMultiPartUploadTask *multiPartUploadTask = [tempMultiPartMasterTaskDictionary objectForKey:subTask.uploadID]; + if ( !multiPartUploadTask ) { + //Couldn't find the multipart upload master record. Must be an orphan part record. Clean up the DB and continue. + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:subTask.transferID databaseQueue:self->_databaseQueue]; + continue; + } + //Check if the subTask is is already completed. If it is, add it to the completed parts list, update the progress object and go to the next iteration of the loop + if (subTask.status== AWSS3TransferUtilityTransferStatusCompleted) { + [multiPartUploadTask.completedPartsDictionary setObject:subTask forKey:@(sessionTaskID)]; + multiPartUploadTask.progress.completedUnitCount += subTask.totalBytesExpectedToSend; + continue; + } + + //Check if the subTask is in Waiting status. If it is, add it to the waiting parts list and go to the next iteration of the loop. + if (subTask.status == AWSS3TransferUtilityTransferStatusWaiting) { + [multiPartUploadTask.waitingPartsDictionary setObject:subTask forKey:subTask.partNumber]; + continue; + } + + //The subTask must be in In_Progress status. Lodge it in temporary Dictionary for linking. + [tempTransferDictionary setObject:subTask forKey:@(sessionTaskID)]; } } - - //Reattach to the NSURLsession objects +} + +- (void) linkTransfersToNSURLSession:(NSMutableDictionary *) tempMultiPartMasterTaskDictionary + tempTransferDictionary: (NSMutableDictionary *) tempTransferDictionary { + //Get tasks from the NSURLSession and reattach to them. + //getTasksWithCompletionHandler is an ansynchronous task, so the thread that is calling this method will not be blocked. [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 ) { - + AWSDDLogDebug(@"Iterating through task Identifier [%lu]", (unsigned long)task.taskIdentifier); //Get the Task - id obj = [transferRequests objectForKey:@(task.taskIdentifier)]; + id obj = [tempTransferDictionary 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)); + AWSDDLogDebug(@"Status is %ld", (long)uploadTask.status); - //Remove this object from the transferRequests list - [transferRequests removeObjectForKey:@(task.taskIdentifier)]; + //Remove this object from the tempTransferDictionary list + [tempTransferDictionary 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 (uploadTask.status == AWSS3TransferUtilityTransferStatusInProgress) { + //Check if the the underlying NSURLSession 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.completedTaskDictionary setObject:uploadTask forKey:uploadTask.transferID]; [self.taskDictionary removeObjectForKey:@(uploadTask.taskIdentifier)]; if (uploadTask.temporaryFileCreated) { [self removeFile:uploadTask.file]; } - [self deleteTransferRequestFromDB:uploadTask.transferID databaseQueue:self->_databaseQueue]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:uploadTask.transferID databaseQueue:self->_databaseQueue]; if(uploadTask.expression.completionHandler) { uploadTask.expression.completionHandler(uploadTask,nil); } @@ -549,54 +556,44 @@ - (void) recover:(void (^)(AWSS3TransferUtilityUploadTask *uploadTask, } } else if ([obj isKindOfClass:[AWSS3TransferUtilityUploadSubTask class]]) { - //Found a upload subtask. + //Found a upload subtask. + AWSDDLogDebug(@"Looking at NSURLSession Upload SubTask [%lu]", (unsigned long)task.taskIdentifier); AWSS3TransferUtilityUploadSubTask *subTaskObj = obj; subTaskObj.sessionTask = task; - AWSS3TransferUtilityMultiPartUploadTask *multiPartUploadTask = [multiPartUploads objectForKey:subTaskObj.uploadID]; + AWSS3TransferUtilityMultiPartUploadTask *multiPartUploadTask = [tempMultiPartMasterTaskDictionary 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; - } + //Remove this object from the tempTransferDictionary + [tempTransferDictionary removeObjectForKey:@(task.taskIdentifier)]; - //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]) { + if (subTaskObj.status == AWSS3TransferUtilityTransferStatusPaused) { 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); + AWSDDLogDebug(@"SubTask %lu is in %ld status according to DB, but the underlying task is not running. Retrying", (unsigned long)subTaskObj.taskIdentifier, + (long)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); + AWSDDLogWarn(@"NSURLSession task[%lu] is not found in the taskDictionary. Ignoring.",(unsigned long)task.taskIdentifier); } } + //Loop through all the Download tasks for( NSURLSessionDownloadTask *task in downloadTasks ) { - id obj = [self.taskDictionary objectForKey:@(task.taskIdentifier)]; + id obj = [tempTransferDictionary objectForKey:@(task.taskIdentifier)]; + AWSDDLogDebug(@"Looking at NSURLSession Download Task [%lu]", (unsigned long)task.taskIdentifier); if ([obj isKindOfClass:[AWSS3TransferUtilityDownloadTask class]]) { //Found a download task @@ -605,12 +602,18 @@ - (void) recover:(void (^)(AWSS3TransferUtilityUploadTask *uploadTask, [self.taskDictionary setObject:downloadTask forKey:@(downloadTask.taskIdentifier)]; //Remove this request from the transferRequests list. - [transferRequests removeObjectForKey:@(task.taskIdentifier)]; + [tempTransferDictionary removeObjectForKey:@(task.taskIdentifier)]; //Check if this is in progress - if ([downloadTask.status isEqualToString:AWSS3TransferUtilityInProgressStatus]) { + if (downloadTask.status == AWSS3TransferUtilityTransferStatusInProgress) { + if ([task state] == NSURLSessionTaskStateCompleted) { + [self.completedTaskDictionary setObject:downloadTask forKey:downloadTask.transferID]; + [self.taskDictionary removeObjectForKey:@(downloadTask.taskIdentifier)]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:downloadTask.transferID databaseQueue:self->_databaseQueue]; + continue; + } //Check if the underlying task's status is not in Progress. - if ([task state] != NSURLSessionTaskStateRunning) { + else 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]; @@ -623,16 +626,206 @@ - (void) recover:(void (^)(AWSS3TransferUtilityUploadTask *uploadTask, } } - //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. + //We have run through all the Session Tasks and removed the matching records from the multiPartUploads and transferRequests dictionaries. + //Handle any stragglers. + [self handleUnlinkedTransfers:tempMultiPartMasterTaskDictionary tempTransferDictionary:tempTransferDictionary]; - //Call the completion handler if one was provided. - if (completionHandler) { - completionHandler(nil); + }]; +} + + +- (void) handleUnlinkedTransfers:(NSMutableDictionary *) tempMultiPartMasterTaskDictionary + tempTransferDictionary: (NSMutableDictionary *) tempTransferDictionary { + //At this point, we have finished iterating through the tasks present in the NSURLSession and removed all the matching ones from the transferRequests dictionary. + //If there are any left in the transferRequests list, it means that we think they are running, but NSURLSession doesn't know about them. + for (id taskIdentifier in [tempTransferDictionary allKeys]) { + AWSDDLogDebug(@"No sessionTask found for taskIdentifier %@",taskIdentifier); + id obj = [tempTransferDictionary objectForKey:taskIdentifier]; + if ([obj isKindOfClass:[AWSS3TransferUtilityUploadTask class]]) + { + //Delete the transfer record from the DB + AWSS3TransferUtilityUploadTask *transferUtilityUploadTask = obj; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityUploadTask.transferID taskIdentifier:[taskIdentifier integerValue] databaseQueue:self->_databaseQueue ]; + AWSDDLogDebug(@"Deleted transfer request from the DB"); + + if (transferUtilityUploadTask.status == AWSS3TransferUtilityTransferStatusCompleted ) { + [self.completedTaskDictionary setObject:transferUtilityUploadTask forKey:transferUtilityUploadTask.transferID]; + } + //Check if the input file for the transfer exists. + else if ( [[NSFileManager defaultManager] fileExistsAtPath:transferUtilityUploadTask.file]) { + //If the transfer was paused, create another NSURLSession task and leave it in an paused state + if (transferUtilityUploadTask.status == AWSS3TransferUtilityTransferStatusPaused ) { + [ self createUploadTask:transferUtilityUploadTask startTransfer:NO]; + [AWSS3TransferUtilityDatabaseHelper updateTransferRequestStatusInDB:transferUtilityUploadTask.transferID + taskIdentifier:transferUtilityUploadTask.taskIdentifier + status:AWSS3TransferUtilityTransferStatusPaused + databaseQueue:self.databaseQueue]; + } + else { + //Transfer is in progress according to us, but not present in the NSURLSession. It may have been sucessfully completed. Do not retry. + //The app developer should check to see if the S3 file was uploaded in the app logic and reinitate the transfer if required. + } + } } + else if([obj isKindOfClass:[AWSS3TransferUtilityUploadSubTask class]]) + { + AWSS3TransferUtilityUploadSubTask *subTask = obj; + //We think the subtask is in progress, but NSURLSession does not know about it. So lets retry. + //An optimization here is to check if the part has been already uploaded by querying S3 and only retry if not already uploaded. + + AWSS3TransferUtilityMultiPartUploadTask *multiPartUploadTask = [tempMultiPartMasterTaskDictionary objectForKey:subTask.uploadID]; + [self retryUploadSubTask: multiPartUploadTask subTask:subTask]; + } + else if ([obj isKindOfClass:[AWSS3TransferUtilityDownloadTask class]]) { + + AWSS3TransferUtilityDownloadTask *downloadTask = obj; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:downloadTask.transferID taskIdentifier:[taskIdentifier integerValue] databaseQueue:self->_databaseQueue ]; + AWSDDLogDebug(@"Deleted transfer request from DB"); + + if (downloadTask.status == AWSS3TransferUtilityTransferStatusCompleted ) { + [self.completedTaskDictionary setObject:downloadTask forKey:downloadTask.transferID]; + } + + else if (downloadTask.status == AWSS3TransferUtilityTransferStatusPaused) { + [ self createDownloadTask:downloadTask startTransfer:NO]; + [AWSS3TransferUtilityDatabaseHelper updateTransferRequestStatusInDB:downloadTask.transferID + taskIdentifier:downloadTask.taskIdentifier + status:AWSS3TransferUtilityTransferStatusPaused + databaseQueue:self.databaseQueue]; + [downloadTask suspend]; + } + else { + //Transfer is in progress according to us, but not present in the NSURLSession. It may have been sucessfully completed. Do not retry. + //The app developer should check to see if the S3 file was uploaded in the app logic and reinitate the transfer if required. + } + } + } + + //Multipart transfer uses a relay style architecture. At any point in time, n parts are in progress and each part triggers the next part to start when it is finished. + //During the recovery procees, it is possible for the multipart transfer to not have an adequate number of parts in progress. + //This loop below will check and ensure that the correct number of concurrent transfers are in progress. + for (id obj in [tempMultiPartMasterTaskDictionary allKeys]) { + NSString *uploadID = obj; + AWSS3TransferUtilityMultiPartUploadTask *multiPartUploadTask = [tempMultiPartMasterTaskDictionary objectForKey:uploadID]; + + if (multiPartUploadTask.status == AWSS3TransferUtilityTransferStatusPaused) { + continue; + } + + long numberOfPartsInProgress = [multiPartUploadTask.inProgressPartsDictionary count]; + while (numberOfPartsInProgress < [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { + if ([multiPartUploadTask.waitingPartsDictionary count] > 0) { + //Get a part from the waitingList + AWSS3TransferUtilityUploadSubTask *nextSubTask = [[multiPartUploadTask.waitingPartsDictionary allValues] objectAtIndex:0]; - }]; + //Remove it from the waitingList + [multiPartUploadTask.waitingPartsDictionary removeObjectForKey:nextSubTask.partNumber]; + + //Create the subtask and start the transfer + NSError *error = [self createUploadSubTask:multiPartUploadTask subTask:nextSubTask]; + if (error) { + //Abort the request, so the server can clean up any partials. + [self callAbortMultiPartForUploadTask:multiPartUploadTask]; + if (multiPartUploadTask.expression.completionHandler) { + multiPartUploadTask.expression.completionHandler(multiPartUploadTask, error); + } + //Clean up. + [self cleanupForMultiPartUploadTask:multiPartUploadTask]; + break; + }; + numberOfPartsInProgress++; + } + else { + break; + } + } + } +} + +-(AWSS3TransferUtilityUploadTask *) hydrateUploadTask: (NSMutableDictionary *) task + sessionIdentifier: (NSString *) sessionIdentifier + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue +{ + AWSS3TransferUtilityUploadTask *transferUtilityUploadTask = [AWSS3TransferUtilityUploadTask new]; + transferUtilityUploadTask.nsURLSessionID = sessionIdentifier; + transferUtilityUploadTask.databaseQueue = databaseQueue; + transferUtilityUploadTask.bucket = [task objectForKey:@"bucket_name"]; + transferUtilityUploadTask.key = [task objectForKey:@"key"]; + transferUtilityUploadTask.expression = [AWSS3TransferUtilityUploadExpression new]; + transferUtilityUploadTask.expression.internalRequestHeaders = [[AWSS3TransferUtilityDatabaseHelper getDictionaryFromJson:[task objectForKey:@"request_headers"]] mutableCopy]; + transferUtilityUploadTask.expression.internalRequestParameters = [[AWSS3TransferUtilityDatabaseHelper 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]; + NSNumber *statusValue = [task objectForKey:@"status"]; + transferUtilityUploadTask.status = [statusValue intValue]; + return transferUtilityUploadTask; +} + + +- (AWSS3TransferUtilityDownloadTask *) hydrateDownloadTask: (NSMutableDictionary *) task + sessionIdentifier: (NSString *) sessionIdentifier + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue +{ + AWSS3TransferUtilityDownloadTask *transferUtilityDownloadTask = [AWSS3TransferUtilityDownloadTask new]; + transferUtilityDownloadTask.nsURLSessionID = sessionIdentifier; + transferUtilityDownloadTask.databaseQueue = databaseQueue; + transferUtilityDownloadTask.bucket = [task objectForKey:@"bucket_name"]; + transferUtilityDownloadTask.key = [task objectForKey:@"key"]; + transferUtilityDownloadTask.expression = [AWSS3TransferUtilityDownloadExpression new]; + transferUtilityDownloadTask.expression.internalRequestHeaders = [[AWSS3TransferUtilityDatabaseHelper getDictionaryFromJson:[task objectForKey:@"request_headers"]] mutableCopy]; + transferUtilityDownloadTask.expression.internalRequestParameters = [[AWSS3TransferUtilityDatabaseHelper 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]; + NSNumber *statusValue = [task objectForKey:@"status"]; + transferUtilityDownloadTask.status = [statusValue intValue]; + return transferUtilityDownloadTask; +} + + +-( AWSS3TransferUtilityMultiPartUploadTask *) hydrateMultiPartUploadTask: (NSMutableDictionary *) task + sessionIdentifier: (NSString *) sessionIdentifier + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue +{ + AWSS3TransferUtilityMultiPartUploadTask *transferUtilityMultiPartUploadTask = [AWSS3TransferUtilityMultiPartUploadTask new]; + transferUtilityMultiPartUploadTask.nsURLSessionID = sessionIdentifier; + transferUtilityMultiPartUploadTask.databaseQueue = databaseQueue; + transferUtilityMultiPartUploadTask.bucket = [task objectForKey:@"bucket_name"]; + transferUtilityMultiPartUploadTask.key = [task objectForKey:@"key"]; + transferUtilityMultiPartUploadTask.expression = [AWSS3TransferUtilityMultiPartUploadExpression new]; + transferUtilityMultiPartUploadTask.expression.internalRequestHeaders = [[AWSS3TransferUtilityDatabaseHelper getDictionaryFromJson:[task objectForKey:@"request_headers"]] mutableCopy]; + transferUtilityMultiPartUploadTask.expression.internalRequestParameters = [[AWSS3TransferUtilityDatabaseHelper 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"]; + NSNumber *statusValue = [task objectForKey:@"status"]; + transferUtilityMultiPartUploadTask.status = [statusValue intValue]; + return transferUtilityMultiPartUploadTask; +} + +- (AWSS3TransferUtilityUploadSubTask * ) hydrateMultiPartUploadSubTask:(NSMutableDictionary *) task + sessionTaskID: (int) sessionTaskID +{ + 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.transferID = [task objectForKey:@"transfer_id"]; + subTask.totalBytesExpectedToSend = [[task objectForKey:@"content_length"] integerValue]; + + NSNumber *statusValue = [task objectForKey:@"status"]; + subTask.status = [statusValue intValue]; + return subTask; } @@ -750,11 +943,17 @@ - (void) recover:(void (^)(AWSS3TransferUtilityUploadTask *uploadTask, transferUtilityUploadTask.cancelled = NO; transferUtilityUploadTask.temporaryFileCreated = temporaryFileCreated; transferUtilityUploadTask.responseData = @""; + transferUtilityUploadTask.status = AWSS3TransferUtilityTransferStatusInProgress; return [self createUploadTask:transferUtilityUploadTask]; } -(AWSTask *) createUploadTask: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTask { + return [self createUploadTask:transferUtilityUploadTask startTransfer:YES]; +} + + +-(AWSTask *) createUploadTask: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTask startTransfer:(BOOL) startTransfer { //Create PreSigned URL Request AWSS3GetPreSignedURLRequest *getPreSignedURLRequest = [AWSS3GetPreSignedURLRequest new]; getPreSignedURLRequest.bucket = transferUtilityUploadTask.bucket; @@ -768,8 +967,13 @@ - (void) recover:(void (^)(AWSS3TransferUtilityUploadTask *uploadTask, [transferUtilityUploadTask.expression assignRequestHeaders:getPreSignedURLRequest]; [transferUtilityUploadTask.expression assignRequestParameters:getPreSignedURLRequest]; - return [[self.preSignedURLBuilder getPreSignedURL:getPreSignedURLRequest] continueWithSuccessBlock:^id(AWSTask *task) { + return [[self.preSignedURLBuilder getPreSignedURL:getPreSignedURLRequest] continueWithBlock:^id(AWSTask *task) { NSURL *presignedURL = task.result; + NSError *error = task.error; + if ( error ) { + AWSDDLogError(@"Error: %@", error); + return [AWSTask taskWithError:error]; + } NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:presignedURL]; request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData; @@ -792,8 +996,12 @@ - (void) recover:(void (^)(AWSS3TransferUtilityUploadTask *uploadTask, [self.taskDictionary setObject:transferUtilityUploadTask forKey:@(transferUtilityUploadTask.sessionTask.taskIdentifier) ]; //Add to Database - [self insertUploadTransferRequestInDB:transferUtilityUploadTask databaseQueue:self->_databaseQueue]; - [uploadTask resume]; + [AWSS3TransferUtilityDatabaseHelper insertUploadTransferRequestInDB:transferUtilityUploadTask databaseQueue:self->_databaseQueue]; + + if (startTransfer) { + [uploadTask resume]; + } + return [AWSTask taskWithResult:transferUtilityUploadTask]; }]; } @@ -804,14 +1012,29 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa [self.taskDictionary removeObjectForKey:@(transferUtilityUploadTask.taskIdentifier)]; //Remove from Database - [self deleteTransferRequestFromDB:transferUtilityUploadTask.transferID taskIdentifier:transferUtilityUploadTask.taskIdentifier databaseQueue:_databaseQueue ]; + [AWSS3TransferUtilityDatabaseHelper 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]; + //Check if the file to be uploaded still exists. Otherwise, fail the transfer and call the completion handler with the error. + if (![[NSFileManager defaultManager] fileExistsAtPath:transferUtilityUploadTask.file]) { + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:@"Local file not found" + forKey:@"Message"]; + + NSError *error = [NSError errorWithDomain:AWSS3TransferUtilityErrorDomain + code:AWSS3TransferUtilityErrorLocalFileNotFound + userInfo:userInfo]; + + if (transferUtilityUploadTask.expression.completionHandler) { + transferUtilityUploadTask.expression.completionHandler(transferUtilityUploadTask, error); + } + } + else { + //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 @@ -933,6 +1156,7 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa transferUtilityMultiPartUploadTask.file = [fileURL path]; transferUtilityMultiPartUploadTask.retryCount = 0; transferUtilityMultiPartUploadTask.temporaryFileCreated = temporaryFileCreated; + transferUtilityMultiPartUploadTask.status = AWSS3TransferUtilityTransferStatusInProgress; //Get the size of the file and calculate the number of parts. NSError *nsError = nil; @@ -974,7 +1198,7 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa transferUtilityMultiPartUploadTask.uploadID = output.uploadId; //Save the Multipart Upload in the DB - [self insertMultiPartUploadRequestInDB:transferUtilityMultiPartUploadTask databaseQueue:self->_databaseQueue]; + [AWSS3TransferUtilityDatabaseHelper insertMultiPartUploadRequestInDB:transferUtilityMultiPartUploadTask databaseQueue:self->_databaseQueue]; AWSDDLogInfo(@"Initiated multipart upload on server: %@", output.uploadId); AWSDDLogInfo(@"Concurrency Limit is %@", self.transferUtilityConfiguration.multiPartConcurrencyLimit); @@ -995,11 +1219,18 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa //Move to inProgress or Waiting based on concurrency limit if (i <= [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { - subTask.status = AWSS3TransferUtilityInProgressStatus; - [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask]; + subTask.status = AWSS3TransferUtilityTransferStatusInProgress; + NSError *error = [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask]; + if ( error) { + //Abort the request, so the server can clean up any partials. + [self callAbortMultiPartForUploadTask:transferUtilityMultiPartUploadTask]; + //Clean up. + [self cleanupForMultiPartUploadTask:transferUtilityMultiPartUploadTask]; + return [AWSTask taskWithError:error]; + }; } else { - subTask.status = AWSS3TransferUtilityWaitingStatus; + subTask.status = AWSS3TransferUtilityTransferStatusWaiting; [transferUtilityMultiPartUploadTask.waitingPartsDictionary setObject:subTask forKey:subTask.partNumber]; } } @@ -1010,7 +1241,19 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa -(NSString *) createTemporaryFileForPart: (NSString *) fileName partNumber: (long) partNumber - dataLength: (NSUInteger) dataLength { + dataLength: (NSUInteger) dataLength + error: (NSError **) error{ + if (![[NSFileManager defaultManager] fileExistsAtPath:fileName]) { + NSString *errorMessage = [NSString stringWithFormat:@"Local file not found. Unable to process Part #: %ld", partNumber]; + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMessage + forKey:@"Message"]; + + *error = [NSError errorWithDomain:AWSS3TransferUtilityErrorDomain + code:AWSS3TransferUtilityErrorLocalFileNotFound + userInfo:userInfo]; + return nil; + } + //Create a temporary file for this part. NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:fileName]; [fileHandle seekToFileOffset:(partNumber - 1) * AWSS3TransferUtilityMultiPartSize]; @@ -1023,109 +1266,17 @@ -(NSString *) createTemporaryFileForPart: (NSString *) fileName return partFile; } --(void) propagateHeaderInformation: (AWSS3CreateMultipartUploadRequest *) uploadRequest - expression: (AWSS3TransferUtilityMultiPartUploadExpression *) expression { - - //Propagate header info and add custom metadata - NSMutableDictionary *metadata = [NSMutableDictionary new]; - for (NSString *key in expression.requestHeaders) { - NSString *lKey = [key lowercaseString]; - if ([lKey hasPrefix:@"x-amz-meta"]) { - [metadata setValue:expression.requestHeaders[key] forKey:[key stringByReplacingOccurrencesOfString:@"x-amz-meta-" withString:@""]]; - } - else if ([lKey isEqualToString:@"x-amz-acl"]) { - NSValueTransformer *transformer = [AWSS3CreateMultipartUploadRequest ACLJSONTransformer]; - uploadRequest.ACL = (AWSS3ObjectCannedACL)[[transformer transformedValue:expression.requestHeaders[key]] integerValue]; - } - else if ([lKey isEqualToString:@"x-amz-grant-read" ]) { - uploadRequest.grantRead = expression.requestHeaders[key]; - } - else if ([lKey isEqualToString:@"x-amz-grant-read-acp" ]) { - uploadRequest.grantReadACP = expression.requestHeaders[key]; - } - else if ([lKey isEqualToString:@"x-amz-grant-read-acp" ]) { - uploadRequest.grantReadACP = expression.requestHeaders[key]; - } - else if ([lKey isEqualToString:@"x-amz-grant-write-acp" ]) { - uploadRequest.grantWriteACP = expression.requestHeaders[key]; - } - else if ([lKey isEqualToString:@"x-amz-grant-full-control" ]) { - uploadRequest.grantFullControl = expression.requestHeaders[key]; - } - else if ([lKey isEqualToString:@"x-amz-server-side-encryption" ]) { - NSValueTransformer *transformer = [AWSS3CreateMultipartUploadRequest serverSideEncryptionJSONTransformer]; - uploadRequest.serverSideEncryption = (AWSS3ServerSideEncryption)[[transformer transformedValue:expression.requestHeaders[key]] integerValue]; - } - else if ([lKey isEqualToString:@"x-amz-server-side-encryption-aws-kms-key-id" ]) { - uploadRequest.SSEKMSKeyId = expression.requestHeaders[key]; - } - else if ([lKey isEqualToString:@"x-amz-server-side​-encryption​-customer-algorithm" ]) { - uploadRequest.SSECustomerAlgorithm = expression.requestHeaders[key]; - } - else if ([lKey isEqualToString:@"x-amz-server-side​-encryption​-customer-key" ]) { - uploadRequest.SSECustomerKey = expression.requestHeaders[key]; - } - else if ([lKey isEqualToString:@"x-amz-server-side​-encryption​-customer-key-MD5" ]) { - uploadRequest.SSECustomerKeyMD5 = expression.requestHeaders[key]; - } - else if ([lKey isEqualToString:@"content-encoding" ]) { - uploadRequest.contentEncoding = expression.requestHeaders[key]; - } - else if ([lKey isEqualToString:@"content-type" ]) { - uploadRequest.contentType = expression.requestHeaders[key]; - } - else if([lKey isEqualToString:@"cache-control"]) { - uploadRequest.cacheControl = expression.requestHeaders[key]; - } - else if ([lKey isEqualToString:@"x-amz-request-payer" ]) { - NSValueTransformer *transformer = [AWSS3CreateMultipartUploadRequest requestPayerJSONTransformer]; - uploadRequest.requestPayer = (AWSS3RequestPayer)[[transformer transformedValue:expression.requestHeaders[key]] integerValue]; - } - else if ([lKey isEqualToString:@"expires" ]) { - NSValueTransformer *transformer = [AWSS3CreateMultipartUploadRequest expiresJSONTransformer]; - uploadRequest.expires = [transformer transformedValue:expression.requestHeaders[key]]; - } - else if ([lKey isEqualToString:@"x-amz-storage-class" ]) { - NSValueTransformer *transformer = [AWSS3CreateMultipartUploadRequest storageClassJSONTransformer]; - uploadRequest.storageClass = (AWSS3StorageClass)[[transformer transformedValue:expression.requestHeaders[key]] integerValue]; - } - else if ([lKey isEqualToString:@"x-amz-website-redirect-location" ]) { - uploadRequest.websiteRedirectLocation = expression.requestHeaders[key]; - } - else if ([lKey isEqualToString:@"x-amz-tagging" ]) { - uploadRequest.tagging = expression.requestHeaders[key]; - } - } - uploadRequest.metadata = metadata; -} - --(void) filterAndAssignHeaders:(NSDictionary *) requestHeaders - getPresignedURLRequest:(AWSS3GetPreSignedURLRequest *) getPresignedURLRequest - URLRequest: (NSMutableURLRequest *) URLRequest { - - NSSet *disallowedHeaders = [[NSSet alloc] initWithArray: - @[@"x-amz-acl", @"x-amz-tagging", @"x-amz-storage-class", @"x-amz-server-side-encryption"]]; - - for (NSString *key in requestHeaders) { - //Do not include custom metadata or custom grants - NSString *lKey = [key lowercaseString]; - if ([ lKey hasPrefix:@"x-amz-meta"] || [lKey hasPrefix:@"x-amz-grant"]) { - continue; - } - if ([disallowedHeaders containsObject:lKey]) { - continue; - } - [getPresignedURLRequest setValue:requestHeaders[key] forRequestHeader:key]; - [URLRequest setValue:requestHeaders[key] forHTTPHeaderField:key]; - } -} - --(void) createUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *) transferUtilityMultiPartUploadTask +-(NSError *) createUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *) transferUtilityMultiPartUploadTask subTask: (AWSS3TransferUtilityUploadSubTask *) subTask { - if (!subTask.file ) { + if (!subTask.file || ![[NSFileManager defaultManager] fileExistsAtPath:subTask.file]) { //Create a temporary file for this part. - NSString * partFileName = [self createTemporaryFileForPart:transferUtilityMultiPartUploadTask.file partNumber:[subTask.partNumber integerValue] dataLength:subTask.totalBytesExpectedToSend]; + NSError *error = nil; + NSString * partFileName = [self createTemporaryFileForPart:transferUtilityMultiPartUploadTask.file partNumber:[subTask.partNumber integerValue] dataLength:subTask.totalBytesExpectedToSend error:&error]; + if (partFileName == nil) { + //Unable to create partFile. Send back error object to indicate that createUploadSubtask failed. + return error; + } subTask.file = partFileName; } @@ -1167,9 +1318,10 @@ -(void) createUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *) transfer [self->_taskDictionary setObject:transferUtilityMultiPartUploadTask forKey:@(subTask.taskIdentifier)]; //Save in Database - [self insertMultiPartUploadRequestSubTaskInDB:transferUtilityMultiPartUploadTask subTask:subTask databaseQueue:self.databaseQueue]; + [AWSS3TransferUtilityDatabaseHelper insertMultiPartUploadRequestSubTaskInDB:transferUtilityMultiPartUploadTask subTask:subTask databaseQueue:self.databaseQueue]; return nil; }]; + return nil; } -(void) retryUploadSubTask: (AWSS3TransferUtilityMultiPartUploadTask *) transferUtilityMultiPartUploadTask @@ -1180,10 +1332,25 @@ -(void) retryUploadSubTask: (AWSS3TransferUtilityMultiPartUploadTask *) transfer [transferUtilityMultiPartUploadTask.inProgressPartsDictionary removeObjectForKey:@(subTask.taskIdentifier)]; //Remove subTask from Database - [self deleteTransferRequestFromDB:subTask.transferID taskIdentifier:subTask.taskIdentifier databaseQueue:_databaseQueue]; - + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:subTask.transferID taskIdentifier:subTask.taskIdentifier databaseQueue:_databaseQueue]; transferUtilityMultiPartUploadTask.retryCount = transferUtilityMultiPartUploadTask.retryCount + 1; - [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask]; + + //Check if the part file exists + if (![[NSFileManager defaultManager] fileExistsAtPath:subTask.file]) { + //Set it to nil. This will force the creatUploadSubTask to create the part from the main file + subTask.file = nil; + } + + NSError *error = [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask]; + if ( error ) { + //cancel the multipart transfer + [transferUtilityMultiPartUploadTask cancel]; + + //Call the completion handler if one was present + if (transferUtilityMultiPartUploadTask.expression.completionHandler) { + transferUtilityMultiPartUploadTask.expression.completionHandler(transferUtilityMultiPartUploadTask, error); + } + } } #pragma mark - Download methods @@ -1269,11 +1436,17 @@ -(void) retryUploadSubTask: (AWSS3TransferUtilityMultiPartUploadTask *) transfer transferUtilityDownloadTask.cancelled = NO; transferUtilityDownloadTask.retryCount = 0; transferUtilityDownloadTask.responseData = @""; + transferUtilityDownloadTask.status = AWSS3TransferUtilityTransferStatusInProgress; return [self createDownloadTask:transferUtilityDownloadTask]; } -(AWSTask *) createDownloadTask: (AWSS3TransferUtilityDownloadTask *) transferUtilityDownloadTask { + return [self createDownloadTask:transferUtilityDownloadTask startTransfer:YES]; +} + +-(AWSTask *) createDownloadTask: (AWSS3TransferUtilityDownloadTask *) transferUtilityDownloadTask + startTransfer: (BOOL) startTransfer { AWSS3GetPreSignedURLRequest *getPreSignedURLRequest = [AWSS3GetPreSignedURLRequest new]; getPreSignedURLRequest.bucket = transferUtilityDownloadTask.bucket; getPreSignedURLRequest.key = transferUtilityDownloadTask.key; @@ -1309,7 +1482,7 @@ -(void) retryUploadSubTask: (AWSS3TransferUtilityMultiPartUploadTask *) transfer [self.taskDictionary setObject:transferUtilityDownloadTask forKey:@(transferUtilityDownloadTask.sessionTask.taskIdentifier) ]; //Add to Database - [self insertDownloadTransferRequestInDB:transferUtilityDownloadTask databaseQueue:self->_databaseQueue]; + [AWSS3TransferUtilityDatabaseHelper insertDownloadTransferRequestInDB:transferUtilityDownloadTask databaseQueue:self->_databaseQueue]; [downloadTask resume]; return [AWSTask taskWithResult:transferUtilityDownloadTask]; }]; @@ -1321,7 +1494,7 @@ - (void) retryDownload: (AWSS3TransferUtilityDownloadTask *) transferUtilityDown [self.taskDictionary removeObjectForKey:@(transferUtilityDownloadTask.sessionTask.taskIdentifier)]; //Remove from Database - [self deleteTransferRequestFromDB:transferUtilityDownloadTask.transferID taskIdentifier:transferUtilityDownloadTask.sessionTask.taskIdentifier + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityDownloadTask.transferID taskIdentifier:transferUtilityDownloadTask.sessionTask.taskIdentifier databaseQueue:_databaseQueue]; AWSDDLogDebug(@"Removed object from key %@", @(transferUtilityDownloadTask.sessionTask.taskIdentifier) ); @@ -1430,8 +1603,8 @@ - (AWSTask *)getAllTasks { - (AWSTask *)getUploadTasks { AWSTaskCompletionSource *completionSource = [AWSTaskCompletionSource new]; NSMutableArray *allTasks = [NSMutableArray new]; - for (id key in [self.taskDictionary allKeys]) { + AWSDDLogDebug(@"Iterating through taskDictionary"); id value = [self.taskDictionary objectForKey:key]; if ([value isKindOfClass:[AWSS3TransferUtilityUploadTask class]]) { [allTasks addObject:value]; @@ -1616,6 +1789,7 @@ - (void)URLSession:(NSURLSession *)session //Check if the task was cancelled. if (uploadTask.cancelled) { [self cleanupForUploadTask:uploadTask]; + [self.completedTaskDictionary setObject:uploadTask forKey:uploadTask.transferID]; return; } @@ -1642,9 +1816,19 @@ - (void)URLSession:(NSURLSession *)session uploadTask.error = updatedError; } + //Mark status as completed if there is no error. + if (! uploadTask.error ) { + uploadTask.status = AWSS3TransferUtilityTransferStatusCompleted; + } + //Else mark as error. + else { + uploadTask.status = AWSS3TransferUtilityTransferStatusError; + } + if(uploadTask.expression.completionHandler) { uploadTask.expression.completionHandler(uploadTask,uploadTask.error); } + [self.completedTaskDictionary setObject:uploadTask forKey:uploadTask.transferID]; [self cleanupForUploadTask:uploadTask]; return; } @@ -1661,6 +1845,9 @@ - (void)URLSession:(NSURLSession *)session //Abort the request, so the server can clean up any partials. [self callAbortMultiPartForUploadTask:transferUtilityMultiPartUploadTask]; + //Add it to list of completed Tasks + [self.completedTaskDictionary setObject:transferUtilityMultiPartUploadTask forKey:transferUtilityMultiPartUploadTask.transferID]; + //Clean up. [self cleanupForMultiPartUploadTask:transferUtilityMultiPartUploadTask]; return; @@ -1690,6 +1877,8 @@ - (void)URLSession:(NSURLSession *)session //Error is not retriable. transferUtilityMultiPartUploadTask.error = updatedError; + transferUtilityMultiPartUploadTask.status = AWSS3TransferUtilityTransferStatusError; + //Execute call back if provided. if(transferUtilityMultiPartUploadTask.expression.completionHandler) { transferUtilityMultiPartUploadTask.expression.completionHandler(transferUtilityMultiPartUploadTask, transferUtilityMultiPartUploadTask.error); @@ -1701,6 +1890,9 @@ - (void)URLSession:(NSURLSession *)session //Abort the request, so the server can clean up any partials. [self callAbortMultiPartForUploadTask:transferUtilityMultiPartUploadTask]; + //Add it to list of completed Tasks + [self.completedTaskDictionary setObject:transferUtilityMultiPartUploadTask forKey:transferUtilityMultiPartUploadTask.transferID]; + //clean up. [self cleanupForMultiPartUploadTask:transferUtilityMultiPartUploadTask]; return; @@ -1720,7 +1912,7 @@ - (void)URLSession:(NSURLSession *)session [self removeFile:subTask.file]; //Update Database - [self updateTransferRequestInDB:subTask.transferID taskIdentifier:subTask.taskIdentifier eTag:subTask.eTag status:AWSS3TransferUtilityCompletedStatus databaseQueue:_databaseQueue]; + [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:subTask.transferID taskIdentifier:subTask.taskIdentifier eTag:subTask.eTag status:AWSS3TransferUtilityTransferStatusCompleted 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) { @@ -1731,7 +1923,20 @@ - (void)URLSession:(NSURLSession *)session [transferUtilityMultiPartUploadTask.waitingPartsDictionary removeObjectForKey:nextSubTask.partNumber]; //Create the subtask and start the transfer - [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:nextSubTask]; + NSError *error = [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:nextSubTask]; + if ( error ) { + transferUtilityMultiPartUploadTask.status = AWSS3TransferUtilityTransferStatusError; + //Add it to list of completed Tasks + [self.completedTaskDictionary setObject:transferUtilityMultiPartUploadTask forKey:transferUtilityMultiPartUploadTask.transferID]; + + //cancel the multipart transfer + [transferUtilityMultiPartUploadTask cancel]; + + //Call the completion handler if one was present + if (transferUtilityMultiPartUploadTask.expression.completionHandler) { + transferUtilityMultiPartUploadTask.expression.completionHandler(transferUtilityMultiPartUploadTask, error); + } + } } //If there are no more inProgress parts, then we are done. else if ([transferUtilityMultiPartUploadTask.inProgressPartsDictionary count] == 0) { @@ -1740,8 +1945,13 @@ - (void)URLSession:(NSURLSession *)session if (task.error) { AWSDDLogError(@"Error finishing up MultiPartForUpload Task[%@]", task.error); transferUtilityMultiPartUploadTask.error = error; + transferUtilityMultiPartUploadTask.status = AWSS3TransferUtilityTransferStatusError; } AWSDDLogInfo(@"Completed Multipart Transfer: %@", transferUtilityMultiPartUploadTask.uploadID); + transferUtilityMultiPartUploadTask.status = AWSS3TransferUtilityTransferStatusCompleted; + //Add it to list of completed Tasks + [self.completedTaskDictionary setObject:transferUtilityMultiPartUploadTask forKey:transferUtilityMultiPartUploadTask.transferID]; + [self cleanupForMultiPartUploadTask:transferUtilityMultiPartUploadTask]; //Call the callback function is specified. @@ -1762,12 +1972,19 @@ - (void)URLSession:(NSURLSession *)session //Check if the task was cancelled. if (downloadTask.cancelled) { + [self.completedTaskDictionary setObject:downloadTask forKey:downloadTask.transferID]; [self.taskDictionary removeObjectForKey:@(downloadTask.sessionTask.taskIdentifier)]; - [self deleteTransferRequestFromDB:downloadTask.transferID databaseQueue:_databaseQueue]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:downloadTask.transferID databaseQueue:_databaseQueue]; return; } downloadTask.error = error; + if(!error ) { + downloadTask.status = AWSS3TransferUtilityTransferStatusCompleted; + } + else { + downloadTask.status = AWSS3TransferUtilityTransferStatusError; + } if (error && HTTPResponse) { if ([self isErrorRetriable:HTTPResponse.statusCode responseFromServer:downloadTask.responseData]) { if (downloadTask.retryCount < self.transferUtilityConfiguration.retryLimit) { @@ -1794,8 +2011,9 @@ - (void)URLSession:(NSURLSession *)session downloadTask.data, downloadTask.error); } + [self.completedTaskDictionary setObject:downloadTask forKey:downloadTask.transferID]; [self.taskDictionary removeObjectForKey:@(downloadTask.sessionTask.taskIdentifier)]; - [self deleteTransferRequestFromDB:downloadTask.transferID databaseQueue:_databaseQueue]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:downloadTask.transferID databaseQueue:_databaseQueue]; } } @@ -1816,7 +2034,7 @@ - (void)handleS3Errors:(NSString *)responseString - (void) cleanupForMultiPartUploadTask: (AWSS3TransferUtilityMultiPartUploadTask *) task { //Remove data from the Database. - [self deleteTransferRequestFromDB:task.transferID databaseQueue:_databaseQueue]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:task.transferID databaseQueue:_databaseQueue]; //Remove all temporary files and entries from taskDictionary. for ( AWSS3TransferUtilityUploadSubTask *subTask in [task.inProgressPartsDictionary allValues] ) { @@ -1829,11 +2047,12 @@ - (void) cleanupForMultiPartUploadTask: (AWSS3TransferUtilityMultiPartUploadTask } - (void) cleanupForUploadTask: (AWSS3TransferUtilityUploadTask *) uploadTask { + [self.completedTaskDictionary setObject:uploadTask forKey:uploadTask.transferID]; [self.taskDictionary removeObjectForKey:@(uploadTask.taskIdentifier)]; if (uploadTask.temporaryFileCreated) { [self removeFile:uploadTask.file]; } - [self deleteTransferRequestFromDB:uploadTask.transferID databaseQueue:_databaseQueue]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:uploadTask.transferID databaseQueue:_databaseQueue]; } - (BOOL) isErrorRetriable:(NSInteger) HTTPStatusCode @@ -1997,353 +2216,6 @@ - (void)URLSession:(NSURLSession *)session } } -#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]; - } - - [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]) { @@ -2386,262 +2258,6 @@ - (id)copyWithZone:(NSZone *)zone { @end -#pragma mark - AWSS3TransferUtilityTasks - -@implementation AWSS3TransferUtilityTask - -- (instancetype)init { - if (self = [super init]) { - _progress = [NSProgress new]; - } - - return self; -} - -- (NSUInteger)taskIdentifier { - return self.sessionTask.taskIdentifier; -} - -- (void)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 { - return self.sessionTask.originalRequest; -} -- (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 - -- (AWSS3TransferUtilityUploadExpression *)expression { - if (!_expression) { - _expression = [AWSS3TransferUtilityUploadExpression new]; - } - 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 - -- (AWSS3TransferUtilityDownloadExpression *)expression { - if (!_expression) { - _expression = [AWSS3TransferUtilityDownloadExpression new]; - } - return _expression; -} - --(void) cancel { - self.cancelled = YES; - [self.sessionTask cancel]; -} - -@end - -#pragma mark - AWSS3TransferUtilityExpressions - -@implementation AWSS3TransferUtilityExpression - -- (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 AWSS3TransferUtilityUploadExpression -- (NSString *)contentMD5 { - return [self.internalRequestHeaders valueForKey:@"Content-MD5"]; -} - -- (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)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/AWSS3TransferUtilityDatabaseHelper.h b/AWSS3/AWSS3TransferUtilityDatabaseHelper.h new file mode 100644 index 00000000000..0ee959ed8bb --- /dev/null +++ b/AWSS3/AWSS3TransferUtilityDatabaseHelper.h @@ -0,0 +1,19 @@ +// +// 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. +// + + +@interface AWSS3TransferUtilityDatabaseHelper:NSObject + +@end diff --git a/AWSS3/AWSS3TransferUtilityDatabaseHelper.m b/AWSS3/AWSS3TransferUtilityDatabaseHelper.m new file mode 100644 index 00000000000..cee6034c294 --- /dev/null +++ b/AWSS3/AWSS3TransferUtilityDatabaseHelper.m @@ -0,0 +1,452 @@ +// +// 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 "AWSFMDB.h" +#import "AWSS3TransferUtilityDatabaseHelper.h" +#import "AWSS3TransferUtility.h" +#import "AWSS3TransferUtilityTasks.h" + +//Constants for DB +NSString *const AWSS3TransferUtilityDatabaseDirectory = @"/com/amazonaws/AWSS3TransferUtility/"; +NSString *const AWSS3TransferUtilityDatabaseName = @"transfer_utility_database"; + +@interface AWSS3TransferUtilityTask() +@property NSString *nsURLSessionID; +@property NSString *file; +@property int retryCount; +@property (strong, nonatomic) NSString *transferID; +@end + +@interface AWSS3TransferUtilityUploadTask() +@property (strong, nonatomic) AWSS3TransferUtilityUploadExpression *expression; +@property BOOL temporaryFileCreated; +@end + +@interface AWSS3TransferUtilityMultiPartUploadTask() +@property (strong, nonatomic) AWSS3TransferUtilityMultiPartUploadExpression *expression; +@property NSString *nsURLSessionID; +@property NSString * uploadID; +@property NSString *file; +@property BOOL temporaryFileCreated; +@property NSNumber *contentLength; +@property int retryCount; +@end + +@interface AWSS3TransferUtilityDownloadTask() +@property (strong, nonatomic) AWSS3TransferUtilityDownloadExpression *expression; +@end + +@interface AWSS3TransferUtilityUploadSubTask() +@property (readwrite) NSUInteger taskIdentifier; +@property (strong, nonatomic) NSNumber *partNumber; +@property NSString *file; +@property int64_t totalBytesExpectedToSend; +@property AWSS3TransferUtilityTransferStatusType status; + +@end + +#pragma mark - AWSS3 Transfer Utility Database Functions + +@implementation AWSS3TransferUtilityDatabaseHelper + ++ (AWSFMDatabaseQueue *) createDatabase:(NSString*) cacheDirectoryPath { + //Create temporary Dir to hold DB + 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 *dbDirPath = [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; +} + + +//Delete a transfer request given its transfer ID ++ (void) deleteTransferRequestFromDB:(NSString *) transferID + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { + NSString *const AWSS3TransferUtilityDeleteTransfer = @"DELETE FROM awstransfer " + @"WHERE transfer_id=:transfer_id"; + + [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; + } + + }]; +} + +//Delete a transfer request given its transfer ID and task Identifier. ++ (void) deleteTransferRequestFromDB:(NSString *) transferID + taskIdentifier: (NSUInteger) taskIdentifier + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { + NSString *const AWSS3TransferUtilityDeleteATask = @"DELETE FROM awstransfer " + @"WHERE transfer_id=:transfer_id and " + @" session_task_id=:session_task_id "; + [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); + } + }]; +} + +//update transfer status. ++ (void) updateTransferRequestStatusInDB: (NSString *) transferID + taskIdentifier: (NSUInteger) taskIdentifier + status: (AWSS3TransferUtilityTransferStatusType) status + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { + NSString *const AWSS3TransferUtilityUpdateTransferUtilityStatus = @"UPDATE awstransfer " + @"SET status=:status " + @"WHERE transfer_id=:transfer_id and " + @" session_task_id=:session_task_id "; + + [databaseQueue inDatabase:^(AWSFMDatabase *db) { + BOOL result = [db executeUpdate: AWSS3TransferUtilityUpdateTransferUtilityStatus + withParameterDictionary:@{ + @"transfer_id": transferID, + @"session_task_id": @(taskIdentifier), + @"status": [AWSS3TransferUtilityDatabaseHelper getStringRepresentation:status], + }]; + if (!result) { + AWSDDLogError(@"Failed to update transfer_request [%@] in Database. [%@]", transferID, + db.lastError); + } + }]; +} + +// update transfer status and eTag ++ (void) updateTransferRequestInDB: (NSString *) transferID + taskIdentifier: (NSUInteger) taskIdentifier + eTag: (NSString *) eTag + status: (AWSS3TransferUtilityTransferStatusType) status + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { + NSString *const AWSS3TransferUtilityUpdateTransferUtilityStatusAndETag = @"UPDATE awstransfer " + @"SET status=:status, etag = :etag " + @"WHERE transfer_id=:transfer_id and " + @" session_task_id=:session_task_id "; + [databaseQueue inDatabase:^(AWSFMDatabase *db) { + BOOL result = [db executeUpdate: AWSS3TransferUtilityUpdateTransferUtilityStatusAndETag + withParameterDictionary:@{ + @"transfer_id": transferID, + @"session_task_id": @(taskIdentifier), + @"etag": eTag, + @"status": [AWSS3TransferUtilityDatabaseHelper getStringRepresentation:status] + }]; + + if (!result) { + AWSDDLogError(@"Failed to update transfer_request [%@] in Database. [%@]", transferID, + db.lastError); + } + }]; +} + + ++ (void) insertUploadTransferRequestInDB:(AWSS3TransferUtilityUploadTask *) task + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { + + [AWSS3TransferUtilityDatabaseHelper 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:task.status + retryCount:@(task.retryCount) + requestHeadersJSON:[AWSS3TransferUtilityDatabaseHelper getJSONRepresentation:task.expression.requestHeaders] + requestParametersJSON:[AWSS3TransferUtilityDatabaseHelper getJSONRepresentation:task.expression.requestParameters] + databaseQueue:databaseQueue]; +} + ++ (void) insertDownloadTransferRequestInDB:(AWSS3TransferUtilityDownloadTask *) task + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { + NSString *file = task.file; + if(!file) { + file = @""; + } + [AWSS3TransferUtilityDatabaseHelper 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:task.status + retryCount:@(task.retryCount) + requestHeadersJSON:[self getJSONRepresentation:task.expression.requestHeaders] + requestParametersJSON:[self getJSONRepresentation:task.expression.requestParameters] + databaseQueue:databaseQueue]; +} + ++ (void) insertMultiPartUploadRequestInDB:(AWSS3TransferUtilityMultiPartUploadTask *) task + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { + [AWSS3TransferUtilityDatabaseHelper 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:task.status + 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 { + [AWSS3TransferUtilityDatabaseHelper 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: (AWSS3TransferUtilityTransferStatusType) status + retryCount: (NSNumber *) retryCount + requestHeadersJSON: (NSString *) requestHeadersJSON + requestParametersJSON: (NSString *) requestParametersJSON + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { + 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" + @")"; + + NSNumber *tempFileCreated = [NSNumber numberWithInt:0]; + if (temporaryFileCreated) { + tempFileCreated = [NSNumber numberWithInt:1]; + } + + [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": [AWSS3TransferUtilityDatabaseHelper getStringRepresentation:status], + @"request_headers": requestHeadersJSON, + @"request_parameters": requestParametersJSON, + @"retry_count": retryCount + }]; + + if (!result) { + AWSDDLogError(@"Failed to save Transfer [%@] in awstransfer database table. [%@]", transferID, db.lastError); + } + }]; +} + ++ (NSMutableArray *) getTransferTaskDataFromDB:(NSString *)nsURLSessionID + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue +{ + 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"; + + 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 intForColumn:@"retry_count"]) forKey:@"retry_count"]; + [transfer setObject:[rs stringForColumn:@"request_headers"] forKey:@"request_headers"]; + [transfer setObject:[rs stringForColumn:@"request_parameters"] forKey:@"request_parameters"]; + NSNumber *statusValue = [ NSNumber numberWithInteger:[AWSS3TransferUtilityDatabaseHelper getEnumRepresentation:[rs stringForColumn:@"status"]]]; + [transfer setObject: statusValue forKey:@"status"]; + [tasks addObject:transfer]; + } + rs = nil; + }]; + return tasks; +} + + ++ (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; +} + ++ (NSString *) getStringRepresentation: (AWSS3TransferUtilityTransferStatusType) status { + switch (status) { + case AWSS3TransferUtilityTransferStatusInProgress: return @"IN_PROGRESS"; + case AWSS3TransferUtilityTransferStatusPaused: return @"PAUSED"; + case AWSS3TransferUtilityTransferStatusError: return @"ERROR"; + case AWSS3TransferUtilityTransferStatusCompleted: return @"COMPLETED"; + case AWSS3TransferUtilityTransferStatusWaiting: return @"WAITING"; + case AWSS3TransferUtilityTransferStatusCancelled: return @"CANCELLED"; + default: return @"UNKNOWN"; + } +} + ++ (AWSS3TransferUtilityTransferStatusType) getEnumRepresentation: (NSString *) status { + if ([status caseInsensitiveCompare:@"IN_PROGRESS"] == NSOrderedSame) { + return AWSS3TransferUtilityTransferStatusInProgress; + } + if ([status caseInsensitiveCompare:@"PAUSED"] == NSOrderedSame) { + return AWSS3TransferUtilityTransferStatusPaused; + } + if ([status caseInsensitiveCompare:@"ERROR"] == NSOrderedSame) { + return AWSS3TransferUtilityTransferStatusError; + } + if ([status caseInsensitiveCompare:@"COMPLETED"] == NSOrderedSame) { + return AWSS3TransferUtilityTransferStatusCompleted; + } + if ([status caseInsensitiveCompare:@"WAITING"] == NSOrderedSame) { + return AWSS3TransferUtilityTransferStatusWaiting; + } + if ([status caseInsensitiveCompare:@"CANCELLED"] == NSOrderedSame) { + return AWSS3TransferUtilityTransferStatusCancelled; + } + return AWSS3TransferUtilityTransferStatusUnknown; +} +@end + diff --git a/AWSS3/AWSS3TransferUtilityTasks.h b/AWSS3/AWSS3TransferUtilityTasks.h new file mode 100644 index 00000000000..9f3ad8e56a4 --- /dev/null +++ b/AWSS3/AWSS3TransferUtilityTasks.h @@ -0,0 +1,342 @@ +// +// 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. +// + +NS_ASSUME_NONNULL_BEGIN + + + +@class AWSS3TransferUtilityTask; +@class AWSS3TransferUtilityUploadTask; +@class AWSS3TransferUtilityMultiPartUploadTask; +@class AWSS3TransferUtilityDownloadTask; +@class AWSS3TransferUtilityExpression; +@class AWSS3TransferUtilityUploadExpression; +@class AWSS3TransferUtilityMultiPartUploadExpression; +@class AWSS3TransferUtilityDownloadExpression; + +typedef NS_ENUM(NSInteger, AWSS3TransferUtilityTransferStatusType) { + AWSS3TransferUtilityTransferStatusUnknown, + AWSS3TransferUtilityTransferStatusInProgress, + AWSS3TransferUtilityTransferStatusPaused, + AWSS3TransferUtilityTransferStatusCompleted, + AWSS3TransferUtilityTransferStatusWaiting, + AWSS3TransferUtilityTransferStatusError, + AWSS3TransferUtilityTransferStatusCancelled +}; + +/** + The upload completion handler. + + @param task The upload task object. + @param error Returns the error object when the download failed. + */ +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. + + @param task The download task object. + @param location When downloading an Amazon S3 object to a file, returns a file URL of the returned object. Otherwise, returns `nil`. + @param data When downloading an Amazon S3 object as an `NSData`, returns the returned object as an instance of `NSData`. Otherwise, returns `nil`. + @param error Returns the error object when the download failed. Returns `nil` on successful downlaod. + */ +typedef void (^AWSS3TransferUtilityDownloadCompletionHandlerBlock) (AWSS3TransferUtilityDownloadTask *task, + NSURL * _Nullable location, + NSData * _Nullable data, + NSError * _Nullable error); + +/** + The transfer progress feedback block. + + @param task The upload task object. + @param progress The progress object. + + @note Refer to `- URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:` in `NSURLSessionTaskDelegate` for more details on upload progress and `- URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:` in `NSURLSessionDownloadDelegate` for more details on download progress. + */ +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 - AWSS3TransferUtilityTasks + +/** + The task object to represent a upload or download task. + */ +@interface AWSS3TransferUtilityTask : NSObject + +/** + An identifier uniquely identifies the transferID. + */ + +@property (readonly) NSString *transferID; + +/** + An identifier uniquely identifies the task within a given `AWSS3TransferUtility` instance. + */ +@property (readonly) NSUInteger taskIdentifier; + +/** + 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; + +/** + the status of the Transfer. + */ +@property (readonly) AWSS3TransferUtilityTransferStatusType status; + +/** + The underlying `NSURLSessionTask` object. + */ +@property (readonly) NSURLSessionTask *sessionTask; + +/** + The HTTP request object. + */ +@property (nullable, readonly) NSURLRequest *request; + +/** + The HTTP response object. May be nil if no response has been received. + */ +@property (nullable, readonly) NSHTTPURLResponse *response; + +/** + Cancels the task. + */ +- (void)cancel; + +/** + Resumes the task, if it is suspended. + */ +- (void)resume; + +/** + Temporarily suspends a task. + */ +- (void)suspend; + +- (void) setProgressBlock: (AWSS3TransferUtilityProgressBlock) progressBlock; + +@end + +/** + The task object to represent a upload task. + */ +@interface AWSS3TransferUtilityUploadTask : AWSS3TransferUtilityTask + +- (void) setCompletionHandler: (AWSS3TransferUtilityUploadCompletionHandlerBlock)completionHandler; +@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; + +/** + the status of the Transfer. + */ +@property (readonly) AWSS3TransferUtilityTransferStatusType status; + + +/** + Cancels the task. + */ +- (void)cancel; + +/** + Resumes the task, if it is suspended. + */ +- (void)resume; + +/** + Temporarily suspends a task. + */ +- (void)suspend; + +/** + set completion handler for task + **/ + +- (void) setCompletionHandler: (AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock)completionHandler; + +- (void) setProgressBlock: (AWSS3TransferUtilityMultiPartProgressBlock) progressBlock; + +@end + + +/** + The task object to represent a download task. + */ +@interface AWSS3TransferUtilityDownloadTask : AWSS3TransferUtilityTask + +- (void) setCompletionHandler: (AWSS3TransferUtilityDownloadCompletionHandlerBlock)completionHandler; + +@end + +@interface AWSS3TransferUtilityUploadSubTask: NSObject +@end + +#pragma mark - AWSS3TransferUtilityExpressions + +/** + The expression object for configuring a upload or download task. + */ +@interface AWSS3TransferUtilityExpression : 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) AWSS3TransferUtilityProgressBlock 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 upload task. + */ +@interface AWSS3TransferUtilityUploadExpression : AWSS3TransferUtilityExpression + +/** + The upload request header for `Content-MD5`. + */ +@property (nonatomic, nullable) NSString *contentMD5; + +@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. + */ +@interface AWSS3TransferUtilityDownloadExpression : AWSS3TransferUtilityExpression + +@end + +NS_ASSUME_NONNULL_END + diff --git a/AWSS3/AWSS3TransferUtilityTasks.m b/AWSS3/AWSS3TransferUtilityTasks.m new file mode 100644 index 00000000000..3ff357e8fe8 --- /dev/null +++ b/AWSS3/AWSS3TransferUtilityTasks.m @@ -0,0 +1,426 @@ +// +// 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 +#import "AWSS3TransferUtilityTasks.h" +#import "AWSS3TransferUtilityDatabaseHelper.h" +#import "AWSS3PreSignedURL.h" +#import "AWSFMDB.h" + + +@interface AWSS3TransferUtilityExpression() +@property (strong, nonatomic) NSMutableDictionary *internalRequestHeaders; + +@property (strong, nonatomic) NSMutableDictionary *internalRequestParameters; + +- (void)assignRequestParameters:(AWSS3GetPreSignedURLRequest *)getPreSignedURLRequest; +- (void)assignRequestHeaders:(AWSS3GetPreSignedURLRequest *)getPreSignedURLRequest; +@end + +@interface AWSS3TransferUtilityUploadExpression() +@property (copy, atomic) AWSS3TransferUtilityUploadCompletionHandlerBlock completionHandler; +@end + +@interface AWSS3TransferUtilityMultiPartUploadExpression() +@property (strong, nonatomic) NSMutableDictionary *internalRequestHeaders; +@property (strong, nonatomic) NSMutableDictionary *internalRequestParameters; +- (void)assignRequestParameters:(AWSS3GetPreSignedURLRequest *)getPreSignedURLRequest; +@property (copy, atomic) AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock completionHandler; +@end + + +@interface AWSS3TransferUtilityDownloadExpression() +@property (copy, atomic) AWSS3TransferUtilityDownloadCompletionHandlerBlock completionHandler; +@end + + +@interface AWSS3TransferUtilityTask() + +@property (strong, nonatomic) NSURLSessionTask *sessionTask; +@property (strong, nonatomic) NSString *transferID; +@property (strong, nonatomic) NSString *bucket; +@property (strong, nonatomic) NSString *key; +@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 AWSS3TransferUtilityTransferStatusType 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 AWSS3TransferUtilityTransferStatusType status; +@property NSNumber *contentLength; +@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 AWSS3TransferUtilityTransferStatusType status; +@property NSString *uploadID; + +@end + +@interface AWSS3TransferUtilityDownloadTask() + +@property (strong, nonatomic) AWSS3TransferUtilityDownloadExpression *expression; +@property BOOL cancelled; +@property NSString *responseData; +@end + + + +@interface AWSS3TransferUtilityDatabaseHelper() + ++ (void) updateTransferRequestStatusInDB: (NSString *) transferID + taskIdentifier: (NSUInteger) taskIdentifier + status: (AWSS3TransferUtilityTransferStatusType) status + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue; + ++ (void) updateTransferRequestInDB: (NSString *) transferID + taskIdentifier: (NSUInteger) taskIdentifier + eTag: (NSString *) eTag + status: (AWSS3TransferUtilityTransferStatusType) status + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue; + ++ (void) deleteTransferRequestFromDB:(NSString *) transferID + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue; + +@end + +#pragma mark - AWSS3TransferUtilityTasks + +@implementation AWSS3TransferUtilityTask + +- (instancetype)init { + if (self = [super init]) { + _progress = [NSProgress new]; + } + + return self; +} + +- (NSUInteger)taskIdentifier { + return self.sessionTask.taskIdentifier; +} + +- (void)cancel { +} + +- (void)resume { + [self.sessionTask resume]; + self.status = AWSS3TransferUtilityTransferStatusInProgress; + [AWSS3TransferUtilityDatabaseHelper updateTransferRequestStatusInDB:self.transferID taskIdentifier:self.taskIdentifier status:AWSS3TransferUtilityTransferStatusInProgress databaseQueue:self.databaseQueue]; + +} + +- (void)suspend { + [self.sessionTask suspend]; + self.status = AWSS3TransferUtilityTransferStatusPaused; + [AWSS3TransferUtilityDatabaseHelper updateTransferRequestStatusInDB:self.transferID taskIdentifier:self.sessionTask.taskIdentifier status:AWSS3TransferUtilityTransferStatusPaused databaseQueue:self.databaseQueue]; +} + +- (NSURLRequest *)request { + return self.sessionTask.originalRequest; +} + +- (NSHTTPURLResponse *)response { + if ([self.sessionTask.response isKindOfClass:[NSHTTPURLResponse class]]) { + return (NSHTTPURLResponse *)self.sessionTask.response; + } + return nil; +} + +- (void) setProgressBlock: (AWSS3TransferUtilityProgressBlock) progressBlock { + //self.expression.progressBlock = progressBlock; +} + + +@end + +@implementation AWSS3TransferUtilityUploadTask + +- (AWSS3TransferUtilityUploadExpression *)expression { + if (!_expression) { + _expression = [AWSS3TransferUtilityUploadExpression new]; + } + return _expression; +} + +-(void) cancel { + self.status = AWSS3TransferUtilityTransferStatusCancelled; + self.cancelled = YES; + [self.sessionTask cancel]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:self.transferID databaseQueue:self.databaseQueue]; +} + +-(void) setCompletionHandler:(AWSS3TransferUtilityUploadCompletionHandlerBlock)completionHandler { + + self.expression.completionHandler = completionHandler; + //If the task has already completed successfully, call the completion handler + if (self.status == AWSS3TransferUtilityTransferStatusCompleted ) { + _expression.completionHandler(self, nil); + } + //If the task has completed with error, call the completion handler + else if (self.error) { + _expression.completionHandler(self, self.error); + } +} + +-(void) setProgressBlock:(AWSS3TransferUtilityProgressBlock)progressBlock { + self.expression.progressBlock = progressBlock; +} + +@end + +@implementation AWSS3TransferUtilityMultiPartUploadTask + +- (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; + self.status = AWSS3TransferUtilityTransferStatusCancelled; + for (NSNumber *key in [self.inProgressPartsDictionary allKeys]) { + AWSS3TransferUtilityUploadSubTask *subTask = [self.inProgressPartsDictionary objectForKey:key]; + [subTask.sessionTask cancel]; + } + + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:_transferID databaseQueue:self.databaseQueue]; +} + +- (void)resume { + for (NSNumber *key in [self.inProgressPartsDictionary allKeys]) { + AWSS3TransferUtilityUploadSubTask *subTask = [self.inProgressPartsDictionary objectForKey:key]; + subTask.status = AWSS3TransferUtilityTransferStatusInProgress; + [AWSS3TransferUtilityDatabaseHelper updateTransferRequestStatusInDB:subTask.transferID taskIdentifier:subTask.taskIdentifier status:AWSS3TransferUtilityTransferStatusInProgress databaseQueue:self.databaseQueue]; + [subTask.sessionTask resume]; + } + self.status = AWSS3TransferUtilityTransferStatusInProgress; + //Update the Master Record + [AWSS3TransferUtilityDatabaseHelper updateTransferRequestStatusInDB:_transferID taskIdentifier:0 status:AWSS3TransferUtilityTransferStatusInProgress databaseQueue:self.databaseQueue]; +} + +- (void)suspend { + for (NSNumber *key in [self.inProgressPartsDictionary allKeys]) { + AWSS3TransferUtilityUploadSubTask *subTask = [self.inProgressPartsDictionary objectForKey:key]; + [subTask.sessionTask suspend]; + subTask.status = AWSS3TransferUtilityTransferStatusPaused; + [AWSS3TransferUtilityDatabaseHelper updateTransferRequestStatusInDB:subTask.transferID taskIdentifier:subTask.taskIdentifier status:AWSS3TransferUtilityTransferStatusPaused databaseQueue:self.databaseQueue]; + } + self.status = AWSS3TransferUtilityTransferStatusPaused; + //Update the Master Record + [AWSS3TransferUtilityDatabaseHelper updateTransferRequestStatusInDB:_transferID taskIdentifier:0 status:AWSS3TransferUtilityTransferStatusPaused databaseQueue:self.databaseQueue]; +} + +-(void) setCompletionHandler:(AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock)completionHandler { + + self.expression.completionHandler = completionHandler; + //If the task has already completed successfully, call the completion handler + if (self.status == AWSS3TransferUtilityTransferStatusCompleted) { + _expression.completionHandler(self, nil); + } + //If the task has completed with error, call the completion handler + else if (self.error ) { + _expression.completionHandler(self, self.error); + } +} + +-(void) setProgressBlock:(AWSS3TransferUtilityMultiPartProgressBlock)progressBlock { + self.expression.progressBlock = progressBlock; +} + +@end + +@implementation AWSS3TransferUtilityDownloadTask + +- (AWSS3TransferUtilityDownloadExpression *)expression { + if (!_expression) { + _expression = [AWSS3TransferUtilityDownloadExpression new]; + } + return _expression; +} + +-(void) cancel { + self.cancelled = YES; + self.status = AWSS3TransferUtilityTransferStatusCancelled; + [self.sessionTask cancel]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:self.transferID databaseQueue:self.databaseQueue]; +} + +-(void) setCompletionHandler:(AWSS3TransferUtilityDownloadCompletionHandlerBlock)completionHandler { + + self.expression.completionHandler = completionHandler; + //If the task has already completed successfully, call the completion handler + if (self.status == AWSS3TransferUtilityTransferStatusCompleted) { + _expression.completionHandler(self, self.location, self.data, nil); + } + //If the task has completed with error, call the completion handler + else if (self.error ) { + _expression.completionHandler(self, self.location, self.data, self.error); + } +} + +-(void) setProgressBlock:(AWSS3TransferUtilityProgressBlock)progressBlock { + self.expression.progressBlock = progressBlock; +} + +@end + +@implementation AWSS3TransferUtilityUploadSubTask +@end + +#pragma mark - AWSS3TransferUtilityExpressions + +@implementation AWSS3TransferUtilityExpression + +- (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 AWSS3TransferUtilityUploadExpression +- (NSString *)contentMD5 { + return [self.internalRequestHeaders valueForKey:@"Content-MD5"]; +} + +- (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)assignRequestParameters:(AWSS3GetPreSignedURLRequest *)getPreSignedURLRequest { + for (NSString *key in self.internalRequestParameters) { + [getPreSignedURLRequest setValue:self.internalRequestParameters[key] + forRequestParameter:key]; + } +} + +@end + +@implementation AWSS3TransferUtilityDownloadExpression +@end diff --git a/AWSS3/Info.plist b/AWSS3/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSS3/Info.plist +++ b/AWSS3/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSS3Tests/AWSS3Tests.m b/AWSS3Tests/AWSS3Tests.m index 1fb95b757d3..4121030c5c1 100644 --- a/AWSS3Tests/AWSS3Tests.m +++ b/AWSS3Tests/AWSS3Tests.m @@ -1,5 +1,5 @@ // -// Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// 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. @@ -31,12 +31,14 @@ @implementation AWSS3Tests static NSURL *tempSmallURL = nil; static NSString *testBucketNameGeneral = nil; +static NSMutableArray *testBucketsCreated; + (void)setUp { [super setUp]; [AWSTestUtility setupCognitoCredentialsProvider]; //[AWSTestUtility setupCrdentialsViaFile]; + testBucketsCreated = [NSMutableArray new]; //Create bucketName @@ -44,7 +46,7 @@ + (void)setUp { testBucketNameGeneral = [NSString stringWithFormat:@"%@%lld", AWSS3TestBucketNamePrefix, (int64_t)timeIntervalSinceReferenceDate]; [AWSS3Tests createBucketWithName:testBucketNameGeneral]; - + [testBucketsCreated addObject:testBucketNameGeneral]; //Create a large temporary file for uploading & downloading test tempLargeURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-s3tmTestTempLarge",testBucketNameGeneral]]]; @@ -114,8 +116,10 @@ + (void)tearDown { // Delete all contents of the bucket [AWSS3Tests deleteAllObjectsFromBucket:testBucketNameGeneral]; - //Delete Bucket - [AWSS3Tests deleteBucketWithName:testBucketNameGeneral]; + //Delete Buckets + for ( NSString *bucketName in testBucketsCreated) { + [AWSS3Tests deleteBucketWithName:bucketName]; + } //Delete Temp files if (tempLargeURL) { @@ -304,7 +308,8 @@ - (void)testListBucket { - (void)testCreateDeleteBucket { NSString *bucketNameTest2 = [testBucketNameGeneral stringByAppendingString:@"-testcreatedeletebucket"]; - + [testBucketsCreated addObject:bucketNameTest2]; + AWSS3 *s3 = [AWSS3 defaultS3]; XCTAssertNotNil(s3); @@ -348,6 +353,68 @@ - (void)testCreateDeleteBucket { }] waitUntilFinished]; } +- (void)testCreateDeleteBucketWithDefaultEncryption { + NSString *bucketNameTest2 = [testBucketNameGeneral stringByAppendingString:@"-testbucket"]; + [testBucketsCreated addObject:bucketNameTest2]; + + AWSS3 *s3 = [AWSS3 defaultS3]; + XCTAssertNotNil(s3); + + AWSS3CreateBucketRequest *createBucketReq = [AWSS3CreateBucketRequest new]; + createBucketReq.ACL = AWSS3BucketCannedACLAuthenticatedRead; + createBucketReq.bucket = bucketNameTest2; + +#if AWS_TEST_BJS_INSTEAD + AWSS3CreateBucketConfiguration *createBucketConfiguration = [AWSS3CreateBucketConfiguration new]; + createBucketConfiguration.locationConstraint = AWSS3BucketLocationConstraintCNNorth1; + createBucketReq.createBucketConfiguration = createBucketConfiguration; +#endif + + [[[[[[[s3 createBucket:createBucketReq] continueWithBlock:^id(AWSTask *task) { + XCTAssertNil(task.error, @"The request failed. error: [%@]", task.error); + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]]; + + //Setup Default encryption here + AWSS3ServerSideEncryptionRule *rule= [AWSS3ServerSideEncryptionRule new]; + AWSS3ServerSideEncryptionByDefault *applyServerSideEncryptionByDefault =[AWSS3ServerSideEncryptionByDefault new];applyServerSideEncryptionByDefault.SSEAlgorithm=AWSS3ServerSideEncryptionAwsKms; + rule.applyServerSideEncryptionByDefault = applyServerSideEncryptionByDefault; + + AWSS3ServerSideEncryptionConfiguration *configuration = [AWSS3ServerSideEncryptionConfiguration new]; + configuration.rules = @[rule]; + + AWSS3PutBucketEncryptionRequest *request = + [AWSS3PutBucketEncryptionRequest new]; + request.bucket = bucketNameTest2; + request.serverSideEncryptionConfiguration=configuration; + return [s3 putBucketEncryption:request]; + }] continueWithBlock:^id (AWSTask *task) { + XCTAssertNil(task.error, @"The request failed. error: [%@]", task.error); + return [s3 listBuckets:[AWSRequest new]]; + }] continueWithBlock:^id(AWSTask *task) { + //Check if bucket is there. + XCTAssertNil(task.error, @"The request failed. error: [%@]", task.error); + XCTAssertTrue([task.result isKindOfClass:[AWSS3ListBucketsOutput class]],@"The response object is not a class of [%@]", NSStringFromClass([AWSS3ListBucketsOutput class])); + + AWSS3ListBucketsOutput *listBucketOutput = task.result; + XCTAssertTrue([self isContainBucketName:bucketNameTest2 inBucketArray:listBucketOutput.buckets], @"%@ can not be found in S3 Bucket List",bucketNameTest2); + + AWSS3DeleteBucketRequest *deleteBucketReq = [AWSS3DeleteBucketRequest new]; + deleteBucketReq.bucket = bucketNameTest2; + return [s3 deleteBucket:deleteBucketReq]; + }] continueWithBlock:^id(AWSTask *task) { + XCTAssertNil(task.error, @"The request failed. error: [%@]", task.error); + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]]; + return [s3 listBuckets:[AWSRequest new]]; + }] continueWithBlock:^id(AWSTask *task) { + XCTAssertNil(task.error, @"The request failed. error: [%@]", task.error); + XCTAssertTrue([task.result isKindOfClass:[AWSS3ListBucketsOutput class]],@"The response object is not a class of [%@]", NSStringFromClass([AWSS3ListBucketsOutput class])); + + AWSS3ListBucketsOutput *listBucketOutput = task.result; + XCTAssertFalse([self isContainBucketName:bucketNameTest2 inBucketArray:listBucketOutput.buckets], @"%@ should NOT befound in S3 Bucket List",bucketNameTest2); + return nil; + }] waitUntilFinished]; +} + - (void)testBucketCustomEndpoint { id credentialsProvider = [[[AWSServiceManager defaultServiceManager] defaultServiceConfiguration] credentialsProvider]; @@ -428,6 +495,7 @@ - (void)testEmptyFolder{ - (void)testPutBucketWithGrants { NSString *grantBucketName = [testBucketNameGeneral stringByAppendingString:@"-grant"]; + [testBucketsCreated addObject:grantBucketName]; XCTAssertTrue([AWSS3Tests createBucketWithName:grantBucketName]); AWSS3 *s3 = [AWSS3 defaultS3]; diff --git a/AWSS3Tests/AWSS3TransferUtilityTests.swift b/AWSS3Tests/AWSS3TransferUtilityTests.swift index 981c05a32aa..24bb68abe59 100644 --- a/AWSS3Tests/AWSS3TransferUtilityTests.swift +++ b/AWSS3Tests/AWSS3TransferUtilityTests.swift @@ -1,5 +1,5 @@ // -// Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// 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. @@ -1832,8 +1832,8 @@ class AWSS3TransferUtilityTests: XCTestCase { let fmt = DateFormatter() fmt.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz" - //Sleep for 2 seconds to make sure that we can set the current date/time for the ifModifiedSince header. - sleep(2) + //Sleep for 5 seconds to make sure that we can set the current date/time for the ifModifiedSince header. + sleep(5) let ifModifiedSince = Date() downloadExpression.setValue(fmt.string(from:ifModifiedSince), forRequestHeader: "If-Modified-Since") @@ -2044,7 +2044,7 @@ class AWSS3TransferUtilityTests: XCTestCase { } else { XCTFail() } - if count == 25 { + if count >= 25 { expectation.fulfill() } } @@ -2072,6 +2072,7 @@ class AWSS3TransferUtilityTests: XCTestCase { XCTAssertNil(task.error) return nil }) + sleep(1) } } else { @@ -2095,11 +2096,91 @@ class AWSS3TransferUtilityTests: XCTestCase { return nil }) - waitForExpectations(timeout: 300) { (error) in + waitForExpectations(timeout: 450) { (error) in XCTAssertNil(error) } } + func testUploadAndDownloadDataSingleClientMultipleFiles() { + let expectation = self.expectation(description: "The completion handler called.") + var completedTransfers = 0; + + let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry") + let uploadExpression = AWSS3TransferUtilityUploadExpression() + + uploadExpression.progressBlock = {(task, progress) in + print("Upload progress: ", progress.fractionCompleted) + } + + + var testData = "Test123456789" + for _ in 1...10 { + testData = testData + testData; + } + + for i in 1...25 { + let uploadCompletionHandler = { (task: AWSS3TransferUtilityUploadTask, error: Error?) -> Void in + XCTAssertNil(error) + if ( error != nil ) { + completedTransfers = completedTransfers + 1 + if ( completedTransfers >= 25 ) { + expectation.fulfill() + } + return + } + + if let HTTPResponse = task.response { + XCTAssertEqual(HTTPResponse.statusCode, 200) + + let downloadExpression = AWSS3TransferUtilityDownloadExpression() + 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) + completedTransfers = completedTransfers+1; + } else { + XCTFail() + } + if completedTransfers >= 25 { + expectation.fulfill() + } + } + + let url = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("file\(i)") + AWSS3TransferUtility.s3TransferUtility(forKey: "with-retry").download(to: url!, + bucket: "ios-v2-s3.periods", + key: "testDataForConcurrentDownloads\(i).txt", + expression: downloadExpression, + completionHandler: downloadCompletionHandler).continueWith(block: { (task) -> Any? in + XCTAssertNil(task.error) + return nil + }) + } + } + + transferUtility.uploadData( + testData.data(using: String.Encoding.utf8)!, + bucket: "ios-v2-s3.periods", + key: "testDataForConcurrentDownloads\(i).txt", + contentType: "text/plain", + expression: uploadExpression, + completionHandler: uploadCompletionHandler + ).continueWith (block: { (task) -> AnyObject? in + XCTAssertNil(task.error) + + return nil + }) + } + + waitForExpectations(timeout: 300) { (error) in + XCTAssertNil(error) + } + } + + func testLargeFileUploadCredentialsExpired() { let expectation = self.expectation(description: "The completion handler called.") let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "short-expiry") diff --git a/AWSSES.podspec b/AWSSES.podspec index cf551d3dabd..83e4f51eaef 100644 --- a/AWSSES.podspec +++ b/AWSSES.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSSES' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSSES/*.{h,m}' end diff --git a/AWSSES/AWSSESService.m b/AWSSES/AWSSESService.m index 2d884c68dc1..0b5530b9402 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.22"; +static NSString *const AWSSESSDKVersion = @"2.6.23"; @interface AWSSESResponseSerializer : AWSXMLResponseSerializer diff --git a/AWSSES/Info.plist b/AWSSES/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSSES/Info.plist +++ b/AWSSES/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSSNS.podspec b/AWSSNS.podspec index e936a0e98d0..1a2e6e5a6f5 100644 --- a/AWSSNS.podspec +++ b/AWSSNS.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSSNS' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSSNS/*.{h,m}' end diff --git a/AWSSNS/AWSSNSService.m b/AWSSNS/AWSSNSService.m index 742b556ed9c..9a59d1227cc 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.22"; +static NSString *const AWSSNSSDKVersion = @"2.6.23"; @interface AWSSNSResponseSerializer : AWSXMLResponseSerializer diff --git a/AWSSNS/Info.plist b/AWSSNS/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSSNS/Info.plist +++ b/AWSSNS/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSSQS.podspec b/AWSSQS.podspec index 7327fd3ce71..a343b558e9c 100644 --- a/AWSSQS.podspec +++ b/AWSSQS.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSSQS' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSSQS/*.{h,m}' end diff --git a/AWSSQS/AWSSQSService.m b/AWSSQS/AWSSQSService.m index 280255a34ae..d0f71026e71 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.22"; +static NSString *const AWSSQSSDKVersion = @"2.6.23"; @interface AWSSQSResponseSerializer : AWSXMLResponseSerializer diff --git a/AWSSQS/Info.plist b/AWSSQS/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSSQS/Info.plist +++ b/AWSSQS/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSSimpleDB.podspec b/AWSSimpleDB.podspec index ed41079a76d..cdd524722ee 100644 --- a/AWSSimpleDB.podspec +++ b/AWSSimpleDB.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSSimpleDB' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSSimpleDB/*.{h,m}' end diff --git a/AWSSimpleDB/AWSSimpleDBService.m b/AWSSimpleDB/AWSSimpleDBService.m index ae2f9e2d60a..f2191342db7 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.22"; +static NSString *const AWSSimpleDBSDKVersion = @"2.6.23"; @interface AWSSimpleDBResponseSerializer : AWSXMLResponseSerializer diff --git a/AWSSimpleDB/Info.plist b/AWSSimpleDB/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSSimpleDB/Info.plist +++ b/AWSSimpleDB/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSTranscribe.podspec b/AWSTranscribe.podspec index cb3c3651b81..dfa0247a867 100644 --- a/AWSTranscribe.podspec +++ b/AWSTranscribe.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSTranscribe' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSTranscribe/*.{h,m}' end diff --git a/AWSTranscribe/AWSTranscribeService.m b/AWSTranscribe/AWSTranscribeService.m index 3d886e7cc10..0ab52664745 100644 --- a/AWSTranscribe/AWSTranscribeService.m +++ b/AWSTranscribe/AWSTranscribeService.m @@ -26,7 +26,7 @@ #import "AWSTranscribeResources.h" static NSString *const AWSInfoTranscribe = @"Transcribe"; -static NSString *const AWSTranscribeSDKVersion = @"2.6.22"; +static NSString *const AWSTranscribeSDKVersion = @"2.6.23"; @interface AWSTranscribeResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSTranscribe/Info.plist b/AWSTranscribe/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSTranscribe/Info.plist +++ b/AWSTranscribe/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSTranslate.podspec b/AWSTranslate.podspec index 5f845045664..cef1155b204 100644 --- a/AWSTranslate.podspec +++ b/AWSTranslate.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSTranslate' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' + s.dependency 'AWSCore', '2.6.23' s.source_files = 'AWSTranslate/*.{h,m}' end diff --git a/AWSTranslate/AWSTranslateService.m b/AWSTranslate/AWSTranslateService.m index 30364e8d07a..8c3ad664ca6 100644 --- a/AWSTranslate/AWSTranslateService.m +++ b/AWSTranslate/AWSTranslateService.m @@ -26,7 +26,7 @@ #import "AWSTranslateResources.h" static NSString *const AWSInfoTranslate = @"Translate"; -static NSString *const AWSTranslateSDKVersion = @"2.6.22"; +static NSString *const AWSTranslateSDKVersion = @"2.6.23"; @interface AWSTranslateResponseSerializer : AWSJSONResponseSerializer diff --git a/AWSTranslate/Info.plist b/AWSTranslate/Info.plist index ed624ca8f6f..52bdaa70347 100644 --- a/AWSTranslate/Info.plist +++ b/AWSTranslate/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.6.22 + 2.6.23 CFBundleSignature ???? CFBundleVersion diff --git a/AWSUserPoolsSignIn.podspec b/AWSUserPoolsSignIn.podspec index a0fee072f3b..8228ab1491d 100644 --- a/AWSUserPoolsSignIn.podspec +++ b/AWSUserPoolsSignIn.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AWSUserPoolsSignIn' - s.version = '2.6.22' + s.version = '2.6.23' 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.22' - s.dependency 'AWSAuthCore', '2.6.22' + s.dependency 'AWSCognitoIdentityProvider', '2.6.23' + s.dependency 'AWSAuthCore', '2.6.23' 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 5fdb9f549ba..6919b506685 100644 --- a/AWSiOSSDKv2.podspec +++ b/AWSiOSSDKv2.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'AWSiOSSDKv2' - s.version = '2.6.22' + s.version = '2.6.23' 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,115 +15,115 @@ Pod::Spec.new do |s| s.requires_arc = true s.subspec 'AWSCore' do |aws| - aws.dependency 'AWSCore', '2.6.22' + aws.dependency 'AWSCore', '2.6.23' end s.subspec 'AWSAPIGateway' do |apigateway| - apigateway.dependency 'AWSAPIGateway', '2.6.22' + apigateway.dependency 'AWSAPIGateway', '2.6.23' end s.subspec 'AutoScaling' do |autoscaling| - autoscaling.dependency 'AWSAutoScaling', '2.6.22' + autoscaling.dependency 'AWSAutoScaling', '2.6.23' end s.subspec 'CloudWatch' do |cloudwatch| - cloudwatch.dependency 'AWSCloudWatch', '2.6.22' + cloudwatch.dependency 'AWSCloudWatch', '2.6.23' end s.subspec 'Pinpoint' do |pp| - pp.dependency 'AWSPinpoint', '2.6.22' + pp.dependency 'AWSPinpoint', '2.6.23' end s.subspec 'AWSCognito' do |cognito| - cognito.dependency 'AWSCognito', '2.6.22' + cognito.dependency 'AWSCognito', '2.6.23' end s.subspec 'AWSCognitoIdentityProvider' do |cognitoidentityprovider| - cognitoidentityprovider.dependency 'AWSCognitoIdentityProvider', '2.6.22' + cognitoidentityprovider.dependency 'AWSCognitoIdentityProvider', '2.6.23' end s.subspec 'AWSComprehend' do |comprehend| - comprehend.dependency 'AWSComprehend', '2.6.22' + comprehend.dependency 'AWSComprehend', '2.6.23' end s.subspec 'DynamoDB' do |ddb| - ddb.dependency 'AWSDynamoDB', '2.6.22' + ddb.dependency 'AWSDynamoDB', '2.6.23' end s.subspec 'EC2' do |ec2| - ec2.dependency 'AWSEC2', '2.6.22' + ec2.dependency 'AWSEC2', '2.6.23' end s.subspec 'ElasticLoadBalancing' do |elasticloadbalancing| - elasticloadbalancing.dependency 'AWSElasticLoadBalancing', '2.6.22' + elasticloadbalancing.dependency 'AWSElasticLoadBalancing', '2.6.23' end s.subspec 'AWSIoT' do |iot| - iot.dependency 'AWSIoT', '2.6.22' + iot.dependency 'AWSIoT', '2.6.23' end s.subspec 'Kinesis' do |kinesis| - kinesis.dependency 'AWSKinesis', '2.6.22' + kinesis.dependency 'AWSKinesis', '2.6.23' end s.subspec 'AWSKMS' do |kms| - kms.dependency 'AWSKMS', '2.6.22' + kms.dependency 'AWSKMS', '2.6.23' end s.subspec 'AWSLambda' do |lambda| - lambda.dependency 'AWSLambda', '2.6.22' + lambda.dependency 'AWSLambda', '2.6.23' end s.subspec 'AWSLex' do |lex| - lex.dependency 'AWSLex', '2.6.22' + lex.dependency 'AWSLex', '2.6.23' end s.subspec 'AWSLogs' do |log| - log.dependency 'AWSLogs', '2.6.22' + log.dependency 'AWSLogs', '2.6.23' end s.subspec 'AWSMachineLearning' do |machinelearning| - machinelearning.dependency 'AWSMachineLearning', '2.6.22' + machinelearning.dependency 'AWSMachineLearning', '2.6.23' end s.subspec 'AWSPolly' do |polly| - polly.dependency 'AWSPolly', '2.6.22' + polly.dependency 'AWSPolly', '2.6.23' end s.subspec 'MobileAnalytics' do |mobileanalytics| - mobileanalytics.dependency 'AWSMobileAnalytics', '2.6.22' + mobileanalytics.dependency 'AWSMobileAnalytics', '2.6.23' end s.subspec 'AWSRekognition' do |rekognition| - rekognition.dependency 'AWSRekognition', '2.6.22' + rekognition.dependency 'AWSRekognition', '2.6.23' end s.subspec 'AWSS3' do |s3| - s3.dependency 'AWSS3', '2.6.22' + s3.dependency 'AWSS3', '2.6.23' end s.subspec 'AWSSES' do |ses| - ses.dependency 'AWSSES', '2.6.22' + ses.dependency 'AWSSES', '2.6.23' end s.subspec 'AWSSimpleDB' do |simpledb| - simpledb.dependency 'AWSSimpleDB', '2.6.22' + simpledb.dependency 'AWSSimpleDB', '2.6.23' end s.subspec 'AWSSNS' do |sns| - sns.dependency 'AWSSNS', '2.6.22' + sns.dependency 'AWSSNS', '2.6.23' end s.subspec 'AWSSQS' do |sqs| - sqs.dependency 'AWSSQS', '2.6.22' + sqs.dependency 'AWSSQS', '2.6.23' end s.subspec 'AWSTranscribe' do |transcribe| - transcribe.dependency 'AWSTranscribe', '2.6.22' + transcribe.dependency 'AWSTranscribe', '2.6.23' end s.subspec 'AWSTranslate' do |translate| - translate.dependency 'AWSTranslate', '2.6.22' + translate.dependency 'AWSTranslate', '2.6.23' end end diff --git a/AWSiOSSDKv2.xcodeproj/project.pbxproj b/AWSiOSSDKv2.xcodeproj/project.pbxproj index 703aca2dbaf..e633d2bcd57 100644 --- a/AWSiOSSDKv2.xcodeproj/project.pbxproj +++ b/AWSiOSSDKv2.xcodeproj/project.pbxproj @@ -301,6 +301,10 @@ 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 */; }; + 9A2562EC20E2E0D100D2451E /* AWSS3TransferUtility+HeaderHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A2562EA20E2E0D100D2451E /* AWSS3TransferUtility+HeaderHelper.h */; }; + 9A2562ED20E2E0D100D2451E /* AWSS3TransferUtility+HeaderHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A2562EB20E2E0D100D2451E /* AWSS3TransferUtility+HeaderHelper.m */; }; + 9A2562F320E2E4D400D2451E /* AWSS3TransferUtilityTasks.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A2562F220E2E4D400D2451E /* AWSS3TransferUtilityTasks.m */; }; + 9A2562F520E31B7A00D2451E /* AWSS3TransferUtilityTasks.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A2562F420E2E50A00D2451E /* AWSS3TransferUtilityTasks.h */; settings = {ATTRIBUTES = (Public, ); }; }; 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 */; }; 9A7ACC3520B0E85100DDBEC1 /* AWSTranslate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A7ACC2020B0E85000DDBEC1 /* AWSTranslate.framework */; }; @@ -341,6 +345,8 @@ 9A7ACD0920B1CF3900DDBEC1 /* AWSTestUtility.m in Sources */ = {isa = PBXBuildFile; fileRef = CEB8EF2E1C6A69A00098B15B /* AWSTestUtility.m */; }; 9A7ACD0A20B1CF5C00DDBEC1 /* libOCMock.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CEB8EF551C6A6A2E0098B15B /* libOCMock.a */; }; 9A7ACD0B20B1CF7C00DDBEC1 /* credentials.json in Resources */ = {isa = PBXBuildFile; fileRef = CEB8EF3E1C6A69AB0098B15B /* credentials.json */; }; + 9A82CE5620E295170099B04E /* AWSS3TransferUtilityDatabaseHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A82CE5420E295170099B04E /* AWSS3TransferUtilityDatabaseHelper.h */; }; + 9A82CE5720E295170099B04E /* AWSS3TransferUtilityDatabaseHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A82CE5520E295170099B04E /* AWSS3TransferUtilityDatabaseHelper.m */; }; 9AA55EF7209F7EB300FF2AC4 /* AWSIoTDataManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA55EF6209F7EB300FF2AC4 /* AWSIoTDataManagerTests.swift */; }; 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, ); }; }; @@ -3082,6 +3088,10 @@ 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 = ""; }; + 9A2562EA20E2E0D100D2451E /* AWSS3TransferUtility+HeaderHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AWSS3TransferUtility+HeaderHelper.h"; sourceTree = ""; }; + 9A2562EB20E2E0D100D2451E /* AWSS3TransferUtility+HeaderHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "AWSS3TransferUtility+HeaderHelper.m"; sourceTree = ""; }; + 9A2562F220E2E4D400D2451E /* AWSS3TransferUtilityTasks.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AWSS3TransferUtilityTasks.m; sourceTree = ""; }; + 9A2562F420E2E50A00D2451E /* AWSS3TransferUtilityTasks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AWSS3TransferUtilityTasks.h; 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 = ""; }; 9A7ACC2020B0E85000DDBEC1 /* AWSTranslate.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AWSTranslate.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -3116,6 +3126,8 @@ 9A7ACCF920B11CDD00DDBEC1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9A7ACD0520B12F3400DDBEC1 /* AWSTranslateTests-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AWSTranslateTests-Bridging-Header.h"; sourceTree = ""; }; 9A7ACD0620B1CE0B00DDBEC1 /* AWSComprehendTests-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AWSComprehendTests-Bridging-Header.h"; sourceTree = ""; }; + 9A82CE5420E295170099B04E /* AWSS3TransferUtilityDatabaseHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AWSS3TransferUtilityDatabaseHelper.h; sourceTree = ""; }; + 9A82CE5520E295170099B04E /* AWSS3TransferUtilityDatabaseHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AWSS3TransferUtilityDatabaseHelper.m; sourceTree = ""; }; 9AA55EF5209F7EB200FF2AC4 /* AWSIoTTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AWSIoTTests-Bridging-Header.h"; sourceTree = ""; }; 9AA55EF6209F7EB300FF2AC4 /* AWSIoTDataManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSIoTDataManagerTests.swift; sourceTree = ""; }; B52FBE911F17414A000F0C00 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; }; @@ -6185,6 +6197,12 @@ CE9DE9C11C6A7C2E0060793F /* Info.plist */, 9A293CEE203885A300A12241 /* AWSS3TransferUtility+Validation.h */, 9A293CEF203885A300A12241 /* AWSS3TransferUtility+Validation.m */, + 9A82CE5420E295170099B04E /* AWSS3TransferUtilityDatabaseHelper.h */, + 9A82CE5520E295170099B04E /* AWSS3TransferUtilityDatabaseHelper.m */, + 9A2562EA20E2E0D100D2451E /* AWSS3TransferUtility+HeaderHelper.h */, + 9A2562EB20E2E0D100D2451E /* AWSS3TransferUtility+HeaderHelper.m */, + 9A2562F220E2E4D400D2451E /* AWSS3TransferUtilityTasks.m */, + 9A2562F420E2E50A00D2451E /* AWSS3TransferUtilityTasks.h */, ); path = AWSS3; sourceTree = ""; @@ -7167,11 +7185,14 @@ buildActionMask = 2147483647; files = ( CE9DE9E31C6A7C5E0060793F /* AWSS3PreSignedURL.h in Headers */, + 9A2562EC20E2E0D100D2451E /* AWSS3TransferUtility+HeaderHelper.h in Headers */, 9A293CF0203885A300A12241 /* AWSS3TransferUtility+Validation.h in Headers */, CE9DE9E91C6A7C5E0060793F /* AWSS3TransferManager.h in Headers */, 18CDFB281D66561F0021B1DE /* AWSS3Serializer.h in Headers */, CE9DE9E11C6A7C5E0060793F /* AWSS3Model.h in Headers */, + 9A2562F520E31B7A00D2451E /* AWSS3TransferUtilityTasks.h in Headers */, CE9DE9EB1C6A7C5E0060793F /* AWSS3TransferUtility.h in Headers */, + 9A82CE5620E295170099B04E /* AWSS3TransferUtilityDatabaseHelper.h in Headers */, CE9DE9E51C6A7C5E0060793F /* AWSS3Resources.h in Headers */, CE9DE9E71C6A7C5E0060793F /* AWSS3Service.h in Headers */, CE9DE9D41C6A7C360060793F /* AWSS3.h in Headers */, @@ -10923,8 +10944,11 @@ files = ( CE9DE9E61C6A7C5E0060793F /* AWSS3Resources.m in Sources */, CE9DE9EA1C6A7C5E0060793F /* AWSS3TransferManager.m in Sources */, + 9A2562ED20E2E0D100D2451E /* AWSS3TransferUtility+HeaderHelper.m in Sources */, CE9DE9E21C6A7C5E0060793F /* AWSS3Model.m in Sources */, CE9DE9E41C6A7C5E0060793F /* AWSS3PreSignedURL.m in Sources */, + 9A82CE5720E295170099B04E /* AWSS3TransferUtilityDatabaseHelper.m in Sources */, + 9A2562F320E2E4D400D2451E /* AWSS3TransferUtilityTasks.m in Sources */, 18DF08E61D349137004C7D19 /* AWSS3RequestRetryHandler.m in Sources */, CE9DE9E81C6A7C5E0060793F /* AWSS3Service.m in Sources */, CE9DE9EC1C6A7C5E0060793F /* AWSS3TransferUtility.m in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index 2aedb42dfcf..93fc1541f12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ # AWS Mobile SDK for iOS CHANGELOG +## 2.6.23 + +### Enhancements + +* **Amazon S3** + * Added enhancements to TransferUtility to resume transfers after app restart, convenience methods for status, completion handler and status tracking. See [issue#489](https://github.com/aws/aws-sdk-ios/issues/489), [issue#755](https://github.com/aws/aws-sdk-ios/issues/755), [issue#759](https://github.com/aws/aws-sdk-ios/issues/759), and [issue#769](https://github.com/aws/aws-sdk-ios/issues/769) + +### Bug Fixes + +* **AWS IoT** + * Fixed bugs in AWS IoT reconnection logic. See [issue#965](https://github.com/aws/aws-sdk-ios/issues/965), [issue#968](https://github.com/aws/aws-sdk-ios/issues/968), and [issue#972](https://github.com/aws/aws-sdk-ios/issues/972) + ## 2.6.22 ### Bug Fixes @@ -46,7 +58,7 @@ ### Bug Fixes * **AWS IoT** - * Fixed crash in AWS IoT due to use of API is not backward compatible with iOS 8.0. See [issue#949](https://github.com/aws/aws-sdk-ios/issues/949) + * Fixed crash in AWS IoT due to use of API that was not backward compatible with iOS 8.0. See [issue#949](https://github.com/aws/aws-sdk-ios/issues/949) ### Enhancements diff --git a/README.md b/README.md index f98a9537016..4e796026261 100644 --- a/README.md +++ b/README.md @@ -357,7 +357,7 @@ CocoaLumberjack logging levels are additive such that when the level is set to v **Swift** ```swift -AWSDDLog.sharedInstance().logLevel = .verbose +AWSDDLog.sharedInstance.logLevel = .verbose ``` The following logging level options are available: diff --git a/Scripts/APIReference.sh b/Scripts/APIReference.sh deleted file mode 100644 index a2dd15be6d2..00000000000 --- a/Scripts/APIReference.sh +++ /dev/null @@ -1,113 +0,0 @@ -#!/bin/sh -set -x - -# remove everything generated by this script -function cleanup -{ - rm -rf docs/reference - rm -rf Documentation - rm -rf docs_tmp -} - - -VERSION="2.6.22" -if [ -n $1 ] && [ "$1" == "clean" ]; -then - cleanup - exit 0 -else - cd "$SOURCE_ROOT" - - if [ -d docs/reference ]; - then - cleanup - fi - - rm -rf docs_tmp - mkdir -p docs_tmp - - cp -r AWSAPIGateway ./docs_tmp/AWSAPIGateway - cp -r AWSAutoScaling ./docs_tmp/AWSAutoScaling - cp -r AWSCore ./docs_tmp/AWSCore - cp -r AWSCognito ./docs_tmp/AWSCognito - cp -r AWSCognitoIdentityProvider ./docs_tmp/AWSCognitoIdentityProvider - cp -r AWSCloudWatch ./docs_tmp/AWSCloudWatch - cp -r AWSDynamoDB ./docs_tmp/AWSDynamoDB - cp -r AWSElasticLoadBalancing ./docs_tmp/AWSElasticLoadBalancing - cp -r AWSIoT ./docs_tmp/AWSIoT - cp -r AWSKinesis ./docs_tmp/AWSKinesis - cp -r AWSKMS ./docs_tmp/AWSKMS - cp -r AWSLambda ./docs_tmp/AWSLambda - cp -r AWSLex ./docs_tmp/AWSLex - cp -r AWSLogs ./docs_tmp/AWSLogs - cp -r AWSMachineLearning ./docs_tmp/AWSMachineLearning - cp -r AWSMobileAnalytics ./docs_tmp/AWSMobileAnalytics - cp -r AWSPinpoint ./docs_tmp/AWSPinpoint - cp -r AWSPolly ./docs_tmp/AWSPolly - cp -r AWSRekognition ./docs_tmp/AWSRekognition - cp -r AWSS3 ./docs_tmp/AWSS3 - cp -r AWSSES ./docs_tmp/AWSSES - cp -r AWSSimpleDB ./docs_tmp/AWSSimpleDB - cp -r AWSSNS ./docs_tmp/AWSSNS - cp -r AWSSQS ./docs_tmp/AWSSQS - cp -r AWSTranscribe ./docs_tmp/AWSTranscribe - cp -r AWSTranslate ./docs_tmp/AWSTranslate - cp -r AWSComprehend ./docs_tmp/AWSComprehend - cp -r AWSCognitoAuth ./docs_tmp/AWSCognitoAuth - cp -r AWSAuthSDK/Sources/AWSAuthCore ./docs_tmp/AWSAuthSDK/ - cp -r AWSAuthSDK/Sources/AWSAuthUI ./docs_tmp/AWSAuthSDK/ - cp -r AWSAuthSDK/Sources/AWSFacebookSignIn ./docs_tmp/AWSAuthSDK/ - cp -r AWSAuthSDK/Sources/AWSGoogleSignIn ./docs_tmp/AWSAuthSDK/ - cp -r AWSAuthSDK/Sources/AWSUserPoolsSignIn ./docs_tmp/AWSAuthSDK/ - cp -r AWSAuthSDK/Sources/AWSAuthUI ./docs_tmp/AWSAuthSDK/ - cp -r AWSAuthSDK/Sources/AWSMobileClient ./docs_tmp/AWSAuthSDK/ - - rm -rf ./docs_tmp/AWSCore/Bolts - rm -rf ./docs_tmp/AWSCore/Fabric - rm -rf ./docs_tmp/AWSCore/FMDB - rm -rf ./docs_tmp/AWSCore/GZIP - rm -rf ./docs_tmp/AWSCore/Logging - rm -rf ./docs_tmp/AWSCore/Mantle - rm -rf ./docs_tmp/AWSCore/Reachability - rm -rf ./docs_tmp/AWSCore/TMCache - rm -rf ./docs_tmp/AWSCore/UICKeyChainStore - rm -rf ./docs_tmp/AWSCore/XMLDictionary - rm -rf ./docs_tmp/AWSCore/XMLWriter - rm -rf ./docs_tmp/AWSCognito/Internal - rm -rf ./docs_tmp/AWSCognito/Fabric - rm -rf ./docs_tmp/AWSCognitoIdentityProvider/Internal - rm -rf ./docs_tmp/AWSCognitoAuth/Internal - rm -rf ./docs_tmp/AWSMobileAnalytics/Internal - rm -rf ./docs_tmp/AWSIoT/Internal - rm -rf ./docs_tmp/AWSLex/Bluefront - rm -rf ./docs_tmp/AWSAuthSDK/UserPoolsUI - - cd docs_tmp - - # generate documenation - appledoc --verbose 1 \ - --output ../docs/reference \ - --exit-threshold 2 \ - --no-repeat-first-par \ - --explicit-crossref \ - --company-id aws.amazon.com \ - --project-name "AWS Mobile SDK for iOS v${VERSION}" \ - --project-version "${VERSION}" \ - --project-company "Amazon Web Services, Inc." \ - --create-html \ - --keep-intermediate-files \ - --index-desc ../aws-sdk-for-ios.markdown \ - ./ - - # get command execution result - result=$? - - if [ $result != 0 ]; - then - echo "Building the AWS Mobile SDK for iOS documentation FAILED!" - cleanup; - exit 1; - fi - - exit 0 -fi diff --git a/Scripts/DocsCleanup.sh b/Scripts/DocsCleanup.sh deleted file mode 100644 index 1493db6a277..00000000000 --- a/Scripts/DocsCleanup.sh +++ /dev/null @@ -1,4 +0,0 @@ -rm -rf docs/reference/docset/ -rm docs/reference/docset-installed.txt -cp -r docs/reference/html/ docs/docs/reference -rm -rf docs/reference diff --git a/Scripts/GenerateAppleDocs.sh b/Scripts/GenerateAppleDocs.sh index 64a30ba9d24..adff50cb215 100644 --- a/Scripts/GenerateAppleDocs.sh +++ b/Scripts/GenerateAppleDocs.sh @@ -9,7 +9,7 @@ function cleanup } -VERSION="2.6.22" +VERSION="2.6.23" if [ -n $1 ] && [ "$1" == "clean" ]; then cleanup diff --git a/docs/awstask.md b/docs/awstask.md deleted file mode 100644 index 7f2e3589f4f..00000000000 --- a/docs/awstask.md +++ /dev/null @@ -1 +0,0 @@ -With native AWSTask support in the SDK for iOS, you can chain async requests instead of nesting them. It makes the logic cleaner, while keeping the code more readable. Read [Working with AWSTask](http://docs.aws.amazon.com/mobile/sdkforios/developerguide/awstask.html) to learn how to use AWSTask. \ No newline at end of file diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 5f3324e6568..00000000000 --- a/docs/index.md +++ /dev/null @@ -1,40 +0,0 @@ -# AWS Mobile SDK for iOS - -[![Release](https://img.shields.io/github/release/aws/aws-sdk-ios.svg)]() -[![CocoaPods](https://img.shields.io/cocoapods/v/AWSiOSSDKv2.svg)]() -[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) -[![Twitter Follow](https://img.shields.io/twitter/follow/AWSforMobile.svg?style=social&label=Follow)](https://twitter.com/AWSforMobile) - -## Introduction - -AWS Mobile SDK for iOS offers a set of SDKs which make working with AWS easy. You could leverage services like `S3`, `AppSync`, `IoT`, `CognitoUserPools`, `DynamoDB`, and many other in your application with very few steps. We also provide utility clients like `S3TransferUtility`, `Auth`, `DynamoDB`, etc. which further simplify interaction with the cloud! - -## Quick Links - -* [API Reference](http://aws.github.io/aws-sdk-ios/docs/reference) -* [Source Code on Github](https://github.com/aws/aws-sdk-ios) - -## Setting Up - -???+info "Setup Requirements" - To use the AWS SDK for iOS, you will need the following installed on your development machine: - - * Xcode 8 or later - * iOS Simulators / iPhones with iOS 9 or later - - To get started with AWS Mobile SDK for iOS, follow __**[this link](setup/setup-dependencies.md)**__ to setup the dependencies and integrate them in your application. - -???- tip "SDK Samples" - The [samples](https://github.com/awslabs/aws-sdk-ios-samples) included with the SDK for iOS are standalone projects that are already set up for you. You can also integrate the SDK for iOS with your own existing project. - -## Talk to Us - -Visit our [GitHub Page](https://github.com/aws/aws-sdk-ios) to leave feedback and to connect with other users of the SDK. - -## Author - -Amazon Web Services - -## License - -See the **LICENSE** file for more info. diff --git a/docs/logging.md b/docs/logging.md deleted file mode 100644 index acf4a09fbeb..00000000000 --- a/docs/logging.md +++ /dev/null @@ -1,65 +0,0 @@ -As of version 2.5.4 of this SDK, logging utilizes [CocoaLumberjack](https://github.com/CocoaLumberjack/CocoaLumberjack), a flexible, fast, open source logging framework. It supports many capabilities including the ability to set logging level per output target, for instance, concise messages logged to the console and verbose messages to a log file. - -CocoaLumberjack logging levels are additive such that when the level is set to verbose, all messages from the levels below verbose are logged. It is also possible to set custom logging to meet your needs. For more information, see [CocoaLumberjack](https://github.com/CocoaLumberjack/CocoaLumberjack/blob/master/Documentation/CustomLogLevels.md) - -## Changing Log Levels - -```swift tab="Swift" -AWSDDLog.sharedInstance.logLevel = .verbose -``` - -```objective-c tab="Objective-C" -[AWSDDLog sharedInstance].logLevel = AWSDDLogLevelVerbose; -``` - -The following logging level options are available: - -```swift tab="Swift" -.off -.error -.warning -.info -.debug -.verbose -``` - -```objective-c tab="Objective-C" -AWSDDLogLevelOff -AWSDDLogLevelError -AWSDDLogLevelWarning -AWSDDLogLevelInfo -AWSDDLogLevelDebug -AWSDDLogLevelVerbose -``` - -We recommend setting the log level to `Off` before publishing to the Apple App Store. - -## Targeting Log Output - -CocoaLumberjack can direct logs to file or used as a framework that integrates with the Xcode console. - -To initialize logging to files, use the following code: - -```swift tab="Swift" -let fileLogger: AWSDDFileLogger = AWSDDFileLogger() // File Logger -fileLogger.rollingFrequency = TimeInterval(60*60*24) // 24 hours -fileLogger.logFileManager.maximumNumberOfLogFiles = 7 -AWSDDLog.add(fileLogger) -``` - -```objective-c tab="Objective-C" -AWSDDFileLogger *fileLogger = [[AWSDDFileLogger alloc] init]; // File Logger -fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling -fileLogger.logFileManager.maximumNumberOfLogFiles = 7; -[AWSDDLog addLogger:fileLogger]; -``` - -To initialize logging to your Xcode console, use the following code: - -```swift tab="Swift" -AWSDDLog.add(AWSDDTTYLogger.sharedInstance) // TTY = Xcode console -``` - -```objective-c tab="Objective-C" -[AWSDDLog addLogger:[AWSDDTTYLogger sharedInstance]]; // TTY = Xcode console -``` \ No newline at end of file diff --git a/docs/readme-images/cocoapods-setup-01.png b/docs/readme-images/cocoapods-setup-01.png deleted file mode 100644 index 3b71ba7df8058f27e7b63b5d02527ea063d19d05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47163 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 z�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 jzlZw2zt#VwFuX diff --git a/docs/readme-images/cocoapods-setup-02.png b/docs/readme-images/cocoapods-setup-02.png deleted file mode 100644 index 7a06e416741ec7cf04af8013a5cc52f2f0a76468..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41370 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 diff --git a/docs/s3/s3-downloads.md b/docs/s3/s3-downloads.md deleted file mode 100644 index 60ceae93c64..00000000000 --- a/docs/s3/s3-downloads.md +++ /dev/null @@ -1,119 +0,0 @@ -You can use `AWSS3TransferUtility` to download your data from `Amazon S3`. It internally uses `NSURLSession` to facilitate that the downloads continue even when the app goes into background. - -## Download raw data - -You can pass in a `Data` object to the `uploadUsingMultiPart` API to upload data which will be store in `S3`. - -```swift linenums="2" - - // Fetch the transfer utility client - let transferUtility = AWSS3TransferUtility.default() - - // Initialize the completion handler which will be called when the upload completes - var completionHandler: AWSS3TransferUtilityDownloadCompletionHandlerBlock? - - completionHandler = { (task, location, data, error) -> Void in - DispatchQueue.main.async(execute: { - if let error = error { - // Handle error here. Indicates download failed. - } else { - // `data` object contains the downloaded data - } - }) - } - - // The download task contains information about your download and can be used for activities like pause, resume, cancel. - let downloadTask = transferUtility.downloadData( - fromBucket: "YourBucket", - key: "YourFileName", - expression: nil, - completionHandler: completionHandler) -``` - -## Download to File - -```swift linenums="2" - - // Fetch the transfer utility client - let transferUtility = AWSS3TransferUtility.default() - - // Initialize the completion handler which will be called when the upload completes - var completionHandler: AWSS3TransferUtilityDownloadCompletionHandlerBlock? - - completionHandler = { (task, location, data, error) -> Void in - DispatchQueue.main.async(execute: { - if let error = error { - // Handle error here. Indicates download failed. - } else { - // `location` object contains the location of downloaded data - } - }) - } - - // Determine the file location where you want the download to happen - let downloadLocation: URL = URL(....) - - // The download task contains information about your download and can be used for activities like pause, resume, cancel. - let downloadTask = transferUtility.download(to: downloadLocation - fromBucket: "YourBucket", - key: "YourFileName", - expression: nil, - completionHandler: completionHandler) -``` - -## Progress Tracking - -To enable progress tracking, we will use the `expression` parameter in the `download` API call as highlighted below. We will update the example above to include progress tracking. - -```swift hl_lines="4 5 6 7 8 9 10 32" linenums="2" - - // Fetch the transfer utility client - let transferUtility = AWSS3TransferUtility.default() - - // Create progress tracking expression - let expression = AWSS3TransferUtilityDownloadExpression() - expression.progressBlock = {(task, progress) in - DispatchQueue.main.async(execute: { - // Do something e.g. Update a progress bar. - }) - } - - // Initialize the completion handler which will be called when the upload completes - var completionHandler: AWSS3TransferUtilityDownloadCompletionHandlerBlock? - - completionHandler = { (task, location, data, error) -> Void in - DispatchQueue.main.async(execute: { - if let error = error { - // Handle error here. Indicates download failed. - } else { - // `location` object contains the location of downloaded data - } - }) - } - - // Determine the file location where you want the download to happen - let downloadLocation: URL = URL(....) - - // The download task contains information about your download and can be used for activities like pause, resume, cancel. - let downloadTask = transferUtility.download(to: downloadLocation - fromBucket: "YourBucket", - key: "YourFileName", - expression: expression, - completionHandler: completionHandler) -``` - -## Pause / Resume / Cancel - -We use the `downloadTask` object which we get back from the `download` call to perform the pause, resume and cancel operations. - -### Pause - - downloadTask.suspend() - -### Resume - - downloadTask.resume() - -### Cancel - - downloadTask.cancel() \ No newline at end of file diff --git a/docs/s3/s3-setup.md b/docs/s3/s3-setup.md deleted file mode 100644 index 10415528e02..00000000000 --- a/docs/s3/s3-setup.md +++ /dev/null @@ -1,105 +0,0 @@ -`AWSS3TransferUtility` is the recommended way to make uploads and downloads from `Amazon S3` to your application. It has the following features: - -- Progress Tracking -- Multi-part Uploads -- Background Uploads/ Downloads -- Pause/ Resume/ Cancel functionality -- Retry capabilities - -## Setup Dependency - -To use `Amazon S3` APIs or `AWS S3 Transfer Utility`, you will have to include a dependency on `AWSS3` framework. To do that, update your `Podfile` to include the following dependency: - - pod 'AWSS3', '~> 2.6.22' # Dependency for AWSS3 - -Run `pod install --repo-update` to install the dependency. - - ???- question "Using Carthage or Frameworks?" - Follow the links for [Carthage Setup](../setup/setup-carthage.md) or [Frameworks Setup](../setup/setup-frameworks.md) - -## Import the library - -Add the following import to the files that use AWSS3 operations: - -```swift -import AWSS3 -``` - -## Instantiate the client - -AWS Mobile SDK for iOS provides 2 primary ways of instantiating a client. You can either fetch the `default` service client which uses the `defaultServiceConfiguration` configuration or you can register clients with a custom configuration. - -???- question "Have you followed the primary setup steps?" - Perform the steps mentioned in [Setup Dependencies](../setup/setup-dependencies.md) and [Application Setup](../setup/setup-application.md) if you haven't already. - -### Get default client - -If you have set the default service configuration or have provided a configuration in `awsconfiguration.json`, you can fetch the `default` client using: - -```swift -let transferUtility = AWSS3TransferUtility.default() -``` - -### User custom configuration - -If you want to access S3 Bucket in a different region or use different credentials to access a bucket, you will have to use the `register` and `forKey` APIs which are static APIs to create a custom configuration client. - -#### Register Client - -```swift -// setup custom credential provider -let credentialProvider = AWSCognitoCredentialsProvider( - regionType: .USEast1, - identityPoolId: "Your_Identity_PoolId") - -// setup service configuration with region of choice -let configuration = AWSServiceConfiguration( - region: .USWest2, - credentialsProvider: credentialProvider) - -// register client with configuration -AWSS3TransferUtility.register(with: configuration!, - forKey: "USWest2S3TransferUtility") -``` - -#### Fetch Client - -```swift -let S3TransferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "USWest2S3TransferUtility") -``` - -???- question "Using awsconfiguration.json?" - - Insert the following entry in your `awsconfiguration.json` - - ```json - "S3TransferUtility": { - "USWest2S3TransferUtility": { - "Bucket": "S3-BUCKET-NAME", - "Region": "us-west-2" - } - } - ``` - - Fetch the client in code using: - - ```swift - let S3TransferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "USWest2S3TransferUtility") - ``` - - If you want to use the `default()` client, add the following entry: - - ```json - "S3TransferUtility": { - "Default": { - "Bucket": "S3-BUCKET-NAME", - "Region": "us-east-1" - } - } - ``` - - and retrieve the client in code using: - - ```swift - let S3TransferUtility = AWSS3TransferUtility.default() - ``` \ No newline at end of file diff --git a/docs/s3/s3-uploads.md b/docs/s3/s3-uploads.md deleted file mode 100644 index 9f112de78da..00000000000 --- a/docs/s3/s3-uploads.md +++ /dev/null @@ -1,117 +0,0 @@ -Using `AWSS3TransferUtility` you can either upload files or raw data. `AWSS3TransferUtility` smartly determines if using multi-part upload needs to be used based on the file size. Multi-part uploads allow uploading a file in parts which facilitates easy resuming in case of errors caused due to bad network availability. - -## Upload raw Data - -You can pass in a `Data` object to the `uploadUsingMultiPart` API to upload data which will be store in `S3`. - -```swift linenums="2" - - // Fetch the transfer utility client - let transferUtility = AWSS3TransferUtility.default() - - // Get the data you want to upload. - let data: Data = "Hello World".data(using: .utf8)! // Data to be uploaded - - // Initialize the completion handler which will be called when the upload completes - var completionHandler: AWSS3TransferUtilityUploadCompletionHandlerBlock? - - completionHandler = { (task, error) -> Void in - DispatchQueue.main.async(execute: { - // Do something e.g. Alert a user for transfer completion. - // On failed uploads, `error` contains the error object. - }) - } - - // The upload task contains information about your upload and can be used for activities like pause, resume, cancel. - let uploadTask = transferUtility.uploadUsingMultiPart(data: data, - bucket: "YourBucket", - key: "YourFileName", - contentType: "text/plain", - expression: nil, - completionHandler: completionHandler) -``` - -## Upload a file - -You can pass in a `URL` object to the `uploadUsingMultiPart` API to upload data which will be store in `S3`. - -```swift linenums="2" - - // Fetch the transfer utility client - let transferUtility = AWSS3TransferUtility.default() - - // Determine the file you want to upload. - let fileURL: URL = URL(......) // URL of file to be uploaded - - // Initialize the completion handler which will be called when the upload completes - var completionHandler: AWSS3TransferUtilityUploadCompletionHandlerBlock? - - completionHandler = { (task, error) -> Void in - DispatchQueue.main.async(execute: { - // Do something e.g. Alert a user for transfer completion. - // On failed uploads, `error` contains the error object. - }) - } - - // The upload task contains information about your upload and can be used for activities like pause, resume, cancel. - let uploadTask = transferUtility.uploadUsingMultiPart(fileURL: fileURL, - bucket: "YourBucket", - key: "YourFileName", - contentType: "text/plain", - expression: nil, - completionHandler: completionHandler) -``` - -## Progress Tracking - -To enable progress tracking, we will use the `expression` parameter in the `uploadUsingMultiPart` API call as highlighted below. We will update the example above to include progress tracking. - -```swift hl_lines="7 8 9 10 11 12 13 29" linenums="2" - - // Fetch the transfer utility client - let transferUtility = AWSS3TransferUtility.default() - - // Determine the file/ data you want to upload. - let data: Data = "Hello World".data(using: .utf8)! // Data to be uploaded - - // Create progress tracking expression - let expression = AWSS3TransferUtilityUploadExpression() - expression.progressBlock = {(task, progress) in - DispatchQueue.main.async(execute: { - // Do something e.g. Update a progress bar. - }) - } - - var completionHandler: AWSS3TransferUtilityUploadCompletionHandlerBlock? - completionHandler = { (task, error) -> Void in - DispatchQueue.main.async(execute: { - // Do something e.g. Alert a user for transfer completion. - // On failed uploads, `error` contains the error object. - }) - } - - let transferUtility = AWSS3TransferUtility.default() - - let uploadTask = transferUtility.uploadUsingMultiPart(data: data, - bucket: "YourBucket", - key: "YourFileName", - contentType: "text/plain", - expression: expression, - completionHandler: completionHandler) -``` - -## Pause / Resume / Cancel - -We use the `uploadTask` object which we get back from the `uploadUsingMultiPart` call to perform the pause, resume and cancel operations. - -### Pause - - uploadTask.suspend() - -### Resume - - uploadTask.resume() - -### Cancel - - uploadTask.cancel() diff --git a/docs/samples-info.md b/docs/samples-info.md deleted file mode 100644 index a7c0bc065a8..00000000000 --- a/docs/samples-info.md +++ /dev/null @@ -1,55 +0,0 @@ -The AWS SDK for iOS includes sample apps that demonstrate common use cases. - -## Cognito Your User Pools Sample - -([Swift](https://github.com/awslabs/aws-sdk-ios-samples/tree/master/CognitoYourUserPools-Sample/Swift/), [Objective-C](https://github.com/awslabs/aws-sdk-ios-samples/tree/master/CognitoYourUserPools-Sample/Objective-C/)) - -This sample demonstrates how sign up and sign in a user to display an authenticated portion of your app. - -???+ note "AWS Services Demonstrated" - - * [Amazon Cognito User Pools](http://aws.amazon.com/cognito/) - -## DynamoDB Object Mapper Sample - -([Swift](https://github.com/awslabs/aws-sdk-ios-samples/tree/master/DynamoDBObjectMapper-Sample/Swift/), [Objective-C](https://github.com/awslabs/aws-sdk-ios-samples/tree/master/DynamoDBObjectMapper-Sample/Objective-C/)) - -This sample demonstrates how to insert / update / delete / query items using DynamoDB Object Mapper. - -???- note "AWS Services Demonstrated" - - * [Amazon DynamoDB](http://aws.amazon.com/dynamodb/) - * [Amazon Cognito Identity](http://aws.amazon.com/cognito/) - -## S3 Transfer Utility Sample - -([Swift](https://github.com/awslabs/aws-sdk-ios-samples/tree/master/S3TransferUtility-Sample/Swift/), [Objective-C](https://github.com/awslabs/aws-sdk-ios-samples/tree/master/S3TransferUtility-Sample/Objective-C/)) - -This sample demonstrates how to use the Amazon S3 PreSigned URL Builder to download / upload files in background. - -???- note "AWS Services Demonstrated" - - * [Amazon S3](http://aws.amazon.com/s3/) - * [Amazon Cognito Identity](http://aws.amazon.com/cognito/) - -## IoT Sample - -([Swift](https://github.com/awslabs/aws-sdk-ios-samples/tree/master/IoT-Sample/Swift/)) - -This sample demonstrates how to publish and subscribe to data using AWS IoT. - -???- note "AWS Services Demonstrated" - - * [Amazon AWS IoT](http://aws.amazon.com/iot/) - * [Amazon Cognito Identity](http://aws.amazon.com/cognito/) - -## IoT Temperature Control Sample - -([Swift](https://github.com/awslabs/aws-sdk-ios-samples/tree/master/IoTTemperatureControl-Sample/Swift/)) - -This sample demonstrates accessing device shadows using Cognito authentication; it works in conjunction with the Temperature Control Example Program in the [AWS IoT JavaScript SDK for Embedded Devices](https://github.com/aws/aws-iot-device-sdk-js). - -???- note "AWS Services Demonstrated" - - * [Amazon AWS IoT](http://aws.amazon.com/iot/) - * [Amazon Cognito Identity](http://aws.amazon.com/cognito/) \ No newline at end of file diff --git a/docs/service-clients.md b/docs/service-clients.md deleted file mode 100644 index f30190a0e2c..00000000000 --- a/docs/service-clients.md +++ /dev/null @@ -1,89 +0,0 @@ -All the service clients, e.g. AWSS3, AWSDynamoDB, AWSTranslate, AWSTranscribe, etc. follow a common usage pattern. All of them have a naming convention of `AWS` and have methods that correspond directly to the service APIs. To use these service clients follow the below steps: - -???- question "Have you followed the primary setup steps?" - Perform the steps mentioned in [Setup Dependencies](setup/setup-dependencies.md) and [Application Setup](setup/setup-application.md) if you haven't already. - -We will use `AWSDynamoDB` client and `listTables` API as an example. - -## Setup Dependency - -To use any service client APIs, you will have to include a dependency on `AWS` framework. To do that, update your `Podfile` to include the following dependency: - - pod 'AWSDynamoDB', '~> 2.6.22' # Dependency for AWSDynamoDB - - -Run `pod install --repo-update` to install the dependency. - -???- question "Using Carthage or Frameworks?" - Follow the links for [Carthage Setup](setup/setup-dependencies.md) or [Frameworks Setup](setup/setup-dependencies.md) - -???- info "If using awsconfiguration.json." - If you are using `awsconfiguration.json` you need add the following entry in it: - - ```json - "AWSDynamoDB": { - "Default": { - "Region": "us-west-2" - } - } - ``` - - `awsconfiguration.json` supports adding entries in the form of `AWS` as the key with `Default` as the key so that it can be retrieved using `AWS.default()` - -## Invoke Service API - - -1. In the Swift file you want to use the SDK, import the appropriate headers for the services you are using. The framework import convention is `import AWS`, as in the following examples: - - ```swift - import AWSDynamoDB - ``` - -2. Make a call to the AWS services. - - ```swift - // Fetch the default service client - let dynamoDB = AWSDynamoDB.default() - - // Initialize the request input object - let listTableInput = AWSDynamoDBListTablesInput() - listTableInput?.limit = 10 - - // Invoke the service API - dynamoDB.listTables(listTablesInput!) { (output, error) in - guard error == nil else { - print("error occurred") - return - } - // output has the server response - } - ``` - - -???+ note "Note" - - For Swift, most of the service client classes have a singleton method to get a default client. The naming convention is to call `default()` statically (e.g. `AWSDynamoDB.default()` in the above code snippet). This singleton method creates a service client with `defaultServiceConfiguration`, which you set up in `Setup Application` phase, and maintains a strong reference to the client. - -## List of all Service Clients - -The AWS Mobile SDK for iOS has following `Service Clients` which follow the pattern specified above. - -* AWSAutoScaling -* AWSCloudWatch -* AWSComprehend -* AWSDynanmoDB -* AWSEC2 -* AWSElasticLoadBalancing -* AWSIoT -* AWSKMS -* AWSKinesis -* AWSLambda -* AWSMachineLearning -* AWSPolly -* AWSRekognition -* AWSS3 -* AWSSNS -* AWSSQS -* AWSSimpleDB -* AWSTranscribe -* AWSTranslate \ No newline at end of file diff --git a/docs/setup/setup-application.md b/docs/setup/setup-application.md deleted file mode 100644 index 6cadd2c6e59..00000000000 --- a/docs/setup/setup-application.md +++ /dev/null @@ -1,43 +0,0 @@ -Once you have setup the dependencies, the next step is to setup the client. - -AWS Mobile SDK for iOS provides 2 primary ways of setting up and instantiating the client. You can either initialize your configuration in your app's source code or use a `awsconfiguration.json` file which contains your backend configuration. - -???- question "Not setup your backend yet?" - If you have not setup your backend yet, consider using [AWS Mobile Hub](https://aws.amazon.com/mobile/) or [Getting Started](https://docs.aws.amazon.com/aws-mobile/latest/developerguide/getting-started.html) documentation. - -???+ example "Initialize configuration through code" - - 1. Import the AWSCore framework in the application delegate. - - ```swift - import AWSCore - ``` - - 2. Create a default service configuration by adding the following code snippet in the `application:didFinishLaunchingWithOptions:` application delegate method. - - ```swift - let credentialsProvider = AWSCognitoCredentialsProvider( - regionType: .USEast1, // update region to your region - identityPoolId: "YOUR_POOL_ID_HERE") - - let configuration = AWSServiceConfiguration( - region: .USEast1, // your region here - credentialsProvider: credentialsProvider) - - AWSServiceManager.default().defaultServiceConfiguration = configuration - ``` - - That's it! You can now move to a integrating a service of your choice from the left side of the page. - - -???+ example "Initialize configuration through awsconfiguration.json" - - If you configured your backed using `AWS Mobile Hub` or have a `awsconfiguration.json` file which you want to integrate into your app, follow the below steps: - - 1. Place the `awsconfiguration.json` into the folder containing your info.plist file in your Xcode project. Select **Copy items if needed** and **Create groups** in the options dialog. Choose Next. - - 2. Import the AWSCore framework in the application delegate or the file where you want to use the AWS SDKs. - - ```swift - import AWSCore - ``` \ No newline at end of file diff --git a/docs/setup/setup-dependencies.md b/docs/setup/setup-dependencies.md deleted file mode 100644 index 2aa645b8ea3..00000000000 --- a/docs/setup/setup-dependencies.md +++ /dev/null @@ -1,122 +0,0 @@ -There are three ways to import the AWS Mobile SDK for iOS into your project: - -* Cocoapods - - Add ==`pod 'AWSCore', '~> 2.6.22'`== to your `Podfile`. - -* Carthage - - Add ==`github "aws/aws-sdk-ios" ~> 2.6.22`== to your `Cartfile`. - -* Dynamic Frameworks - - Add the ==`AWSCore.framework`== file into your Embedded Binaries section and make sure it is also included in linked frameworks section. - -???+warning - You should use only one of these three ways to import the AWS Mobile SDK. Importing the SDK in multiple ways loads duplicate copies of the SDK into the project and causes compiler errors. - -If you are using any of these methods for the first time, please use one of the sections below: - -???- question "First time Cocoapods user?" - - 1. The AWS Mobile SDK for iOS is available through [CocoaPods](http://cocoapods.org). If you have not installed CocoaPods, install CocoaPods by running the command: - - $ gem install cocoapods - $ pod setup - - Depending on your system settings, you may have to use `sudo` for installing `cocoapods` as follows: - - $ sudo gem install cocoapods - $ pod setup - - 2. In your project directory (the directory where your `*.xcodeproj` file is), create a plain text file named `Podfile` (without any file extension) and add the lines below. Replace `YOUR_TARGET_NAME_HERE` with your actual target name. - - ```ruby - source 'https://github.com/CocoaPods/Specs.git' - - platform :ios, '9.0' - use_frameworks! - - target :'YOUR_TARGET_NAME_HERE' do - pod 'AWSCore' - end - ``` - - 3. Then run the following command: - - $ pod install --repo-update - - 4. Open up `*.xcworkspace` with Xcode and start using the SDK. - - ???+Warning - Do **NOT** use `*.xcodeproj`. If you open up a project file instead of a workspace, you receive an error: - - ld: library not found for -lPods-AWSCore - clang: error: linker command failed with exit code 1 (use -v to see invocation) - - **Updating the SDK** - - When we release a new version of the SDK, you can pick up the changes using the `pod update` command from your terminal. - - ???+Note "Next Steps" - Go the [Application Setup](setup-application.md) after opening the `.xcworkspace` file. - -???- question "First time Carthage user?" - - 1. Install the latest version of [Carthage](https://github.com/Carthage/Carthage#installing-carthage). - - 2. Add the following to your `Cartfile`: - - github "aws/aws-sdk-ios" - - 3. Then run the following command: - - $ carthage update - - 4. With your project open in Xcode, select your **Target**. Under **General** tab, find **Embedded Binaries** and then click the **+** button. - - 5. Click the **Add Other...** button, navigate to the `AWS<#ServiceName#>.framework` files under `Carthage` > `Build` > `iOS` and select them. Do not check the **Destination: Copy items if needed** checkbox when prompted. - - 6. Under the **Build Phases** tab in your **Target**, click the **+** button on the top left and then select **New Run Script Phase**. Then setup the build phase as follows. Make sure this phase is below the `Embed Frameworks` phase. - - Shell /bin/sh - - bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/AWSCore.framework/strip-frameworks.sh" - - Show environment variables in build log: Checked - Run script only when installing: Not checked - - Input Files: Empty - Output Files: Empty - - **Updating the SDK** - - When we release a new version of the SDK, you can pick up the changes by running `carthage update` from your terminal. - -???- question "First time Dynamic Frameworks user?" - - 1. Download the SDK from our [AWS Mobile SDK](http://aws.amazon.com/mobile/sdk) page. The SDK is stored in a compressed file archive named `aws-ios-sdk-#.#.#` (where `#.#.#` represents the version number, so for version 2.6.22, the filename is `aws-ios-sdk-2.6.22`). - - 2. With your project open in Xcode, select your **Target**. Under **General** tab, find **Embedded Binaries** and then click the **+** button. - - 3. Click the **Add Other...** button, navigate to the `AWS<#ServiceName#>.framework` files and select them. Check the **Destination: Copy items if needed** checkbox when prompted. - - 4. Under the **Buid Phases** tab in your **Target**, click the **+** button on the top left and then select **New Run Script Phase**. Then setup the build phase as follows. Make sure this phase is below the `Embed Frameworks` phase. - - Shell /bin/sh - - bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/AWSCore.framework/strip-frameworks.sh" - - Show environment variables in build log: Checked - Run script only when installing: Not checked - - Input Files: Empty - Output Files: Empty - - **Updating the SDK** - - When we release a new version of the SDK, you can pick up the changes as described below. - - 1. In Xcode select the frameworks with name `AWS*****.framework` in **Project Navigator** and hit **delete** on your keyboard. Then select **Move to Trash**: - - 2. Follow the installation process mentioned above to include the new version of the SDK. \ No newline at end of file diff --git a/docs/setup/setup-sdk-update.md b/docs/setup/setup-sdk-update.md deleted file mode 100644 index b8ea08a1d76..00000000000 --- a/docs/setup/setup-sdk-update.md +++ /dev/null @@ -1,25 +0,0 @@ -## Update the SDK to a Newer Version - -When we release a new version of the SDK, you can pick up the changes as described below. - -### CocoaPods - -1. Run the following command in your project directory. CocoaPods automatically picks up the new changes. - - $ pod update - - **Note**: If your pod is having an issue, you can delete `Podfile.lock` and `Pods/` then run `pod install` to cleanly install the SDK. - - ![image](readme-images/cocoapods-setup-03.png?raw=true) - -### Carthage - -1. Run the following command in your project directory. Carthage automatically picks up the new changes. - - $ carthage update - -### Frameworks - -1. In Xcode select the frameworks with name `AWS*****.framework` in **Project Navigator** and hit **delete** on your keyboard. Then select **Move to Trash**: - -2. Follow the installation process mentioned in the Frameworks setup section to include the new version of the SDK. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index 138e06f9b26..00000000000 --- a/mkdocs.yml +++ /dev/null @@ -1,34 +0,0 @@ -site_name: AWS Mobile SDK for iOS -pages: -- Home: 'index.md' -- Setup: - - Dependencies: 'setup/setup-dependencies.md' - - Application: 'setup/setup-application.md' -- Working with Service Clients: 'service-clients.md' -- S3 Transfer Utility: - - TransferUtility Setup: 's3/s3-setup.md' - - Upload: 's3/s3-uploads.md' - - Download: 's3/s3-downloads.md' -- Sample Apps: 'samples-info.md' -- Logging: 'logging.md' -- AWSTask: 'awstask.md' -theme: - name: 'material' -extra: - social: - - type: 'github' - link: 'https://github.com/aws' - - type: 'twitter' - link: 'https://twitter.com/awsformobile' -markdown_extensions: - - markdown.extensions.def_list - - markdown.extensions.footnotes - - markdown.extensions.meta - - toc: - permalink: true - - pymdownx.superfences - - pymdownx.details - - pymdownx.mark - - pymdownx.magiclink - - pymdownx.betterem: - smart_enable: all \ No newline at end of file