diff --git a/packages/amplify_core/lib/src/types/storage/download_file_options.dart b/packages/amplify_core/lib/src/types/storage/download_file_options.dart index 8681667131..23d745f5e6 100644 --- a/packages/amplify_core/lib/src/types/storage/download_file_options.dart +++ b/packages/amplify_core/lib/src/types/storage/download_file_options.dart @@ -1,7 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import 'package:aws_common/aws_common.dart'; +import 'package:amplify_core/amplify_core.dart'; /// {@template amplify_core.storage.download_file_options} /// Configurable options for `Amplify.Storage.downloadFile`. @@ -14,13 +14,17 @@ class StorageDownloadFileOptions /// {@macro amplify_core.storage.download_file_options} const StorageDownloadFileOptions({ this.pluginOptions, + this.bucket, }); /// {@macro amplify_core.storage.download_file_plugin_options} final StorageDownloadFilePluginOptions? pluginOptions; + /// Optionally specify which bucket to target + final StorageBucket? bucket; + @override - List get props => [pluginOptions]; + List get props => [pluginOptions, bucket]; @override String get runtimeTypeName => 'StorageDownloadFileOptions'; @@ -28,6 +32,7 @@ class StorageDownloadFileOptions @override Map toJson() => { 'pluginOptions': pluginOptions?.toJson(), + 'bucket': bucket, }; } diff --git a/packages/storage/amplify_storage_s3/example/integration_test/download_file_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/download_file_test.dart index fb3dd7e554..937f80a6cb 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/download_file_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/download_file_test.dart @@ -296,5 +296,219 @@ void main() { }, ); }); + group('multibucket config', () { + final secondaryBucket = + StorageBucket.fromOutputs('Storage Integ Test secondary bucket'); + setUpAll(() async { + await configure(amplifyEnvironments['main']!); + await Amplify.Storage.uploadData( + data: StorageDataPayload.bytes(data), + path: StoragePath.fromString(publicPath), + options: StorageUploadDataOptions(bucket: secondaryBucket), + ).result; + + userIdentityId = await signInNewUser(); + identityPath = 'private/$userIdentityId/$name'; + + await Amplify.Storage.uploadData( + data: StorageDataPayload.bytes(data), + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$name', + ), + options: StorageUploadDataOptions(bucket: secondaryBucket), + ).result; + + await Amplify.Storage.uploadData( + data: StorageDataPayload.bytes(data), + path: StoragePath.fromString(metadataFilePath), + options: StorageUploadDataOptions( + metadata: metadata, + bucket: secondaryBucket, + ), + ).result; + + addTearDownPaths( + [ + StoragePath.fromString(publicPath), + StoragePath.fromString(metadataFilePath), + StoragePath.fromString(identityPath), + ], + ); + }); + + group('for file type', () { + testWidgets('to file', (_) async { + final downloadFilePath = '$directory/downloaded-file.txt'; + + final result = await Amplify.Storage.downloadFile( + path: StoragePath.fromString(publicPath), + localFile: AWSFile.fromPath(downloadFilePath), + options: StorageDownloadFileOptions(bucket: secondaryBucket), + ).result; + + // Web browsers do not grant access to read arbitrary files + if (!kIsWeb) { + final downloadedFile = await readFile(path: downloadFilePath); + expect(downloadedFile, data); + } + + expect(result.localFile.path, downloadFilePath); + expect(result.downloadedItem.path, publicPath); + }); + }); + + testWidgets('from identity ID', (_) async { + final downloadFilePath = '$directory/downloaded-file.txt'; + final result = await Amplify.Storage.downloadFile( + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$name', + ), + localFile: AWSFile.fromPath(downloadFilePath), + options: StorageDownloadFileOptions(bucket: secondaryBucket), + ).result; + + if (!kIsWeb) { + final downloadedFile = await readFile(path: downloadFilePath); + expect(downloadedFile, data); + } + expect(result.localFile.path, downloadFilePath); + expect(result.downloadedItem.path, identityPath); + }); + + group('with options', () { + testWidgets('useAccelerateEndpoint', (_) async { + final downloadFilePath = '$directory/downloaded-file.txt'; + + final result = await Amplify.Storage.downloadFile( + path: StoragePath.fromString(publicPath), + options: StorageDownloadFileOptions( + pluginOptions: const S3DownloadFilePluginOptions( + useAccelerateEndpoint: true, + ), + bucket: secondaryBucket, + ), + localFile: AWSFile.fromPath(downloadFilePath), + ).result; + + if (!kIsWeb) { + final downloadedFile = await readFile(path: downloadFilePath); + expect(downloadedFile, data); + } + expect(result.localFile.path, downloadFilePath); + expect(result.downloadedItem.path, publicPath); + }); + + testWidgets('getProperties', (_) async { + final downloadFilePath = '$directory/downloaded-file.txt'; + final downloadResult = await Amplify.Storage.downloadFile( + path: StoragePath.fromString(metadataFilePath), + options: StorageDownloadFileOptions( + pluginOptions: const S3DownloadFilePluginOptions( + getProperties: true, + ), + bucket: secondaryBucket, + ), + localFile: AWSFile.fromPath(downloadFilePath), + ).result; + + if (!kIsWeb) { + final downloadedFile = await readFile(path: downloadFilePath); + expect(downloadedFile, data); + } + expect(downloadResult.localFile.path, downloadFilePath); + expect(downloadResult.downloadedItem.path, metadataFilePath); + }); + + testWidgets('unauthorized path', (_) async { + final downloadFilePath = '$directory/downloaded-file.txt'; + + await expectLater( + () => Amplify.Storage.downloadFile( + path: const StoragePath.fromString('unauthorized/path'), + localFile: AWSFile.fromPath(downloadFilePath), + options: StorageDownloadFileOptions(bucket: secondaryBucket), + ).result, + throwsA(isA()), + ); + }); + }); + + group( + 'download progress', + () { + testWidgets('reports progress', skip: kIsWeb, (_) async { + final downloadFilePath = '$directory/downloaded-file.txt'; + var fractionCompleted = 0.0; + var totalBytes = 0; + var transferredBytes = 0; + + await Amplify.Storage.downloadFile( + path: StoragePath.fromString(publicPath), + localFile: AWSFile.fromPath(downloadFilePath), + onProgress: (StorageTransferProgress progress) { + fractionCompleted = progress.fractionCompleted; + totalBytes = progress.totalBytes; + transferredBytes = progress.transferredBytes; + }, + options: StorageDownloadFileOptions(bucket: secondaryBucket), + ).result; + expect(fractionCompleted, 1.0); + expect(totalBytes, data.length); + expect(transferredBytes, data.length); + }); + }, + // TODO(Jordan-Nelson): Determine why these are failing on web + skip: kIsWeb, + ); + + group( + 'pause, resume, cancel', + () { + const size = 1024 * 1024 * 6; + const chars = 'qwertyuiopasdfghjklzxcvbnm'; + final content = List.generate(size, (i) => chars[i % 25]).join(); + final fileId = uuid(); + final path = 'public/download-file-pause-$fileId'; + setUpAll(() async { + addTearDownPath(StoragePath.fromString(path)); + await Amplify.Storage.uploadData( + data: StorageDataPayload.string(content), + path: StoragePath.fromString(path), + options: StorageUploadDataOptions(bucket: secondaryBucket), + ).result; + }); + testWidgets('can pause', (_) async { + final filePath = '$directory/downloaded-file.txt'; + final operation = Amplify.Storage.downloadFile( + localFile: AWSFile.fromPath(filePath), + path: StoragePath.fromString(path), + options: StorageDownloadFileOptions(bucket: secondaryBucket), + ); + await operation.pause(); + unawaited( + operation.result.then( + (value) => fail('should not complete after pause'), + ), + ); + await Future.delayed(const Duration(seconds: 15)); + }); + + testWidgets('can resume', (_) async { + final filePath = '$directory/downloaded-file.txt'; + final operation = Amplify.Storage.downloadFile( + localFile: AWSFile.fromPath(filePath), + path: StoragePath.fromString(path), + options: StorageDownloadFileOptions(bucket: secondaryBucket), + ); + await operation.pause(); + await operation.resume(); + final result = await operation.result; + expect(result.downloadedItem.path, path); + }); + }, + // TODO(Jordan-Nelson): Determine why these are failing on web + skip: kIsWeb, + ); + }); }); } diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/amplify_storage_s3_dart_impl.dart b/packages/storage/amplify_storage_s3_dart/lib/src/amplify_storage_s3_dart_impl.dart index 41cbcc5df8..4394cb0d14 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/amplify_storage_s3_dart_impl.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/amplify_storage_s3_dart_impl.dart @@ -257,13 +257,14 @@ class AmplifyStorageS3Dart extends StoragePluginInterface pluginOptions: options?.pluginOptions, defaultPluginOptions: const S3DownloadFilePluginOptions(), ); - options = StorageDownloadFileOptions( + final s3options = StorageDownloadFileOptions( pluginOptions: s3PluginOptions, + bucket: options?.bucket, ); return download_file_impl.downloadFile( path: path, localFile: localFile, - options: options, + options: s3options, storageOutputs: storageOutputs, storageS3Service: storageS3Service, appPathProvider: _appPathProvider, diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/platform_impl/download_file/download_file_html.dart b/packages/storage/amplify_storage_s3_dart/lib/src/platform_impl/download_file/download_file_html.dart index 32f79a9d38..aa1d2e9c4b 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/platform_impl/download_file/download_file_html.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/platform_impl/download_file/download_file_html.dart @@ -40,7 +40,7 @@ S3DownloadFileOperation downloadFile({ cancel: noOp, ); } - +// TODO: add bucket option to function once getURL update has been implemented Future _downloadFromUrl({ required StoragePath path, required AWSFile localFile, @@ -58,7 +58,7 @@ Future _downloadFromUrl({ // operation. final downloadedItem = (await storageS3Service.getProperties( path: path, - options: const StorageGetPropertiesOptions(), + options: StorageGetPropertiesOptions(bucket: options.bucket), )) .storageItem; diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/platform_impl/download_file/download_file_io.dart b/packages/storage/amplify_storage_s3_dart/lib/src/platform_impl/download_file/download_file_io.dart index 5651870acd..c17e19b1db 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/platform_impl/download_file/download_file_io.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/platform_impl/download_file/download_file_io.dart @@ -32,6 +32,7 @@ S3DownloadFileOperation downloadFile({ getProperties: s3PluginOptions.getProperties, useAccelerateEndpoint: s3PluginOptions.useAccelerateEndpoint, ), + bucket: options.bucket, ); final downloadDataTask = storageS3Service.downloadData(