55
66import typing
77
8+ import os
9+
810from ._pathcompat import commonpath
911from .copy import copy_dir , copy_file
10- from .errors import FSError
12+ from .error_tools import convert_os_errors
13+ from .errors import DirectoryExpected , FSError , IllegalDestination , ResourceNotFound
1114from .opener import manage_fs
1215from .osfs import OSFS
13- from .path import frombase
16+ from .path import frombase , isbase
1417
1518if typing .TYPE_CHECKING :
1619 from typing import Text , Union
@@ -26,15 +29,13 @@ def move_fs(
2629):
2730 # type: (...) -> None
2831 """Move the contents of a filesystem to another filesystem.
29-
3032 Arguments:
3133 src_fs (FS or str): Source filesystem (instance or URL).
3234 dst_fs (FS or str): Destination filesystem (instance or URL).
3335 workers (int): Use `worker` threads to copy data, or ``0`` (default) for
3436 a single-threaded copy.
3537 preserve_time (bool): If `True`, try to preserve mtime of the
3638 resources (defaults to `False`).
37-
3839 """
3940 move_dir (src_fs , "/" , dst_fs , "/" , workers = workers , preserve_time = preserve_time )
4041
@@ -49,7 +50,6 @@ def move_file(
4950):
5051 # type: (...) -> None
5152 """Move a file from one filesystem to another.
52-
5353 Arguments:
5454 src_fs (FS or str): Source filesystem (instance or URL).
5555 src_path (str): Path to a file on ``src_fs``.
@@ -59,7 +59,6 @@ def move_file(
5959 resources (defaults to `False`).
6060 cleanup_dst_on_error (bool): If `True`, tries to delete the file copied to
6161 ``dst_fs`` if deleting the file from ``src_fs`` fails (defaults to `True`).
62-
6362 """
6463 with manage_fs (src_fs , writeable = True ) as _src_fs :
6564 with manage_fs (dst_fs , writeable = True , create = True ) as _dst_fs :
@@ -123,7 +122,6 @@ def move_dir(
123122):
124123 # type: (...) -> None
125124 """Move a directory from one filesystem to another.
126-
127125 Arguments:
128126 src_fs (FS or str): Source filesystem (instance or URL).
129127 src_path (str): Path to a directory on ``src_fs``
@@ -133,10 +131,35 @@ def move_dir(
133131 (default) for a single-threaded copy.
134132 preserve_time (bool): If `True`, try to preserve mtime of the
135133 resources (defaults to `False`).
136-
134+ Raises:
135+ fs.errors.ResourceNotFound: if ``src_path`` does not exist on `src_fs`
136+ fs.errors.DirectoryExpected: if ``src_path`` or one of its
137+ ancestors is not a directory.
138+ fs.errors.IllegalDestination: when moving a folder into itself
137139 """
138140 with manage_fs (src_fs , writeable = True ) as _src_fs :
139141 with manage_fs (dst_fs , writeable = True , create = True ) as _dst_fs :
142+ if not _src_fs .exists (src_path ):
143+ raise ResourceNotFound (src_path )
144+ if not _src_fs .isdir (src_path ):
145+ raise DirectoryExpected (src_path )
146+
147+ # if both filesystems have a syspath we use `os.rename` to move the folder
148+ if _src_fs .hassyspath (src_path ) and _dst_fs .hassyspath (dst_path ):
149+ src_syspath = _src_fs .getsyspath (src_path )
150+ dst_syspath = _dst_fs .getsyspath (dst_path )
151+ # recheck if the move operation is legal using the syspaths
152+ if isbase (src_syspath , dst_syspath ):
153+ raise IllegalDestination (dst_path )
154+ with _src_fs .lock (), _dst_fs .lock ():
155+ with convert_os_errors ("move_dir" , src_path , directory = True ):
156+ os .rename (src_syspath , dst_syspath )
157+ # recreate the root dir if it has been renamed
158+ if src_path == "/" :
159+ _src_fs .makedir ("/" )
160+ return # optimization worked, exit early
161+
162+ # standard copy then delete
140163 with _src_fs .lock (), _dst_fs .lock ():
141164 _dst_fs .makedir (dst_path , recreate = True )
142165 copy_dir (
0 commit comments