Use voussoirkit.progressbars in spinal.
This commit is contained in:
parent
30a0b49097
commit
ea7f7d5843
1 changed files with 58 additions and 78 deletions
|
@ -6,14 +6,13 @@ import collections
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from voussoirkit import bytestring
|
from voussoirkit import bytestring
|
||||||
from voussoirkit import dotdict
|
from voussoirkit import dotdict
|
||||||
from voussoirkit import pathclass
|
from voussoirkit import pathclass
|
||||||
|
from voussoirkit import progressbars
|
||||||
from voussoirkit import ratelimiter
|
from voussoirkit import ratelimiter
|
||||||
from voussoirkit import safeprint
|
|
||||||
from voussoirkit import sentinel
|
from voussoirkit import sentinel
|
||||||
from voussoirkit import vlogging
|
from voussoirkit import vlogging
|
||||||
from voussoirkit import winglob
|
from voussoirkit import winglob
|
||||||
|
@ -60,42 +59,22 @@ class SourceNotFile(SpinalException):
|
||||||
class SpinalError(SpinalException):
|
class SpinalError(SpinalException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def callback_progress_v1(path, written_bytes, total_bytes):
|
|
||||||
'''
|
|
||||||
Example of a copy callback function.
|
|
||||||
|
|
||||||
Prints "filename written/total (percent%)"
|
|
||||||
'''
|
|
||||||
if written_bytes >= total_bytes:
|
|
||||||
ends = '\r\n'
|
|
||||||
else:
|
|
||||||
ends = ''
|
|
||||||
percent = (100 * written_bytes) / max(total_bytes, 1)
|
|
||||||
percent = f'{percent:07.3f}'
|
|
||||||
written = '{:,}'.format(written_bytes)
|
|
||||||
total = '{:,}'.format(total_bytes)
|
|
||||||
written = written.rjust(len(total), ' ')
|
|
||||||
status = f'{path.absolute_path} {written}/{total} ({percent}%)\r'
|
|
||||||
safeprint.safeprint(status, end=ends)
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
def copy_directory(
|
def copy_directory(
|
||||||
source,
|
source,
|
||||||
destination=None,
|
destination=None,
|
||||||
*,
|
*,
|
||||||
bytes_per_second=None,
|
bytes_per_second=None,
|
||||||
callback_directory_progress=None,
|
|
||||||
callback_file_progress=None,
|
|
||||||
callback_permission_denied=None,
|
callback_permission_denied=None,
|
||||||
callback_post_file=None,
|
callback_post_file=None,
|
||||||
callback_pre_directory=None,
|
callback_pre_directory=None,
|
||||||
callback_pre_file=None,
|
callback_pre_file=None,
|
||||||
callback_verify_hash_progress=None,
|
|
||||||
chunk_size='dynamic',
|
chunk_size='dynamic',
|
||||||
destination_new_root=None,
|
destination_new_root=None,
|
||||||
|
directory_progressbar=None,
|
||||||
dry_run=False,
|
dry_run=False,
|
||||||
exclude_directories=None,
|
exclude_directories=None,
|
||||||
exclude_filenames=None,
|
exclude_filenames=None,
|
||||||
|
file_progressbar=None,
|
||||||
files_per_second=None,
|
files_per_second=None,
|
||||||
hash_class=None,
|
hash_class=None,
|
||||||
overwrite=OVERWRITE_OLD,
|
overwrite=OVERWRITE_OLD,
|
||||||
|
@ -103,6 +82,7 @@ def copy_directory(
|
||||||
skip_symlinks=True,
|
skip_symlinks=True,
|
||||||
stop_event=None,
|
stop_event=None,
|
||||||
verify_hash=False,
|
verify_hash=False,
|
||||||
|
verify_hash_progressbar=None,
|
||||||
):
|
):
|
||||||
'''
|
'''
|
||||||
Copy all of the contents from source to destination,
|
Copy all of the contents from source to destination,
|
||||||
|
@ -118,16 +98,6 @@ def copy_directory(
|
||||||
bytes_per_second:
|
bytes_per_second:
|
||||||
Passed into each `copy_file` as `bytes_per_second`.
|
Passed into each `copy_file` as `bytes_per_second`.
|
||||||
|
|
||||||
callback_directory_progress:
|
|
||||||
This function will be called after each file copy with three arguments:
|
|
||||||
name of file copied, number of bytes written to destination directory
|
|
||||||
so far, total bytes needed (based on precalcsize).
|
|
||||||
If `precalcsize` is False, this function will receive written bytes
|
|
||||||
for both written and total, showing 100% always.
|
|
||||||
|
|
||||||
callback_file_progress:
|
|
||||||
Passed into each `copy_file` as `callback_progress`.
|
|
||||||
|
|
||||||
callback_permission_denied:
|
callback_permission_denied:
|
||||||
Passed into `walk` and each `copy_file` as `callback_permission_denied`.
|
Passed into `walk` and each `copy_file` as `callback_permission_denied`.
|
||||||
|
|
||||||
|
@ -150,9 +120,6 @@ def copy_directory(
|
||||||
If you think copy_dir should be rewritten as a generator instead,
|
If you think copy_dir should be rewritten as a generator instead,
|
||||||
I agree!
|
I agree!
|
||||||
|
|
||||||
callback_verify_hash_progress:
|
|
||||||
Passed into each `copy_file` as `callback_verify_hash_progress`.
|
|
||||||
|
|
||||||
chunk_size:
|
chunk_size:
|
||||||
Passed into each `copy_file` as `chunk_size`.
|
Passed into each `copy_file` as `chunk_size`.
|
||||||
|
|
||||||
|
@ -163,6 +130,13 @@ def copy_directory(
|
||||||
|
|
||||||
`destination` and `destination_new_root` are mutually exclusive.
|
`destination` and `destination_new_root` are mutually exclusive.
|
||||||
|
|
||||||
|
directory_progressbar:
|
||||||
|
An instance of voussoirkit.progressbars.ProgressBar.
|
||||||
|
This progressbar will be updated after each file copy with the number of
|
||||||
|
bytes written into the destination directory so far. If `precalcsize` is
|
||||||
|
True, the progressbar will have its total set to that value. Otherwise
|
||||||
|
it will be indeterminate.
|
||||||
|
|
||||||
dry_run:
|
dry_run:
|
||||||
Do everything except the actual file copying.
|
Do everything except the actual file copying.
|
||||||
|
|
||||||
|
@ -176,6 +150,9 @@ def copy_directory(
|
||||||
Maximum number of files to be processed per second. Helps to keep CPU
|
Maximum number of files to be processed per second. Helps to keep CPU
|
||||||
usage low.
|
usage low.
|
||||||
|
|
||||||
|
file_progressbar:
|
||||||
|
Passed into each `copy_file` as `progressbar`.
|
||||||
|
|
||||||
hash_class:
|
hash_class:
|
||||||
Passed into each `copy_file` as `hash_class`.
|
Passed into each `copy_file` as `hash_class`.
|
||||||
|
|
||||||
|
@ -184,9 +161,7 @@ def copy_directory(
|
||||||
|
|
||||||
precalcsize:
|
precalcsize:
|
||||||
If True, calculate the size of source before beginning the copy.
|
If True, calculate the size of source before beginning the copy.
|
||||||
This number can be used in the callback_directory_progress function.
|
This number can be used in the directory_progressbar.
|
||||||
Else, callback_directory_progress will receive written bytes as total
|
|
||||||
bytes (showing 100% always).
|
|
||||||
This may take a while if the source directory is large.
|
This may take a while if the source directory is large.
|
||||||
|
|
||||||
skip_symlinks:
|
skip_symlinks:
|
||||||
|
@ -201,6 +176,9 @@ def copy_directory(
|
||||||
verify_hash:
|
verify_hash:
|
||||||
Passed into each `copy_file` as `verify_hash`.
|
Passed into each `copy_file` as `verify_hash`.
|
||||||
|
|
||||||
|
verify_hash_progressbar:
|
||||||
|
Passed into each `copy_file` as `verify_hash_progressbar`.
|
||||||
|
|
||||||
Returns a dotdict containing at least these values:
|
Returns a dotdict containing at least these values:
|
||||||
`source` pathclass.Path
|
`source` pathclass.Path
|
||||||
`destination` pathclass.Path
|
`destination` pathclass.Path
|
||||||
|
@ -236,7 +214,11 @@ def copy_directory(
|
||||||
else:
|
else:
|
||||||
total_bytes = 0
|
total_bytes = 0
|
||||||
|
|
||||||
callback_directory_progress = callback_directory_progress or do_nothing
|
directory_progressbar = progressbars.normalize(
|
||||||
|
directory_progressbar,
|
||||||
|
topic=destination.absolute_path,
|
||||||
|
total=total_bytes if precalcsize else None,
|
||||||
|
)
|
||||||
callback_pre_directory = callback_pre_directory or do_nothing
|
callback_pre_directory = callback_pre_directory or do_nothing
|
||||||
callback_pre_file = callback_pre_file or do_nothing
|
callback_pre_file = callback_pre_file or do_nothing
|
||||||
callback_post_file = callback_post_file or do_nothing
|
callback_post_file = callback_post_file or do_nothing
|
||||||
|
@ -311,29 +293,27 @@ def copy_directory(
|
||||||
bytes_per_second=bytes_per_second,
|
bytes_per_second=bytes_per_second,
|
||||||
callback_permission_denied=callback_permission_denied,
|
callback_permission_denied=callback_permission_denied,
|
||||||
callback_pre_copy=callback_pre_file,
|
callback_pre_copy=callback_pre_file,
|
||||||
callback_progress=callback_file_progress,
|
|
||||||
callback_verify_hash_progress=callback_verify_hash_progress,
|
|
||||||
chunk_size=chunk_size,
|
chunk_size=chunk_size,
|
||||||
dry_run=dry_run,
|
dry_run=dry_run,
|
||||||
hash_class=hash_class,
|
hash_class=hash_class,
|
||||||
overwrite=overwrite,
|
overwrite=overwrite,
|
||||||
|
progressbar=file_progressbar,
|
||||||
verify_hash=verify_hash,
|
verify_hash=verify_hash,
|
||||||
|
verify_hash_progressbar=verify_hash_progressbar,
|
||||||
)
|
)
|
||||||
|
|
||||||
if copied.written:
|
if copied.written:
|
||||||
written_files += 1
|
written_files += 1
|
||||||
written_bytes += copied.written_bytes
|
written_bytes += copied.written_bytes
|
||||||
|
|
||||||
if precalcsize is False:
|
directory_progressbar.step(written_bytes)
|
||||||
callback_directory_progress(copied.destination, written_bytes, written_bytes)
|
|
||||||
else:
|
|
||||||
callback_directory_progress(copied.destination, written_bytes, total_bytes)
|
|
||||||
|
|
||||||
callback_post_file(copied)
|
callback_post_file(copied)
|
||||||
|
|
||||||
if files_per_second is not None:
|
if files_per_second is not None:
|
||||||
files_per_second.limit(1)
|
files_per_second.limit(1)
|
||||||
|
|
||||||
|
directory_progressbar.done()
|
||||||
|
|
||||||
results = dotdict.DotDict(
|
results = dotdict.DotDict(
|
||||||
source=source,
|
source=source,
|
||||||
destination=destination,
|
destination=destination,
|
||||||
|
@ -350,17 +330,17 @@ def copy_file(
|
||||||
source,
|
source,
|
||||||
destination=None,
|
destination=None,
|
||||||
*,
|
*,
|
||||||
destination_new_root=None,
|
|
||||||
bytes_per_second=None,
|
bytes_per_second=None,
|
||||||
callback_verify_hash_progress=None,
|
|
||||||
callback_progress=None,
|
|
||||||
callback_permission_denied=None,
|
callback_permission_denied=None,
|
||||||
callback_pre_copy=None,
|
callback_pre_copy=None,
|
||||||
chunk_size='dynamic',
|
chunk_size='dynamic',
|
||||||
|
destination_new_root=None,
|
||||||
dry_run=False,
|
dry_run=False,
|
||||||
hash_class=None,
|
hash_class=None,
|
||||||
overwrite=OVERWRITE_OLD,
|
overwrite=OVERWRITE_OLD,
|
||||||
|
progressbar=None,
|
||||||
verify_hash=False,
|
verify_hash=False,
|
||||||
|
verify_hash_progressbar=None,
|
||||||
):
|
):
|
||||||
'''
|
'''
|
||||||
Copy a file from one place to another.
|
Copy a file from one place to another.
|
||||||
|
@ -396,15 +376,6 @@ def copy_file(
|
||||||
This function may return the BAIL sentinel (return spinal.BAIL) and
|
This function may return the BAIL sentinel (return spinal.BAIL) and
|
||||||
that file will not be copied.
|
that file will not be copied.
|
||||||
|
|
||||||
callback_progress:
|
|
||||||
If provided, this function will be called after writing
|
|
||||||
each chunk_size bytes to destination with three parameters:
|
|
||||||
the Path object being copied, number of bytes written so far,
|
|
||||||
total number of bytes needed.
|
|
||||||
|
|
||||||
callback_verify_hash_progress:
|
|
||||||
Passed into `hash_file` as callback_progress when verifying the hash.
|
|
||||||
|
|
||||||
chunk_size:
|
chunk_size:
|
||||||
An integer number of bytes to read and write at a time.
|
An integer number of bytes to read and write at a time.
|
||||||
Or, the string 'dynamic' to enable dynamic chunk sizing that aims to
|
Or, the string 'dynamic' to enable dynamic chunk sizing that aims to
|
||||||
|
@ -429,11 +400,17 @@ def copy_file(
|
||||||
If any other value, the file will not be overwritten. False or None
|
If any other value, the file will not be overwritten. False or None
|
||||||
would be good values to pass.
|
would be good values to pass.
|
||||||
|
|
||||||
|
progressbar:
|
||||||
|
An instance of voussoirkit.progressbars.ProgressBar.
|
||||||
|
|
||||||
verify_hash:
|
verify_hash:
|
||||||
If True, the copied file will be read back after the copy is complete,
|
If True, the copied file will be read back after the copy is complete,
|
||||||
and its hash will be compared against the hash of the source file.
|
and its hash will be compared against the hash of the source file.
|
||||||
If hash_class is None, then the global HASH_CLASS is used.
|
If hash_class is None, then the global HASH_CLASS is used.
|
||||||
|
|
||||||
|
verify_hash_progressbar:
|
||||||
|
Passed into `hash_file` as `progressbar` when verifying the hash.
|
||||||
|
|
||||||
Returns a dotdict containing at least these values:
|
Returns a dotdict containing at least these values:
|
||||||
`source` pathclass.Path
|
`source` pathclass.Path
|
||||||
`destination` pathclass.Path
|
`destination` pathclass.Path
|
||||||
|
@ -448,6 +425,7 @@ def copy_file(
|
||||||
|
|
||||||
source = pathclass.Path(source)
|
source = pathclass.Path(source)
|
||||||
source.correct_case()
|
source.correct_case()
|
||||||
|
source_bytes = source.size
|
||||||
|
|
||||||
if not source.is_file:
|
if not source.is_file:
|
||||||
raise SourceNotFile(source)
|
raise SourceNotFile(source)
|
||||||
|
@ -456,7 +434,11 @@ def copy_file(
|
||||||
destination = new_root(source, destination_new_root)
|
destination = new_root(source, destination_new_root)
|
||||||
destination = pathclass.Path(destination)
|
destination = pathclass.Path(destination)
|
||||||
|
|
||||||
callback_progress = callback_progress or do_nothing
|
progressbar = progressbars.normalize(
|
||||||
|
progressbar,
|
||||||
|
topic=destination.absolute_path,
|
||||||
|
total=0 if dry_run else source_bytes,
|
||||||
|
)
|
||||||
callback_pre_copy = callback_pre_copy or do_nothing
|
callback_pre_copy = callback_pre_copy or do_nothing
|
||||||
|
|
||||||
if destination.is_dir:
|
if destination.is_dir:
|
||||||
|
@ -484,12 +466,9 @@ def copy_file(
|
||||||
return results
|
return results
|
||||||
|
|
||||||
if dry_run:
|
if dry_run:
|
||||||
if callback_progress is not None:
|
progressbar.done()
|
||||||
callback_progress(destination, 0, 0)
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
source_bytes = source.size
|
|
||||||
|
|
||||||
if callback_pre_copy(source, destination, dry_run=dry_run) is BAIL:
|
if callback_pre_copy(source, destination, dry_run=dry_run) is BAIL:
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
@ -551,7 +530,7 @@ def copy_file(
|
||||||
destination_handle.write(data_chunk)
|
destination_handle.write(data_chunk)
|
||||||
results.written_bytes += data_bytes
|
results.written_bytes += data_bytes
|
||||||
|
|
||||||
callback_progress(destination, results.written_bytes, source_bytes)
|
progressbar.step(results.written_bytes)
|
||||||
|
|
||||||
if bytes_per_second is not None:
|
if bytes_per_second is not None:
|
||||||
bytes_per_second.limit(data_bytes)
|
bytes_per_second.limit(data_bytes)
|
||||||
|
@ -560,9 +539,7 @@ def copy_file(
|
||||||
chunk_time = time.perf_counter() - chunk_start
|
chunk_time = time.perf_counter() - chunk_start
|
||||||
chunk_size = dynamic_chunk_sizer(chunk_size, chunk_time, IDEAL_CHUNK_TIME)
|
chunk_size = dynamic_chunk_sizer(chunk_size, chunk_time, IDEAL_CHUNK_TIME)
|
||||||
|
|
||||||
if results.written_bytes == 0:
|
progressbar.done()
|
||||||
# For zero-length files, we want to get at least one call in there.
|
|
||||||
callback_progress(destination, results.written_bytes, source_bytes)
|
|
||||||
|
|
||||||
# Fin
|
# Fin
|
||||||
log.loud('Closing source handle.')
|
log.loud('Closing source handle.')
|
||||||
|
@ -576,7 +553,7 @@ def copy_file(
|
||||||
if verify_hash:
|
if verify_hash:
|
||||||
file_hash = _verify_hash(
|
file_hash = _verify_hash(
|
||||||
destination,
|
destination,
|
||||||
callback_progress=callback_verify_hash_progress,
|
progressbar=verify_hash_progressbar,
|
||||||
hash_class=hash_class,
|
hash_class=hash_class,
|
||||||
known_hash=results.hash.hexdigest(),
|
known_hash=results.hash.hexdigest(),
|
||||||
known_size=source_bytes,
|
known_size=source_bytes,
|
||||||
|
@ -631,8 +608,8 @@ def hash_file(
|
||||||
hash_class,
|
hash_class,
|
||||||
*,
|
*,
|
||||||
bytes_per_second=None,
|
bytes_per_second=None,
|
||||||
callback_progress=None,
|
|
||||||
chunk_size='dynamic',
|
chunk_size='dynamic',
|
||||||
|
progressbar=None,
|
||||||
):
|
):
|
||||||
'''
|
'''
|
||||||
hash_class:
|
hash_class:
|
||||||
|
@ -643,24 +620,26 @@ def hash_file(
|
||||||
an existing Ratelimiter object, or a string parseable by bytestring.
|
an existing Ratelimiter object, or a string parseable by bytestring.
|
||||||
The bytestring BYTE, KIBIBYTE, etc constants may help.
|
The bytestring BYTE, KIBIBYTE, etc constants may help.
|
||||||
|
|
||||||
callback_progress:
|
|
||||||
A function that takes three parameters:
|
|
||||||
path object, bytes ingested so far, bytes total
|
|
||||||
|
|
||||||
chunk_size:
|
chunk_size:
|
||||||
An integer number of bytes to read at a time.
|
An integer number of bytes to read at a time.
|
||||||
Or, the string 'dynamic' to enable dynamic chunk sizing that aims to
|
Or, the string 'dynamic' to enable dynamic chunk sizing that aims to
|
||||||
keep a consistent pace of progress bar updates.
|
keep a consistent pace of progress bar updates.
|
||||||
|
|
||||||
|
progressbar:
|
||||||
|
An instance from voussoirkit.progressbars.
|
||||||
'''
|
'''
|
||||||
path = pathclass.Path(path)
|
path = pathclass.Path(path)
|
||||||
path.assert_is_file()
|
path.assert_is_file()
|
||||||
hasher = hash_class()
|
hasher = hash_class()
|
||||||
|
|
||||||
bytes_per_second = limiter_or_none(bytes_per_second)
|
bytes_per_second = limiter_or_none(bytes_per_second)
|
||||||
callback_progress = callback_progress or do_nothing
|
progressbar = progressbars.normalize(
|
||||||
|
progressbar,
|
||||||
|
topic=path.absolute_path,
|
||||||
|
total=path.size,
|
||||||
|
)
|
||||||
|
|
||||||
checked_bytes = 0
|
checked_bytes = 0
|
||||||
file_size = path.size
|
|
||||||
|
|
||||||
handle = path.open('rb')
|
handle = path.open('rb')
|
||||||
|
|
||||||
|
@ -680,7 +659,7 @@ def hash_file(
|
||||||
hasher.update(chunk)
|
hasher.update(chunk)
|
||||||
|
|
||||||
checked_bytes += this_size
|
checked_bytes += this_size
|
||||||
callback_progress(path, checked_bytes, file_size)
|
progressbar.step(checked_bytes)
|
||||||
|
|
||||||
if bytes_per_second is not None:
|
if bytes_per_second is not None:
|
||||||
bytes_per_second.limit(this_size)
|
bytes_per_second.limit(this_size)
|
||||||
|
@ -689,6 +668,7 @@ def hash_file(
|
||||||
chunk_time = time.perf_counter() - chunk_start
|
chunk_time = time.perf_counter() - chunk_start
|
||||||
chunk_size = dynamic_chunk_sizer(chunk_size, chunk_time, IDEAL_CHUNK_TIME)
|
chunk_size = dynamic_chunk_sizer(chunk_size, chunk_time, IDEAL_CHUNK_TIME)
|
||||||
|
|
||||||
|
progressbar.done()
|
||||||
return hasher
|
return hasher
|
||||||
|
|
||||||
def is_xor(*args):
|
def is_xor(*args):
|
||||||
|
|
Loading…
Reference in a new issue