feat: add upload/download progress reporting in ssh.py

Files <1MB show size+time in OK line. Files >=1MB show 25/50/75% milestones with transferred/total, plus speed in final OK line.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chrome-storm-c442
2026-02-24 07:38:40 -05:00
parent e7634bf397
commit 7d29f8446a

View File

@@ -188,29 +188,73 @@ def _normalize_remote_path(remote_path: str) -> str:
return remote_path
def _fmt_size(nbytes: int) -> str:
"""Format byte count for human display."""
if nbytes < 1024:
return f"{nbytes} B"
elif nbytes < 1024 * 1024:
return f"{nbytes / 1024:.1f} KB"
elif nbytes < 1024 * 1024 * 1024:
return f"{nbytes / (1024 * 1024):.1f} MB"
else:
return f"{nbytes / (1024 * 1024 * 1024):.2f} GB"
def _progress_cb(total_bytes: int):
"""Return a Paramiko-compatible progress callback.
For files >= 1 MB, prints at 25%, 50%, 75% milestones.
For files < 1 MB, stays silent."""
threshold = 1024 * 1024 # 1 MB
reported = set()
def callback(transferred: int, total: int):
if total < threshold:
return
pct = int(transferred * 100 / total)
for milestone in (25, 50, 75):
if pct >= milestone and milestone not in reported:
reported.add(milestone)
print(f"{milestone}% ({_fmt_size(transferred)}/{_fmt_size(total)})")
return callback
def upload_file(server: dict, local_path: str, remote_path: str):
# Normalize the remote path to handle MSYS conversion issues
normalized_remote_path = _normalize_remote_path(remote_path)
file_size = os.path.getsize(local_path)
client = get_client(server)
try:
sftp = client.open_sftp()
sftp.put(local_path, normalized_remote_path)
t0 = time.time()
sftp.put(local_path, normalized_remote_path, callback=_progress_cb(file_size))
elapsed = time.time() - t0
sftp.chmod(normalized_remote_path, 0o664)
sftp.close()
print(f"OK: {local_path} -> {server['alias']}:{normalized_remote_path}")
info = f"{_fmt_size(file_size)}, {elapsed:.1f}s"
if file_size >= 1024 * 1024 and elapsed > 0:
speed = file_size / elapsed
info += f", {_fmt_size(int(speed))}/s"
print(f"OK: {local_path} -> {server['alias']}:{normalized_remote_path} ({info})")
finally:
client.close()
def download_file(server: dict, remote_path: str, local_path: str):
# Normalize the remote path to handle MSYS conversion issues
normalized_remote_path = _normalize_remote_path(remote_path)
client = get_client(server)
try:
sftp = client.open_sftp()
sftp.get(normalized_remote_path, local_path)
file_size = sftp.stat(normalized_remote_path).st_size
t0 = time.time()
sftp.get(normalized_remote_path, local_path, callback=_progress_cb(file_size))
elapsed = time.time() - t0
sftp.close()
print(f"OK: {server['alias']}:{normalized_remote_path} -> {local_path}")
info = f"{_fmt_size(file_size)}, {elapsed:.1f}s"
if file_size >= 1024 * 1024 and elapsed > 0:
speed = file_size / elapsed
info += f", {_fmt_size(int(speed))}/s"
print(f"OK: {server['alias']}:{normalized_remote_path} -> {local_path} ({info})")
finally:
client.close()