Skip to content

Commit 1327a63

Browse files
author
atollk
committed
Added preserve_time parameter to all copy/move/mirror functions.
If `preserve_time` is set to True, the call tries to preserve the atime, ctime, and mtime timestamps on the file after the copy. The value is currently not actually used; places in code that have to be adapted are marked with `TODO(preserve_time)`.
1 parent 64d7a52 commit 1327a63

File tree

8 files changed

+153
-38
lines changed

8 files changed

+153
-38
lines changed

fs/_bulk.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,16 @@ def __exit__(
124124
if traceback is None and self.errors:
125125
raise BulkCopyFailed(self.errors)
126126

127-
def copy(self, src_fs, src_path, dst_fs, dst_path):
128-
# type: (FS, Text, FS, Text) -> None
127+
def copy(self, src_fs, src_path, dst_fs, dst_path, preserve_time=False):
128+
# type: (FS, Text, FS, Text, bool) -> None
129129
"""Copy a file from one fs to another."""
130130
if self.queue is None:
131131
# This should be the most performant for a single-thread
132-
copy_file_internal(src_fs, src_path, dst_fs, dst_path)
132+
copy_file_internal(
133+
src_fs, src_path, dst_fs, dst_path, preserve_time=preserve_time
134+
)
133135
else:
136+
# TODO(preserve_time)
134137
src_file = src_fs.openbin(src_path, "r")
135138
try:
136139
dst_file = dst_fs.openbin(dst_path, "w")

fs/base.py

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -387,15 +387,23 @@ def close(self):
387387
"""
388388
self._closed = True
389389

390-
def copy(self, src_path, dst_path, overwrite=False):
391-
# type: (Text, Text, bool) -> None
390+
def copy(
391+
self,
392+
src_path, # type: Text
393+
dst_path, # type: Text
394+
overwrite=False, # type: bool
395+
preserve_time=False, # type: bool
396+
):
397+
# type: (...) -> None
392398
"""Copy file contents from ``src_path`` to ``dst_path``.
393399
394400
Arguments:
395401
src_path (str): Path of source file.
396402
dst_path (str): Path to destination file.
397403
overwrite (bool): If `True`, overwrite the destination file
398404
if it exists (defaults to `False`).
405+
preserve_time (bool): If `True`, try to preserve atime, ctime,
406+
and mtime of the resource (defaults to `False`).
399407
400408
Raises:
401409
fs.errors.DestinationExists: If ``dst_path`` exists,
@@ -404,22 +412,31 @@ def copy(self, src_path, dst_path, overwrite=False):
404412
``dst_path`` does not exist.
405413
406414
"""
415+
# TODO(preserve_time)
407416
with self._lock:
408417
if not overwrite and self.exists(dst_path):
409418
raise errors.DestinationExists(dst_path)
410419
with closing(self.open(src_path, "rb")) as read_file:
411420
# FIXME(@althonos): typing complains because open return IO
412421
self.upload(dst_path, read_file) # type: ignore
413422

414-
def copydir(self, src_path, dst_path, create=False):
415-
# type: (Text, Text, bool) -> None
423+
def copydir(
424+
self,
425+
src_path, # type: Text
426+
dst_path, # type: Text
427+
create=False, # type: bool
428+
preserve_time=False, # type: bool
429+
):
430+
# type: (...) -> None
416431
"""Copy the contents of ``src_path`` to ``dst_path``.
417432
418433
Arguments:
419434
src_path (str): Path of source directory.
420435
dst_path (str): Path to destination directory.
421436
create (bool): If `True`, then ``dst_path`` will be created
422437
if it doesn't exist already (defaults to `False`).
438+
preserve_time (bool): If `True`, try to preserve atime, ctime,
439+
and mtime of the resource (defaults to `False`).
423440
424441
Raises:
425442
fs.errors.ResourceNotFound: If the ``dst_path``
@@ -431,7 +448,7 @@ def copydir(self, src_path, dst_path, create=False):
431448
raise errors.ResourceNotFound(dst_path)
432449
if not self.getinfo(src_path).is_dir:
433450
raise errors.DirectoryExpected(src_path)
434-
copy.copy_dir(self, src_path, self, dst_path)
451+
copy.copy_dir(self, src_path, self, dst_path, preserve_time=preserve_time)
435452

436453
def create(self, path, wipe=False):
437454
# type: (Text, bool) -> bool
@@ -1004,15 +1021,17 @@ def lock(self):
10041021
"""
10051022
return self._lock
10061023

1007-
def movedir(self, src_path, dst_path, create=False):
1008-
# type: (Text, Text, bool) -> None
1024+
def movedir(self, src_path, dst_path, create=False, preserve_time=False):
1025+
# type: (Text, Text, bool, bool) -> None
10091026
"""Move directory ``src_path`` to ``dst_path``.
10101027
10111028
Arguments:
10121029
src_path (str): Path of source directory on the filesystem.
10131030
dst_path (str): Path to destination directory.
10141031
create (bool): If `True`, then ``dst_path`` will be created
10151032
if it doesn't exist already (defaults to `False`).
1033+
preserve_time (bool): If `True`, try to preserve atime, ctime,
1034+
and mtime of the resources (defaults to `False`).
10161035
10171036
Raises:
10181037
fs.errors.ResourceNotFound: if ``dst_path`` does not exist,
@@ -1022,7 +1041,7 @@ def movedir(self, src_path, dst_path, create=False):
10221041
with self._lock:
10231042
if not create and not self.exists(dst_path):
10241043
raise errors.ResourceNotFound(dst_path)
1025-
move.move_dir(self, src_path, self, dst_path)
1044+
move.move_dir(self, src_path, self, dst_path, preserve_time=preserve_time)
10261045

10271046
def makedirs(
10281047
self,

fs/copy.py

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def copy_fs(
2424
walker=None, # type: Optional[Walker]
2525
on_copy=None, # type: Optional[_OnCopy]
2626
workers=0, # type: int
27+
preserve_time=False, # type: bool
2728
):
2829
# type: (...) -> None
2930
"""Copy the contents of one filesystem to another.
@@ -39,6 +40,8 @@ def copy_fs(
3940
dst_path)``.
4041
workers (int): Use `worker` threads to copy data, or ``0`` (default) for
4142
a single-threaded copy.
43+
preserve_time (bool): If `True`, try to preserve atime, ctime,
44+
and mtime of the resources (defaults to `False`).
4245
4346
"""
4447
return copy_dir(
@@ -52,6 +55,7 @@ def copy_fs_if_newer(
5255
walker=None, # type: Optional[Walker]
5356
on_copy=None, # type: Optional[_OnCopy]
5457
workers=0, # type: int
58+
preserve_time=False, # type: bool
5559
):
5660
# type: (...) -> None
5761
"""Copy the contents of one filesystem to another, checking times.
@@ -72,10 +76,19 @@ def copy_fs_if_newer(
7276
dst_path)``.
7377
workers (int): Use ``worker`` threads to copy data, or ``0`` (default) for
7478
a single-threaded copy.
79+
preserve_time (bool): If `True`, try to preserve atime, ctime,
80+
and mtime of the resources (defaults to `False`).
7581
7682
"""
7783
return copy_dir_if_newer(
78-
src_fs, "/", dst_fs, "/", walker=walker, on_copy=on_copy, workers=workers
84+
src_fs,
85+
"/",
86+
dst_fs,
87+
"/",
88+
walker=walker,
89+
on_copy=on_copy,
90+
workers=workers,
91+
preserve_time=preserve_time,
7992
)
8093

8194

@@ -113,6 +126,7 @@ def copy_file(
113126
src_path, # type: Text
114127
dst_fs, # type: Union[FS, Text]
115128
dst_path, # type: Text
129+
preserve_time=False, # type: bool
116130
):
117131
# type: (...) -> None
118132
"""Copy a file from one filesystem to another.
@@ -124,15 +138,20 @@ def copy_file(
124138
src_path (str): Path to a file on the source filesystem.
125139
dst_fs (FS or str): Destination filesystem (instance or URL).
126140
dst_path (str): Path to a file on the destination filesystem.
141+
preserve_time (bool): If `True`, try to preserve atime, ctime,
142+
and mtime of the resource (defaults to `False`).
127143
128144
"""
129145
with manage_fs(src_fs, writeable=False) as _src_fs:
130146
with manage_fs(dst_fs, create=True) as _dst_fs:
131147
if _src_fs is _dst_fs:
132148
# Same filesystem, so we can do a potentially optimized
133149
# copy
134-
_src_fs.copy(src_path, dst_path, overwrite=True)
150+
_src_fs.copy(
151+
src_path, dst_path, overwrite=True, preserve_time=preserve_time
152+
)
135153
else:
154+
# TODO(preserve_time)
136155
# Standard copy
137156
with _src_fs.lock(), _dst_fs.lock():
138157
if _dst_fs.hassyspath(dst_path):
@@ -148,6 +167,7 @@ def copy_file_internal(
148167
src_path, # type: Text
149168
dst_fs, # type: FS
150169
dst_path, # type: Text
170+
preserve_time=False, # type: bool
151171
):
152172
# type: (...) -> None
153173
"""Copy a file at low level, without calling `manage_fs` or locking.
@@ -162,12 +182,15 @@ def copy_file_internal(
162182
src_path (str): Path to a file on the source filesystem.
163183
dst_fs (FS): Destination filesystem.
164184
dst_path (str): Path to a file on the destination filesystem.
185+
preserve_time (bool): If `True`, try to preserve atime, ctime,
186+
and mtime of the resource (defaults to `False`).
165187
166188
"""
167189
if src_fs is dst_fs:
168190
# Same filesystem, so we can do a potentially optimized
169191
# copy
170-
src_fs.copy(src_path, dst_path, overwrite=True)
192+
src_fs.copy(src_path, dst_path, overwrite=True, preserve_time=preserve_time)
193+
# TODO(preserve_time)
171194
elif dst_fs.hassyspath(dst_path):
172195
with dst_fs.openbin(dst_path, "w") as write_file:
173196
src_fs.download(src_path, write_file)
@@ -181,6 +204,7 @@ def copy_file_if_newer(
181204
src_path, # type: Text
182205
dst_fs, # type: Union[FS, Text]
183206
dst_path, # type: Text
207+
preserve_time=False, # type: bool
184208
):
185209
# type: (...) -> bool
186210
"""Copy a file from one filesystem to another, checking times.
@@ -196,6 +220,8 @@ def copy_file_if_newer(
196220
src_path (str): Path to a file on the source filesystem.
197221
dst_fs (FS or str): Destination filesystem (instance or URL).
198222
dst_path (str): Path to a file on the destination filesystem.
223+
preserve_time (bool): If `True`, try to preserve atime, ctime,
224+
and mtime of the resource (defaults to `False`).
199225
200226
Returns:
201227
bool: `True` if the file copy was executed, `False` otherwise.
@@ -207,15 +233,23 @@ def copy_file_if_newer(
207233
# Same filesystem, so we can do a potentially optimized
208234
# copy
209235
if _source_is_newer(_src_fs, src_path, _dst_fs, dst_path):
210-
_src_fs.copy(src_path, dst_path, overwrite=True)
236+
_src_fs.copy(
237+
src_path, dst_path, overwrite=True, preserve_time=preserve_time
238+
)
211239
return True
212240
else:
213241
return False
214242
else:
215243
# Standard copy
216244
with _src_fs.lock(), _dst_fs.lock():
217245
if _source_is_newer(_src_fs, src_path, _dst_fs, dst_path):
218-
copy_file_internal(_src_fs, src_path, _dst_fs, dst_path)
246+
copy_file_internal(
247+
_src_fs,
248+
src_path,
249+
_dst_fs,
250+
dst_path,
251+
preserve_time=preserve_time,
252+
)
219253
return True
220254
else:
221255
return False
@@ -225,6 +259,7 @@ def copy_structure(
225259
src_fs, # type: Union[FS, Text]
226260
dst_fs, # type: Union[FS, Text]
227261
walker=None, # type: Optional[Walker]
262+
preserve_time=False, # type: bool
228263
):
229264
# type: (...) -> None
230265
"""Copy directories (but not files) from ``src_fs`` to ``dst_fs``.
@@ -235,6 +270,8 @@ def copy_structure(
235270
walker (~fs.walk.Walker, optional): A walker object that will be
236271
used to scan for files in ``src_fs``. Set this if you only
237272
want to consider a sub-set of the resources in ``src_fs``.
273+
preserve_time (bool): If `True`, try to preserve atime, ctime,
274+
and mtime of the resource (defaults to `False`).
238275
239276
"""
240277
walker = walker or Walker()
@@ -253,6 +290,7 @@ def copy_dir(
253290
walker=None, # type: Optional[Walker]
254291
on_copy=None, # type: Optional[_OnCopy]
255292
workers=0, # type: int
293+
preserve_time=False, # type: bool
256294
):
257295
# type: (...) -> None
258296
"""Copy a directory from one filesystem to another.
@@ -270,6 +308,8 @@ def copy_dir(
270308
``(src_fs, src_path, dst_fs, dst_path)``.
271309
workers (int): Use ``worker`` threads to copy data, or ``0`` (default) for
272310
a single-threaded copy.
311+
preserve_time (bool): If `True`, try to preserve atime, ctime,
312+
and mtime of the resources (defaults to `False`).
273313
274314
"""
275315
on_copy = on_copy or (lambda *args: None)
@@ -297,7 +337,13 @@ def dst():
297337
for info in files:
298338
src_path = info.make_path(dir_path)
299339
dst_path = info.make_path(copy_path)
300-
copier.copy(_src_fs, src_path, _dst_fs, dst_path)
340+
copier.copy(
341+
_src_fs,
342+
src_path,
343+
_dst_fs,
344+
dst_path,
345+
preserve_time=preserve_time,
346+
)
301347
on_copy(_src_fs, src_path, _dst_fs, dst_path)
302348

303349

@@ -309,6 +355,7 @@ def copy_dir_if_newer(
309355
walker=None, # type: Optional[Walker]
310356
on_copy=None, # type: Optional[_OnCopy]
311357
workers=0, # type: int
358+
preserve_time=False, # type: bool
312359
):
313360
# type: (...) -> None
314361
"""Copy a directory from one filesystem to another, checking times.
@@ -331,6 +378,8 @@ def copy_dir_if_newer(
331378
``(src_fs, src_path, dst_fs, dst_path)``.
332379
workers (int): Use ``worker`` threads to copy data, or ``0`` (default) for
333380
a single-threaded copy.
381+
preserve_time (bool): If `True`, try to preserve atime, ctime,
382+
and mtime of the resources (defaults to `False`).
334383
335384
"""
336385
on_copy = on_copy or (lambda *args: None)
@@ -381,5 +430,11 @@ def dst():
381430
)
382431

383432
if do_copy:
384-
copier.copy(_src_fs, dir_path, _dst_fs, copy_path)
433+
copier.copy(
434+
_src_fs,
435+
dir_path,
436+
_dst_fs,
437+
copy_path,
438+
preserve_time=preserve_time,
439+
)
385440
on_copy(_src_fs, dir_path, _dst_fs, copy_path)

fs/mirror.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def mirror(
5757
walker=None, # type: Optional[Walker]
5858
copy_if_newer=True, # type: bool
5959
workers=0, # type: int
60+
preserve_time=False, # type: bool
6061
):
6162
# type: (...) -> None
6263
"""Mirror files / directories from one filesystem to another.
@@ -73,6 +74,8 @@ def mirror(
7374
workers (int): Number of worker threads used
7475
(0 for single threaded). Set to a relatively low number
7576
for network filesystems, 4 would be a good start.
77+
preserve_time (bool): If `True`, try to preserve atime, ctime,
78+
and mtime of the resources (defaults to `False`).
7679
7780
"""
7881

@@ -92,13 +95,19 @@ def dst():
9295
walker=walker,
9396
copy_if_newer=copy_if_newer,
9497
copy_file=copier.copy,
98+
preserve_time=preserve_time,
9599
)
96100

97101

98102
def _mirror(
99-
src_fs, dst_fs, walker=None, copy_if_newer=True, copy_file=copy_file_internal
103+
src_fs, # type: FS
104+
dst_fs, # type: FS
105+
walker=None, # type: Optional[Walker]
106+
copy_if_newer=True, # type: bool
107+
copy_file=copy_file_internal, # type: Callable[[FS, str, FS, str, bool], None]
108+
preserve_time=False, # type: bool
100109
):
101-
# type: (FS, FS, Optional[Walker], bool, Callable[[FS, str, FS, str], None]) -> None
110+
# type: (...) -> None
102111
walker = walker or Walker()
103112
walk = walker.walk(src_fs, namespaces=["details"])
104113
for path, dirs, files in walk:
@@ -122,7 +131,7 @@ def _mirror(
122131
# Compare file info
123132
if copy_if_newer and not _compare(_file, dst_file):
124133
continue
125-
copy_file(src_fs, _path, dst_fs, _path)
134+
copy_file(src_fs, _path, dst_fs, _path, preserve_time)
126135

127136
# Make directories
128137
for _dir in dirs:

0 commit comments

Comments
 (0)