Skip to content

Commit ac8a91a

Browse files
authored
Merge pull request #463 from atollk/issue_450
Added preserve_time parameter to move, copy, and mirror
2 parents 5f73778 + ebcd1a9 commit ac8a91a

20 files changed

+459
-122
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
3030
- Better documentation of the `writable` parameter of `fs.open_fs`, and
3131
hint about using `fs.wrap.read_only` when a read-only filesystem is
3232
required. Closes [#441](https://github.com/PyFilesystem/pyfilesystem2/issues/441).
33+
- Copy and move operations now provide a parameter `preserve_time` that, when
34+
passed as `True`, makes sure the "mtime" of the destination file will be
35+
the same as that of the source file.
3336

3437
### Changed
3538

fs/_bulk.py

+20-7
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@
1111

1212
from six.moves.queue import Queue
1313

14-
from .copy import copy_file_internal
14+
from .copy import copy_file_internal, copy_modified_time
1515
from .errors import BulkCopyFailed
1616
from .tools import copy_file_data
1717

1818
if typing.TYPE_CHECKING:
1919
from .base import FS
2020
from types import TracebackType
21-
from typing import IO, List, Optional, Text, Type
21+
from typing import List, Optional, Text, Type, IO, Tuple
2222

2323

2424
class _Worker(threading.Thread):
@@ -75,11 +75,13 @@ def __call__(self):
7575
class Copier(object):
7676
"""Copy files in worker threads."""
7777

78-
def __init__(self, num_workers=4):
79-
# type: (int) -> None
78+
def __init__(self, num_workers=4, preserve_time=False):
79+
# type: (int, bool) -> None
8080
if num_workers < 0:
8181
raise ValueError("num_workers must be >= 0")
8282
self.num_workers = num_workers
83+
self.preserve_time = preserve_time
84+
self.all_tasks = [] # type: List[Tuple[FS, Text, FS, Text]]
8385
self.queue = None # type: Optional[Queue[_Task]]
8486
self.workers = [] # type: List[_Worker]
8587
self.errors = [] # type: List[Exception]
@@ -97,10 +99,18 @@ def start(self):
9799
def stop(self):
98100
"""Stop the workers (will block until they are finished)."""
99101
if self.running and self.num_workers:
102+
# Notify the workers that all tasks have arrived
103+
# and wait for them to finish.
100104
for _worker in self.workers:
101105
self.queue.put(None)
102106
for worker in self.workers:
103107
worker.join()
108+
109+
# If the "last modified" time is to be preserved, do it now.
110+
if self.preserve_time:
111+
for args in self.all_tasks:
112+
copy_modified_time(*args)
113+
104114
# Free up references held by workers
105115
del self.workers[:]
106116
self.queue.join()
@@ -124,13 +134,16 @@ def __exit__(
124134
if traceback is None and self.errors:
125135
raise BulkCopyFailed(self.errors)
126136

127-
def copy(self, src_fs, src_path, dst_fs, dst_path):
128-
# type: (FS, Text, FS, Text) -> None
137+
def copy(self, src_fs, src_path, dst_fs, dst_path, preserve_time=False):
138+
# type: (FS, Text, FS, Text, bool) -> None
129139
"""Copy a file from one fs to another."""
130140
if self.queue is None:
131141
# This should be the most performant for a single-thread
132-
copy_file_internal(src_fs, src_path, dst_fs, dst_path)
142+
copy_file_internal(
143+
src_fs, src_path, dst_fs, dst_path, preserve_time=self.preserve_time
144+
)
133145
else:
146+
self.all_tasks.append((src_fs, src_path, dst_fs, dst_path))
134147
src_file = src_fs.openbin(src_path, "r")
135148
try:
136149
dst_file = dst_fs.openbin(dst_path, "w")

fs/base.py

+37-10
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import six
2323

2424
from . import copy, errors, fsencode, iotools, move, tools, walk, wildcard
25+
from .copy import copy_modified_time
2526
from .glob import BoundGlobber
2627
from .mode import validate_open_mode
2728
from .path import abspath, join, normpath
@@ -393,15 +394,23 @@ def close(self):
393394
"""
394395
self._closed = True
395396

396-
def copy(self, src_path, dst_path, overwrite=False):
397-
# type: (Text, Text, bool) -> None
397+
def copy(
398+
self,
399+
src_path, # type: Text
400+
dst_path, # type: Text
401+
overwrite=False, # type: bool
402+
preserve_time=False, # type: bool
403+
):
404+
# type: (...) -> None
398405
"""Copy file contents from ``src_path`` to ``dst_path``.
399406
400407
Arguments:
401408
src_path (str): Path of source file.
402409
dst_path (str): Path to destination file.
403410
overwrite (bool): If `True`, overwrite the destination file
404411
if it exists (defaults to `False`).
412+
preserve_time (bool): If `True`, try to preserve mtime of the
413+
resource (defaults to `False`).
405414
406415
Raises:
407416
fs.errors.DestinationExists: If ``dst_path`` exists,
@@ -417,16 +426,26 @@ def copy(self, src_path, dst_path, overwrite=False):
417426
with closing(self.open(src_path, "rb")) as read_file:
418427
# FIXME(@althonos): typing complains because open return IO
419428
self.upload(dst_path, read_file) # type: ignore
429+
if preserve_time:
430+
copy_modified_time(self, src_path, self, dst_path)
420431

421-
def copydir(self, src_path, dst_path, create=False):
422-
# type: (Text, Text, bool) -> None
432+
def copydir(
433+
self,
434+
src_path, # type: Text
435+
dst_path, # type: Text
436+
create=False, # type: bool
437+
preserve_time=False, # type: bool
438+
):
439+
# type: (...) -> None
423440
"""Copy the contents of ``src_path`` to ``dst_path``.
424441
425442
Arguments:
426443
src_path (str): Path of source directory.
427444
dst_path (str): Path to destination directory.
428445
create (bool): If `True`, then ``dst_path`` will be created
429446
if it doesn't exist already (defaults to `False`).
447+
preserve_time (bool): If `True`, try to preserve mtime of the
448+
resource (defaults to `False`).
430449
431450
Raises:
432451
fs.errors.ResourceNotFound: If the ``dst_path``
@@ -440,7 +459,7 @@ def copydir(self, src_path, dst_path, create=False):
440459
raise errors.ResourceNotFound(dst_path)
441460
if not self.getinfo(src_path).is_dir:
442461
raise errors.DirectoryExpected(src_path)
443-
copy.copy_dir(self, src_path, self, dst_path)
462+
copy.copy_dir(self, src_path, self, dst_path, preserve_time=preserve_time)
444463

445464
def create(self, path, wipe=False):
446465
# type: (Text, bool) -> bool
@@ -1027,15 +1046,17 @@ def lock(self):
10271046
"""
10281047
return self._lock
10291048

1030-
def movedir(self, src_path, dst_path, create=False):
1031-
# type: (Text, Text, bool) -> None
1049+
def movedir(self, src_path, dst_path, create=False, preserve_time=False):
1050+
# type: (Text, Text, bool, bool) -> None
10321051
"""Move directory ``src_path`` to ``dst_path``.
10331052
10341053
Arguments:
10351054
src_path (str): Path of source directory on the filesystem.
10361055
dst_path (str): Path to destination directory.
10371056
create (bool): If `True`, then ``dst_path`` will be created
10381057
if it doesn't exist already (defaults to `False`).
1058+
preserve_time (bool): If `True`, try to preserve mtime of the
1059+
resources (defaults to `False`).
10391060
10401061
Raises:
10411062
fs.errors.ResourceNotFound: if ``dst_path`` does not exist,
@@ -1047,7 +1068,7 @@ def movedir(self, src_path, dst_path, create=False):
10471068
with self._lock:
10481069
if not create and not self.exists(dst_path):
10491070
raise errors.ResourceNotFound(dst_path)
1050-
move.move_dir(self, src_path, self, dst_path)
1071+
move.move_dir(self, src_path, self, dst_path, preserve_time=preserve_time)
10511072

10521073
def makedirs(
10531074
self,
@@ -1092,8 +1113,8 @@ def makedirs(
10921113
raise
10931114
return self.opendir(path)
10941115

1095-
def move(self, src_path, dst_path, overwrite=False):
1096-
# type: (Text, Text, bool) -> None
1116+
def move(self, src_path, dst_path, overwrite=False, preserve_time=False):
1117+
# type: (Text, Text, bool, bool) -> None
10971118
"""Move a file from ``src_path`` to ``dst_path``.
10981119
10991120
Arguments:
@@ -1102,6 +1123,8 @@ def move(self, src_path, dst_path, overwrite=False):
11021123
file will be written to.
11031124
overwrite (bool): If `True`, destination path will be
11041125
overwritten if it exists.
1126+
preserve_time (bool): If `True`, try to preserve mtime of the
1127+
resources (defaults to `False`).
11051128
11061129
Raises:
11071130
fs.errors.FileExpected: If ``src_path`` maps to a
@@ -1128,11 +1151,15 @@ def move(self, src_path, dst_path, overwrite=False):
11281151
except OSError:
11291152
pass
11301153
else:
1154+
if preserve_time:
1155+
copy_modified_time(self, src_path, self, dst_path)
11311156
return
11321157
with self._lock:
11331158
with self.open(src_path, "rb") as read_file:
11341159
# FIXME(@althonos): typing complains because open return IO
11351160
self.upload(dst_path, read_file) # type: ignore
1161+
if preserve_time:
1162+
copy_modified_time(self, src_path, self, dst_path)
11361163
self.remove(src_path)
11371164

11381165
def open(

0 commit comments

Comments
 (0)