Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(storage): copy and move APIs #4569

Merged
merged 4 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,8 @@ class StorageCategory extends AmplifyCategory<StoragePluginInterface> {
/// corresponding [StorageAccessLevel].
/// {@endtemplate}
StorageCopyOperation copy({
required StorageItemWithAccessLevel<StorageItem> source,
required StorageItemWithAccessLevel<StorageItem> destination,
required StoragePath source,
required StoragePath destination,
StorageCopyOptions? options,
}) {
return identifyCall(
Expand All @@ -208,31 +208,6 @@ class StorageCategory extends AmplifyCategory<StoragePluginInterface> {
);
}

/// {@template amplify_core.amplify_storage_category.move}
/// Moves [source] to [destination] with optional [StorageMoveOptions],
/// and returns a [StorageMoveOperation].
///
/// This API performs two consecutive S3 service calls:
/// 1. copy the source object to destination objection
/// 2. delete the source object
///
/// {@macro amplify_core.amplify_storage_category.copy_source}
/// {@endtemplate}
StorageMoveOperation move({
required StorageItemWithAccessLevel<StorageItem> source,
required StorageItemWithAccessLevel<StorageItem> destination,
StorageMoveOptions? options,
}) {
return identifyCall(
StorageCategoryMethod.move,
() => defaultPlugin.move(
source: source,
destination: destination,
options: options,
),
);
}

/// {@template amplify_core.amplify_storage_category.remove}
/// Removes an object specified by [key] with optional [StorageRemoveOptions],
/// and returns a [StorageRemoveOperation].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,22 +79,13 @@ abstract class StoragePluginInterface extends AmplifyPluginInterface {

/// {@macro amplify_core.amplify_storage_category.copy}
StorageCopyOperation copy({
required StorageItemWithAccessLevel<StorageItem> source,
required StorageItemWithAccessLevel<StorageItem> destination,
required StoragePath source,
required StoragePath destination,
StorageCopyOptions? options,
}) {
throw UnimplementedError('copy() has not been implemented.');
}

/// {@macro amplify_core.amplify_storage_category.move}
StorageMoveOperation move({
required StorageItemWithAccessLevel<StorageItem> source,
required StorageItemWithAccessLevel<StorageItem> destination,
StorageMoveOptions? options,
}) {
throw UnimplementedError('move() has not been implemented.');
}

/// {@macro amplify_core.amplify_storage_category.remove}
StorageRemoveOperation remove({
required String key,
Expand Down
4 changes: 2 additions & 2 deletions packages/amplify_core/lib/src/types/storage/copy_request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ class StorageCopyRequest<Item extends StorageItem> {
});

/// Copy source.
final StorageItemWithAccessLevel<Item> source;
final StoragePath source;

/// Copy destination.
final StorageItemWithAccessLevel<Item> destination;
final StoragePath destination;

/// Configurable options of the [StorageCopyRequest].
final StorageCopyOptions? options;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,7 @@ void main() {
testLargeFileBytes[0] = 101;
testLargeFileBytes[5 * 1024] = 102;
final testObjectKey1 = 'user1-guest-object-${uuid()}';
final testObject1MoveKey = 'user1-guest-object-move-${uuid()}';
final testObjectKey2 = 'user1-protected-object-${uuid()}';
final testObject2MoveKey = 'user1-protected-object-move-${uuid()}';
final testObjectKey3 = 'user1-private-object-${uuid()}';
final testObject3CopyKey = 'user1-private-object-copy-${uuid()}';
final testObject3CopyMoveKey = 'user1-private-object-copy-move-${uuid()}';
Expand Down Expand Up @@ -407,13 +405,11 @@ void main() {
'copy object with access level private for the currently signed in user',
(WidgetTester tester) async {
final result = await Amplify.Storage.copy(
source: S3ItemWithAccessLevel(
storageItem: S3Item(key: testObjectKey3),
accessLevel: StorageAccessLevel.private,
source: StoragePath.withIdentityId(
(identityId) => '/private/$identityId/$testObjectKey3',
),
destination: S3ItemWithAccessLevel(
storageItem: S3Item(key: testObject3CopyKey),
accessLevel: StorageAccessLevel.private,
destination: StoragePath.withIdentityId(
(identityId) => '/private/$identityId/$testObject3CopyKey',
),
options: const StorageCopyOptions(
pluginOptions: S3CopyPluginOptions(
Expand All @@ -426,41 +422,6 @@ void main() {
expect(result.copiedItem.eTag, isNotEmpty);
});

testWidgets(
skip: true,
'move object with access level private for the currently signed in user',
(WidgetTester tester) async {
final result = await Amplify.Storage.move(
source: S3ItemWithAccessLevel(
storageItem: S3Item(key: testObject3CopyKey),
accessLevel: StorageAccessLevel.private,
),
destination: S3ItemWithAccessLevel(
storageItem: S3Item(key: testObject3CopyMoveKey),
accessLevel: StorageAccessLevel.private,
),
options: const StorageMoveOptions(
pluginOptions: S3MovePluginOptions(getProperties: true),
),
).result;

expect(result.movedItem.key, testObject3CopyMoveKey);
expect(result.movedItem.eTag, isNotEmpty);

final listedObjects = await Amplify.Storage.list(
options: const StorageListOptions(
accessLevel: StorageAccessLevel.private,
),
).result;

expect(
listedObjects.items.map((item) => item.key),
isNot(
contains(testObject3CopyKey),
),
);
});

testWidgets(
'delete object with access level private for the currently signed in user',
(WidgetTester tester) async {
Expand Down Expand Up @@ -696,52 +657,6 @@ void main() {
);
});

testWidgets(
skip: true,
'move object with access level guest for the currently signed in user',
(WidgetTester tester) async {
final result = await Amplify.Storage.move(
source: S3ItemWithAccessLevel(
storageItem: S3Item(key: testObjectKey1),
accessLevel: StorageAccessLevel.guest,
),
destination: S3ItemWithAccessLevel(
storageItem: S3Item(key: testObject1MoveKey),
accessLevel: StorageAccessLevel.private,
),
options: const StorageMoveOptions(
pluginOptions: S3MovePluginOptions(
getProperties: true,
),
),
).result;

expect(result.movedItem.eTag, isNotEmpty);
});

testWidgets(
skip: true,
'copy object (belongs to other user) with access level protected'
' for the currently signed in user', (WidgetTester tester) async {
final result = await Amplify.Storage.copy(
source: S3ItemWithAccessLevel.forIdentity(
user1IdentityId,
storageItem: S3Item(key: testObjectKey2),
),
destination: S3ItemWithAccessLevel(
storageItem: S3Item(key: testObject2MoveKey),
accessLevel: StorageAccessLevel.private,
),
options: const StorageCopyOptions(
pluginOptions: S3CopyPluginOptions(
getProperties: true,
),
),
).result;

expect(result.copiedItem.eTag, isNotEmpty);
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this test being removed?

Copy link
Member Author

@Jordan-Nelson Jordan-Nelson Mar 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I removed this because it depended on a test I was removing and it seemed redundant to other e2e tests we have. I added it back though and updated it to no longer be dependent on the other test. We can decide if it is indeed redundant when we refactor the full test suite.


testWidgets(skip: true, 'list respects pageSize',
(WidgetTester tester) async {
const filesToUpload = 2;
Expand Down Expand Up @@ -876,27 +791,6 @@ void main() {
await Amplify.Auth.signOut();
});

testWidgets(
skip: true,
'move object with access level protected as object owner',
(WidgetTester tester) async {
final result = await Amplify.Storage.move(
source: S3ItemWithAccessLevel.forIdentity(
user1IdentityId,
storageItem: S3Item(key: testObjectKey2),
),
destination: S3ItemWithAccessLevel(
storageItem: S3Item(key: testObject2MoveKey),
accessLevel: StorageAccessLevel.private,
),
options: const StorageMoveOptions(
pluginOptions: S3MovePluginOptions(getProperties: true),
),
).result;

expect(result.movedItem.eTag, isNotEmpty);
});

testWidgets(
skip: true,
'remove many objects belongs to the currently signed user',
Expand Down
62 changes: 3 additions & 59 deletions packages/storage/amplify_storage_s3_dart/example/bin/example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ Future<void> main() async {
3. getUrl 4. download data
5. download file 6. upload data url
7. upload file 8. copy
9. move 10. remove
0. exit
9. remove 0. exit
''');
final operationNum = int.tryParse(operation);

Expand All @@ -64,8 +63,6 @@ Future<void> main() async {
case 8:
await copyOperation();
case 9:
await moveOperation();
case 10:
await removeOperation();
case null:
break;
Expand Down Expand Up @@ -369,24 +366,12 @@ Future<void> uploadFileOperation() async {

Future<void> copyOperation() async {
final sourceKey = prompt('Enter the key of the source object: ');
final sourceStorageAccessLevel = promptStorageAccessLevel(
'Choose the storage access level associated with the source object: ',
);
final destinationKey = prompt('Enter the key of the destination object: ');
final destinationStorageAccessLevel = promptStorageAccessLevel(
'Choose the storage access level associated with the destination object: ',
);

final s3Plugin = Amplify.Storage.getPlugin(AmplifyStorageS3Dart.pluginKey);
final copyOperation = s3Plugin.copy(
source: S3ItemWithAccessLevel(
storageItem: S3Item(key: sourceKey),
accessLevel: sourceStorageAccessLevel,
),
destination: S3ItemWithAccessLevel(
storageItem: S3Item(key: destinationKey),
accessLevel: destinationStorageAccessLevel,
),
source: StoragePath.fromString(sourceKey),
destination: StoragePath.fromString(destinationKey),
options: const StorageCopyOptions(
pluginOptions: S3CopyPluginOptions(getProperties: true),
),
Expand All @@ -408,47 +393,6 @@ Future<void> copyOperation() async {
}
}

Future<void> moveOperation() async {
final sourceKey = prompt('Enter the key of the source object: ');
final sourceStorageAccessLevel = promptStorageAccessLevel(
'Choose the storage access level associated with the source object: ',
);
final destinationKey = prompt('Enter the key of the destination object: ');
final destinationStorageAccessLevel = promptStorageAccessLevel(
'Choose the storage access level associated with the destination object: ',
);

final s3Plugin = Amplify.Storage.getPlugin(AmplifyStorageS3Dart.pluginKey);
final moveOperation = s3Plugin.move(
source: S3ItemWithAccessLevel(
storageItem: S3Item(key: sourceKey),
accessLevel: sourceStorageAccessLevel,
),
destination: S3ItemWithAccessLevel(
storageItem: S3Item(key: destinationKey),
accessLevel: destinationStorageAccessLevel,
),
options: const StorageMoveOptions(
pluginOptions: S3MovePluginOptions(getProperties: true),
),
);

try {
final result = await moveOperation.result;
stdout
..writeln('Copied object: ')
..writeln('key: ${result.movedItem.key}')
..writeln('size: ${result.movedItem.size}')
..writeln('lastModified: ${result.movedItem.lastModified}')
..writeln('eTag: ${result.movedItem.eTag}')
..writeln('metadata: ${result.movedItem.metadata}');
} on Exception catch (error) {
stderr
..writeln('Something went wrong...')
..writeln(error);
}
}

Future<void> removeOperation() async {
final key = prompt('Enter the object key to remove: ');
final accessLevel = promptStorageAccessLevel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,12 +363,12 @@ class AmplifyStorageS3Dart extends StoragePluginInterface

@override
S3CopyOperation copy({
required StorageItemWithAccessLevel<StorageItem> source,
required StorageItemWithAccessLevel<StorageItem> destination,
required StoragePath source,
required StoragePath destination,
StorageCopyOptions? options,
}) {
final s3Source = S3ItemWithAccessLevel.from(source);
final s3Destination = S3ItemWithAccessLevel.from(destination);
// final s3Source = S3ItemWithAccessLevel.from(source);
// final s3Destination = S3ItemWithAccessLevel.from(destination);
Copy link
Member

@khatruong2009 khatruong2009 Mar 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we leaving this in the code to save it for later?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. I removed these.


final s3PluginOptions = reifyPluginOptions(
pluginOptions: options?.pluginOptions,
Expand All @@ -381,45 +381,13 @@ class AmplifyStorageS3Dart extends StoragePluginInterface

return S3CopyOperation(
request: StorageCopyRequest(
source: s3Source,
destination: s3Destination,
source: source,
destination: destination,
options: options,
),
result: storageS3Service.copy(
source: s3Source,
destination: s3Destination,
options: s3Options,
),
);
}

@override
S3MoveOperation move({
required StorageItemWithAccessLevel<StorageItem> source,
required StorageItemWithAccessLevel<StorageItem> destination,
StorageMoveOptions? options,
}) {
final s3PluginOptions = reifyPluginOptions(
pluginOptions: options?.pluginOptions,
defaultPluginOptions: const S3MovePluginOptions(),
);

final s3Options = StorageMoveOptions(
pluginOptions: s3PluginOptions,
);

final s3Source = S3ItemWithAccessLevel.from(source);
final s3Destination = S3ItemWithAccessLevel.from(destination);

return S3MoveOperation(
request: StorageMoveRequest(
source: s3Source,
destination: s3Destination,
options: options,
),
result: storageS3Service.move(
source: s3Source,
destination: s3Destination,
source: source,
destination: destination,
options: s3Options,
),
);
Expand Down
Loading
Loading