diff --git a/tests/core/test_provider.py b/tests/core/test_provider.py index 123888ca0..d06386560 100644 --- a/tests/core/test_provider.py +++ b/tests/core/test_provider.py @@ -276,6 +276,15 @@ async def test_passes_on_rename(self, provider1): conflict='replace', ) + @pytest.mark.asyncio + async def test_copy_will_self_overwrite(self, provider1): + src_path = await provider1.validate_path('/source/path') + dest_path = await provider1.validate_path('/destination/') + provider1.will_self_overwrite = utils.MockCoroutine() + + with pytest.raises(exceptions.OverwriteSelfError): + await provider1.copy(provider1, src_path, dest_path) + @pytest.mark.asyncio async def test_checks_can_intra_copy(self, provider1): provider1.can_intra_copy = mock.Mock(return_value=False) @@ -393,6 +402,15 @@ async def test_passes_on_rename(self, provider1): conflict='replace', ) + @pytest.mark.asyncio + async def test_move_will_self_overwrite(self, provider1): + src_path = await provider1.validate_path('/source/path') + dest_path = await provider1.validate_path('/destination/') + provider1.will_self_overwrite = utils.MockCoroutine() + + with pytest.raises(exceptions.OverwriteSelfError): + await provider1.move(provider1, src_path, dest_path) + @pytest.mark.asyncio async def test_checks_can_intra_move(self, provider1): provider1.can_intra_move = mock.Mock(return_value=False) diff --git a/tests/providers/box/test_provider.py b/tests/providers/box/test_provider.py index 384e9e7dc..f3b670b3f 100644 --- a/tests/providers/box/test_provider.py +++ b/tests/providers/box/test_provider.py @@ -165,7 +165,9 @@ async def test_validate_path(self, provider, root_provider_fixtures): provider.folder = '0' folder_id = '0' - good_url = provider.build_url('folders', folder_id, 'items', fields='id,name,type', limit=1000) + good_url = provider.build_url('folders', folder_id, 'items', + fields='id,name,type', limit=1000) + aiohttpretty.register_json_uri('GET', good_url, body=root_provider_fixtures['revalidate_metadata'], status=200) @@ -316,7 +318,7 @@ async def test_upload_checksum_mismatch(self, provider, root_provider_fixtures, aiohttpretty.register_json_uri('POST', upload_url, status=201, body=root_provider_fixtures['checksum_mismatch_metadata']) - with pytest.raises(exceptions.UploadChecksumMismatchError) as exc: + with pytest.raises(exceptions.UploadChecksumMismatchError): await provider.upload(file_stream, path) assert aiohttpretty.has_call(method='POST', uri=upload_url) @@ -381,8 +383,11 @@ async def test_delete_root(self, provider, root_provider_fixtures): url = provider.build_url('folders', root_path.identifier, 'items', fields='id,name,size,modified_at,etag,total_count', offset=(0), limit=1000) - aiohttpretty.register_json_uri('GET', url, - body=root_provider_fixtures['one_entry_folder_list_metadata']) + aiohttpretty.register_json_uri( + 'GET', + url, + body=root_provider_fixtures['one_entry_folder_list_metadata'] + ) url = provider.build_url('files', item['id'], fields='id,name,path_collection') delete_url = provider.build_url('files', path.identifier) @@ -589,6 +594,7 @@ async def test_get_revisions_free_account(self, provider, root_provider_fixtures class TestIntraCopy: + @pytest.mark.asyncio @pytest.mark.aiohttpretty async def test_intra_copy_file(self, provider, root_provider_fixtures): @@ -609,7 +615,7 @@ async def test_intra_copy_file(self, provider, root_provider_fixtures): async def test_intra_copy_file_replace(self, provider, root_provider_fixtures): item = root_provider_fixtures['file_metadata']['entries'][0] src_path = WaterButlerPath('/name.txt', _ids=(provider, item['id'])) - dest_path = WaterButlerPath('/charmander/name.txt', _ids=(provider, item['id'], item['id'])) + dest_path = WaterButlerPath('/charmander/name.txt', _ids=(provider, item['id'], 'cats77831')) file_url = provider.build_url('files', src_path.identifier, 'copy') delete_url = provider.build_url('files', dest_path.identifier) @@ -641,7 +647,9 @@ async def test_intra_copy_folder(self, provider, intra_fixtures, root_provider_f expected_folder = BoxFolderMetadata(item, dest_path) expected_folder._children = [] for child_item in list_metadata['entries']: - child_path = dest_path.child(child_item['name'], folder=(child_item['type'] == 'folder')) + child_path = dest_path.child(child_item['name'], + folder=(child_item['type'] == 'folder')) + serialized_child = provider._serialize_item(child_item, child_path) expected_folder._children.append(serialized_child) expected = (expected_folder, True) @@ -652,12 +660,15 @@ async def test_intra_copy_folder(self, provider, intra_fixtures, root_provider_f @pytest.mark.asyncio @pytest.mark.aiohttpretty - async def test_intra_copy_folder_replace(self, provider, intra_fixtures, root_provider_fixtures): + async def test_intra_copy_folder_replace(self, + provider, + intra_fixtures, + root_provider_fixtures): item = intra_fixtures['intra_folder_metadata'] list_metadata = root_provider_fixtures['folder_list_metadata'] src_path = WaterButlerPath('/name/', _ids=(provider, item['id'])) - dest_path = WaterButlerPath('/charmander/name/', _ids=(provider, item['id'], item['id'])) + dest_path = WaterButlerPath('/charmander/name/', _ids=(provider, item['id'], '4jkmrm4zzerj')) file_url = provider.build_url('folders', src_path.identifier, 'copy') delete_url = provider.build_url('folders', dest_path.identifier, recursive=True) @@ -672,7 +683,9 @@ async def test_intra_copy_folder_replace(self, provider, intra_fixtures, root_pr expected_folder = BoxFolderMetadata(item, dest_path) expected_folder._children = [] for child_item in list_metadata['entries']: - child_path = dest_path.child(child_item['name'], folder=(child_item['type'] == 'folder')) + child_path = dest_path.child(child_item['name'], + folder=(child_item['type'] == 'folder')) + serialized_child = provider._serialize_item(child_item, child_path) expected_folder._children.append(serialized_child) expected = (expected_folder, False) @@ -705,7 +718,8 @@ async def test_intra_move_file(self, provider, root_provider_fixtures): async def test_intra_move_file_replace(self, provider, root_provider_fixtures): item = root_provider_fixtures['file_metadata']['entries'][0] src_path = WaterButlerPath('/name.txt', _ids=(provider, item['id'])) - dest_path = WaterButlerPath('/charmander/name.txt', _ids=(provider, item['id'], item['id'])) + dest_path = WaterButlerPath('/charmander/name.txt', + _ids=(provider, item['id'], 'YgzZejrj834j')) file_url = provider.build_url('files', src_path.identifier) delete_url = provider.build_url('files', dest_path.identifier) @@ -737,7 +751,10 @@ async def test_intra_move_folder(self, provider, intra_fixtures, root_provider_f expected_folder = BoxFolderMetadata(item, dest_path) expected_folder._children = [] for child_item in list_metadata['entries']: - child_path = dest_path.child(child_item['name'], folder=(child_item['type'] == 'folder')) + child_path = dest_path.child( + child_item['name'], + folder=(child_item['type'] == 'folder') + ) serialized_child = provider._serialize_item(child_item, child_path) expected_folder._children.append(serialized_child) expected = (expected_folder, True) @@ -746,15 +763,17 @@ async def test_intra_move_folder(self, provider, intra_fixtures, root_provider_f assert result == expected - @pytest.mark.asyncio @pytest.mark.aiohttpretty - async def test_intra_move_folder_replace(self, provider, intra_fixtures, root_provider_fixtures): + async def test_intra_move_folder_replace(self, + provider, + intra_fixtures, + root_provider_fixtures): item = intra_fixtures['intra_folder_metadata'] list_metadata = root_provider_fixtures['folder_list_metadata'] src_path = WaterButlerPath('/name/', _ids=(provider, item['id'])) - dest_path = WaterButlerPath('/charmander/name/', _ids=(provider, item['id'], item['id'])) + dest_path = WaterButlerPath('/charmander/name/', _ids=(provider, item['id'], '7759994812')) file_url = provider.build_url('folders', src_path.identifier) delete_url = provider.build_url('folders', dest_path.identifier, recursive=True) @@ -769,7 +788,9 @@ async def test_intra_move_folder_replace(self, provider, intra_fixtures, root_pr expected_folder = BoxFolderMetadata(item, dest_path) expected_folder._children = [] for child_item in list_metadata['entries']: - child_path = dest_path.child(child_item['name'], folder=(child_item['type'] == 'folder')) + child_path = dest_path.child(child_item['name'], + folder=(child_item['type'] == 'folder')) + serialized_child = provider._serialize_item(child_item, child_path) expected_folder._children.append(serialized_child) expected = (expected_folder, False) @@ -853,25 +874,26 @@ async def test_returns_metadata(self, provider, root_provider_fixtures): class TestOperations: - @pytest.mark.asyncio - @pytest.mark.aiohttpretty - async def test_can_duplicate_names(self, provider): + def test_will_self_overwrite(self, provider, other_provider): + src_path = WaterButlerPath('/50 shades of nope.txt', + _ids=(provider.folder, '12231')) + dest_path = WaterButlerPath('/50 shades of nope2223.txt', + _ids=(provider.folder, '2342sdfsd')) + + assert provider.will_self_overwrite(other_provider, src_path, dest_path) is False + assert provider.will_self_overwrite(other_provider, src_path, src_path) is True + + def test_can_duplicate_names(self, provider): assert provider.can_duplicate_names() is False - @pytest.mark.asyncio - @pytest.mark.aiohttpretty - async def test_shares_storage_root(self, provider, other_provider): + def test_shares_storage_root(self, provider, other_provider): assert provider.shares_storage_root(other_provider) is False assert provider.shares_storage_root(provider) is True - @pytest.mark.asyncio - @pytest.mark.aiohttpretty - async def test_can_intra_move(self, provider, other_provider): + def test_can_intra_move(self, provider, other_provider): assert provider.can_intra_move(other_provider) is False assert provider.can_intra_move(provider) is True - @pytest.mark.asyncio - @pytest.mark.aiohttpretty - async def test_can_intra_copy(self, provider, other_provider): + def test_can_intra_copy(self, provider, other_provider): assert provider.can_intra_copy(other_provider) is False assert provider.can_intra_copy(provider) is True diff --git a/tests/providers/dropbox/test_provider.py b/tests/providers/dropbox/test_provider.py index 787af4a31..1b0b55207 100644 --- a/tests/providers/dropbox/test_provider.py +++ b/tests/providers/dropbox/test_provider.py @@ -677,8 +677,8 @@ async def test_intra_copy_replace_file( 'headers': {'Content-Type': 'application/json'}, 'data': data, 'body': json.dumps( - error_fixtures['rename_conflict_folder_metadata'] - ).encode('utf-8'), + error_fixtures['rename_conflict_folder_metadata'] + ).encode('utf-8'), 'status': HTTPStatus.CONFLICT }, { @@ -829,7 +829,9 @@ async def test_intra_move_replace_file(self, provider, provider_fixtures, error_ { 'headers': {'Content-Type': 'application/json'}, 'data': data, - 'body': json.dumps(error_fixtures['rename_conflict_file_metadata']).encode('utf-8'), + 'body': json.dumps( + error_fixtures['rename_conflict_file_metadata'] + ).encode('utf-8'), 'status': HTTPStatus.CONFLICT }, { @@ -883,7 +885,9 @@ async def test_intra_move_replace_folder(self, provider, provider_fixtures, erro { 'headers': {'Content-Type': 'application/json'}, 'data': data, - 'body': json.dumps(error_fixtures['rename_conflict_folder_metadata']).encode('utf-8'), + 'body': json.dumps( + error_fixtures['rename_conflict_folder_metadata'] + ).encode('utf-8'), 'status': HTTPStatus.CONFLICT }, { @@ -921,6 +925,15 @@ async def test_intra_move_casing_change(self, provider): class TestOperations: + def test_will_self_overwrite(self, provider, other_provider): + src_path = WaterButlerPath('/50 shades of nope.txt', + _ids=(provider.folder, '12231')) + dest_path = WaterButlerPath('/50 shades of nope2223.txt', + _ids=(provider.folder, '2342sdfsd')) + + assert provider.will_self_overwrite(other_provider, src_path, dest_path) is False + assert provider.will_self_overwrite(other_provider, src_path, src_path) is True + def test_can_intra_copy(self, provider): assert provider.can_intra_copy(provider) diff --git a/tests/providers/googledrive/test_provider.py b/tests/providers/googledrive/test_provider.py index 6c80a507e..d6ee06a1d 100644 --- a/tests/providers/googledrive/test_provider.py +++ b/tests/providers/googledrive/test_provider.py @@ -1516,9 +1516,9 @@ class TestIntraFunctions: @pytest.mark.aiohttpretty async def test_intra_move_file(self, provider, root_provider_fixtures): item = root_provider_fixtures['docs_file_metadata'] - src_path = WaterButlerPath('/unsure.txt', _ids=(provider.folder['id'], item['id'])) - dest_path = WaterButlerPath('/really/unsure.txt', _ids=(provider.folder['id'], - item['id'], item['id'])) + src_path = WaterButlerPath('/unsure.txt', _ids=('0', item['id'])) + dest_path = WaterButlerPath('/really/unsure.txt', _ids=('0', + 'yy42kcj', 'rrjk42k')) url = provider.build_url('files', src_path.identifier) data = json.dumps({ @@ -1529,7 +1529,7 @@ async def test_intra_move_file(self, provider, root_provider_fixtures): }), aiohttpretty.register_json_uri('PATCH', url, data=data, body=item) - delete_url = provider.build_url('files', item['id']) + delete_url = provider.build_url('files', dest_path.identifier) del_url_body = json.dumps({'labels': {'trashed': 'true'}}) aiohttpretty.register_uri('PUT', delete_url, body=del_url_body, status=200) @@ -1543,9 +1543,9 @@ async def test_intra_move_file(self, provider, root_provider_fixtures): @pytest.mark.aiohttpretty async def test_intra_move_folder(self, provider, root_provider_fixtures): item = root_provider_fixtures['folder_metadata'] - src_path = WaterButlerPath('/unsure/', _ids=(provider.folder['id'], item['id'])) - dest_path = WaterButlerPath('/really/unsure/', _ids=(provider.folder['id'], - item['id'], item['id'])) + src_path = WaterButlerPath('/unsure/', _ids=('0', item['id'])) + dest_path = WaterButlerPath('/really/unsure/', _ids=('0', + '42jdkerf', '7ejGjeajr')) url = provider.build_url('files', src_path.identifier) data = json.dumps({ @@ -1556,11 +1556,11 @@ async def test_intra_move_folder(self, provider, root_provider_fixtures): }), aiohttpretty.register_json_uri('PATCH', url, data=data, body=item) - delete_url = provider.build_url('files', item['id']) + delete_url = provider.build_url('files', dest_path.identifier) del_url_body = json.dumps({'labels': {'trashed': 'true'}}) aiohttpretty.register_uri('PUT', delete_url, body=del_url_body, status=200) - children_query = provider._build_query(dest_path.identifier) + children_query = provider._build_query(src_path.identifier) children_url = provider.build_url('files', q=children_query, alt='json', maxResults=1000) children_list = generate_list(3, **root_provider_fixtures['folder_metadata']) aiohttpretty.register_json_uri('GET', children_url, body=children_list) @@ -1579,10 +1579,9 @@ async def test_intra_move_folder(self, provider, root_provider_fixtures): @pytest.mark.aiohttpretty async def test_intra_copy_file(self, provider, root_provider_fixtures): item = root_provider_fixtures['docs_file_metadata'] - src_path = WaterButlerPath('/unsure.txt', _ids=(provider.folder['id'], item['id'])) - dest_path = WaterButlerPath('/really/unsure.txt', _ids=(provider.folder['id'], - item['id'], item['id'])) - + src_path = WaterButlerPath('/unsure.txt', _ids=('0', item['id'])) + dest_path = WaterButlerPath('/really/unsure.txt', _ids=('0', + '312kjfe', '4ckk2lkl3')) url = provider.build_url('files', src_path.identifier, 'copy') data = json.dumps({ 'parents': [{ @@ -1592,7 +1591,7 @@ async def test_intra_copy_file(self, provider, root_provider_fixtures): }), aiohttpretty.register_json_uri('POST', url, data=data, body=item) - delete_url = provider.build_url('files', item['id']) + delete_url = provider.build_url('files', dest_path.identifier) del_url_body = json.dumps({'labels': {'trashed': 'true'}}) aiohttpretty.register_uri('PUT', delete_url, body=del_url_body, status=200) @@ -1605,33 +1604,30 @@ async def test_intra_copy_file(self, provider, root_provider_fixtures): class TestOperationsOrMisc: - @pytest.mark.asyncio - @pytest.mark.aiohttpretty - async def test_can_duplicate_names(self, provider): + def test_will_self_overwrite(self, provider, other_provider): + src_path = GoogleDrivePath('/root/Gear1.stl', _ids=['0', '10', '11']) + dest_path = GoogleDrivePath('/root/Gear23123.stl', _ids=['0', '10', '12']) + + assert provider.will_self_overwrite(other_provider, src_path, dest_path) is False + assert provider.will_self_overwrite(other_provider, src_path, src_path) is True + + def test_can_duplicate_names(self, provider): assert provider.can_duplicate_names() is True - @pytest.mark.asyncio - @pytest.mark.aiohttpretty - async def test_shares_storage_root(self, provider, other_provider): + def test_shares_storage_root(self, provider, other_provider): assert provider.shares_storage_root(other_provider) is True assert provider.shares_storage_root(provider) is True - @pytest.mark.asyncio - @pytest.mark.aiohttpretty - async def test_can_intra_move(self, provider, other_provider): + def test_can_intra_move(self, provider, other_provider): assert provider.can_intra_move(other_provider) is False assert provider.can_intra_move(provider) is True - @pytest.mark.asyncio - @pytest.mark.aiohttpretty - async def test__serialize_item_raw(self, provider, root_provider_fixtures): + def test__serialize_item_raw(self, provider, root_provider_fixtures): item = root_provider_fixtures['docs_file_metadata'] assert provider._serialize_item(None, item, True) == item - @pytest.mark.asyncio - @pytest.mark.aiohttpretty - async def test_can_intra_copy(self, provider, other_provider, root_provider_fixtures): + def test_can_intra_copy(self, provider, other_provider, root_provider_fixtures): item = root_provider_fixtures['list_file']['items'][0] path = WaterButlerPath('/birdie.jpg', _ids=(provider.folder['id'], item['id'])) @@ -1668,7 +1664,7 @@ async def test_revalidate_path_file_error(self, provider, root_provider_fixtures body=error_fixtures['parts_file_missing_metadata']) with pytest.raises(exceptions.MetadataError) as e: - result = await provider._resolve_path_to_ids(file_name) + await provider._resolve_path_to_ids(file_name) assert e.value.message == '{} not found'.format(str(path)) assert e.value.code == 404 diff --git a/tests/providers/owncloud/test_provider.py b/tests/providers/owncloud/test_provider.py index d83a33479..e6982aae5 100644 --- a/tests/providers/owncloud/test_provider.py +++ b/tests/providers/owncloud/test_provider.py @@ -36,6 +36,7 @@ def file_like(file_content): return io.BytesIO(file_content) + @pytest.fixture def file_stream(file_like): return streams.FileStreamReader(file_like) @@ -375,6 +376,15 @@ async def test_revisions(self, provider, file_metadata): class TestOperations: + def test_will_self_overwrite(self, provider): + src_path = WaterButlerPath('/50 shades of nope.txt', + _ids=(provider.folder, '12231')) + dest_path = WaterButlerPath('/50 shades of nope2223.txt', + _ids=(provider.folder, '2342sdfsd')) + + assert provider.will_self_overwrite(provider, src_path, dest_path) is False + assert provider.will_self_overwrite(provider, src_path, src_path) is True + def test_can_intra_copy(self, provider, provider_different_credentials): assert provider.can_intra_copy(provider) assert not provider.can_intra_copy(provider_different_credentials) diff --git a/waterbutler/core/provider.py b/waterbutler/core/provider.py index 84e44d8dd..003a419f0 100644 --- a/waterbutler/core/provider.py +++ b/waterbutler/core/provider.py @@ -197,12 +197,8 @@ async def make_request(self, method: str, url: str, *args, **kwargs) -> aiohttp. def request(self, *args, **kwargs): return RequestHandlerContext(self.make_request(*args, **kwargs)) - async def move(self, - dest_provider: 'BaseProvider', - src_path: wb_path.WaterButlerPath, - dest_path: wb_path.WaterButlerPath, - rename: str=None, - conflict: str='replace', + async def move(self, dest_provider: 'BaseProvider', src_path: wb_path.WaterButlerPath, + dest_path: wb_path.WaterButlerPath, rename: str=None, conflict: str='replace', handle_naming: bool=True) -> typing.Tuple[wb_metadata.BaseMetadata, bool]: """Moves a file or folder from the current provider to the specified one Performs a copy and then a delete. @@ -224,6 +220,18 @@ async def move(self, 'got_rename': rename is not None, }) + # This does not mean that the `dest_path` is a folder, at this point it could just be the + # parent of the actual destination file/folder, or have a blank name. Use `not .is_file` + # in the if statement for less confusion. + if not dest_path.is_file: + temp_path = await self.revalidate_path( + dest_path, + rename or src_path.name, + folder=src_path.is_dir + ) + if self.will_self_overwrite(dest_provider, src_path, temp_path): + raise exceptions.OverwriteSelfError(src_path) + if handle_naming: dest_path = await dest_provider.handle_naming( src_path, @@ -236,8 +244,8 @@ async def move(self, # files and folders shouldn't overwrite themselves if ( - self.shares_storage_root(dest_provider) and - src_path.materialized_path == dest_path.materialized_path + self.shares_storage_root(dest_provider) and + src_path.materialized_path == dest_path.materialized_path ): raise exceptions.OverwriteSelfError(src_path) @@ -255,13 +263,10 @@ async def move(self, return meta_data, created - async def copy(self, - dest_provider: 'BaseProvider', - src_path: wb_path.WaterButlerPath, - dest_path: wb_path.WaterButlerPath, - rename: str=None, conflict: str='replace', - handle_naming: bool=True) \ - -> typing.Tuple[wb_metadata.BaseMetadata, bool]: + async def copy(self, dest_provider: 'BaseProvider', src_path: wb_path.WaterButlerPath, + dest_path: wb_path.WaterButlerPath, rename: str=None, conflict: str='replace', + handle_naming: bool=True) -> typing.Tuple[wb_metadata.BaseMetadata, bool]: + args = (dest_provider, src_path, dest_path) kwargs = {'rename': rename, 'conflict': conflict, 'handle_naming': handle_naming} @@ -270,6 +275,19 @@ async def copy(self, 'conflict': conflict, 'got_rename': rename is not None, }) + + # This does not mean that the `dest_path` is a folder, at this point it could just be the + # parent of the actual destination file/folder, or have a blank name. Use `not .is_file` + # in the if statement for less confusion. + if not dest_path.is_file: + temp_path = await self.revalidate_path( + dest_path, + rename or src_path.name, + folder=src_path.is_dir + ) + if self.will_self_overwrite(dest_provider, src_path, temp_path): + raise exceptions.OverwriteSelfError(src_path) + if handle_naming: dest_path = await dest_provider.handle_naming( src_path, @@ -374,7 +392,8 @@ async def handle_naming(self, dest_path: wb_path.WaterButlerPath, rename: str=None, conflict: str='replace') -> wb_path.WaterButlerPath: - """Given a :class:`.WaterButlerPath` and the desired name, handle any potential naming issues. + """Given a :class:`.WaterButlerPath` and the desired name, handle any potential naming + issues. i.e.: @@ -388,19 +407,23 @@ async def handle_naming(self, :param src_path: ( :class:`.WaterButlerPath` ) The object that is being copied - :param dest_path: ( :class:`.WaterButlerPath` ) The path that is being copied to or into + :param dest_path: ( :class:`.WaterButlerPath` ) The path that is being copied TO or INTO :param rename: ( :class:`str` ) The desired name of the resulting path, may be incremented :param conflict: ( :class:`str` ) The conflict resolution strategy, ``replace`` or ``keep`` :rtype: :class:`.WaterButlerPath` """ + + # Can't copy a directory to a file if src_path.is_dir and dest_path.is_file: - # Cant copy a directory to a file raise ValueError('Destination must be a directory if the source is') if not dest_path.is_file: - # Directories always are going to be copied into - # cp /folder1/ /folder2/ -> /folder1/folder2/ + # At this point, `dest_path` must be the parent or root folder into which we want to + # move/copy the source. The file/folder is always going to be copied into the parent. + # For example: + # cp /file.txt /folder/ -> /folder/file.txt + # cp /folder1/ /folder2/ -> /folder1/folder2/ dest_path = await self.revalidate_path( dest_path, rename or src_path.name, @@ -408,9 +431,25 @@ async def handle_naming(self, ) dest_path, _ = await self.handle_name_conflict(dest_path, conflict=conflict) - return dest_path + def will_self_overwrite(self, + dest_provider: 'BaseProvider', + src_path: wb_path.WaterButlerPath, + dest_path: wb_path.WaterButlerPath) -> bool: + """Return wether a move or copy operation will result in a self-overwrite. + + .. note:: + Defaults to False + Overridden by providers that need to run this check + + :param dest_provider: ( :class:`.BaseProvider` ) The provider to check against + :param src_path: ( :class:`.WaterButlerPath` ) The move/copy source path + :param dest_path: ( :class:`.WaterButlerPath` ) The move/copy destination path + :rtype: :class:`bool` + """ + return False + def can_intra_copy(self, other: 'BaseProvider', path: wb_path.WaterButlerPath=None) -> bool: diff --git a/waterbutler/providers/box/provider.py b/waterbutler/providers/box/provider.py index fc2533c89..e1ef8e20d 100644 --- a/waterbutler/providers/box/provider.py +++ b/waterbutler/providers/box/provider.py @@ -183,6 +183,9 @@ def shares_storage_root(self, other: provider.BaseProvider) -> bool: Add a comparison of credentials to avoid this.""" return super().shares_storage_root(other) and self.credentials == other.credentials + def will_self_overwrite(self, dest_provider, src_path, dest_path): + return self.NAME == dest_provider.NAME and src_path.identifier == dest_path.identifier + def can_intra_move(self, other: provider.BaseProvider, path: WaterButlerPath=None) -> bool: return self == other diff --git a/waterbutler/providers/dropbox/provider.py b/waterbutler/providers/dropbox/provider.py index 7c4cecdae..b660e99c1 100644 --- a/waterbutler/providers/dropbox/provider.py +++ b/waterbutler/providers/dropbox/provider.py @@ -372,6 +372,9 @@ async def create_folder(self, path: WaterButlerPath, **kwargs) -> DropboxFolderM ) return DropboxFolderMetadata(data['metadata'], self.folder) + def will_self_overwrite(self, dest_provider, src_path, dest_path): + return self.NAME == dest_provider.NAME and dest_path.full_path == src_path.full_path + def can_intra_copy(self, dest_provider: provider.BaseProvider, path: WaterButlerPath=None) -> bool: return type(self) == type(dest_provider) diff --git a/waterbutler/providers/googledrive/provider.py b/waterbutler/providers/googledrive/provider.py index d254bfd75..c743c66b6 100644 --- a/waterbutler/providers/googledrive/provider.py +++ b/waterbutler/providers/googledrive/provider.py @@ -133,6 +133,9 @@ def can_duplicate_names(self) -> bool: def default_headers(self) -> dict: return {'authorization': 'Bearer {}'.format(self.token)} + def will_self_overwrite(self, dest_provider, src_path, dest_path): + return self.NAME == dest_provider.NAME and src_path.identifier == dest_path.identifier + def can_intra_move(self, other: provider.BaseProvider, path: WaterButlerPath=None) -> bool: return self == other diff --git a/waterbutler/providers/owncloud/provider.py b/waterbutler/providers/owncloud/provider.py index 29cc2b10e..d183129c1 100644 --- a/waterbutler/providers/owncloud/provider.py +++ b/waterbutler/providers/owncloud/provider.py @@ -266,6 +266,9 @@ async def create_folder(self, path, **kwargs): def can_duplicate_names(self): return True + def will_self_overwrite(self, dest_provider, src_path, dest_path): + return self.NAME == dest_provider.NAME and src_path.identifier == dest_path.identifier + def can_intra_copy(self, dest_provider, path=None): return self == dest_provider