63 lines
2.0 KiB
Python
63 lines
2.0 KiB
Python
import os
|
|
import posixpath
|
|
import time
|
|
|
|
|
|
def _resolve_local_target(remote_path: str, target: str | None) -> str:
|
|
if target:
|
|
return target
|
|
|
|
basename = posixpath.basename(remote_path.rstrip("/"))
|
|
if not basename:
|
|
raise ValueError("Kann keinen lokalen Dateinamen aus dem Remote-Pfad ableiten. Bitte Zielpfad angeben.")
|
|
return basename
|
|
|
|
|
|
def execute(conn, source: str, target: str | None = None):
|
|
local_path = _resolve_local_target(source, target)
|
|
|
|
os.makedirs(os.path.dirname(local_path) or ".", exist_ok=True)
|
|
|
|
last_print = 0.0
|
|
start_time = time.monotonic()
|
|
|
|
def _progress(_chunk_len: int, received: int, expected: int | None):
|
|
nonlocal last_print
|
|
now = time.monotonic()
|
|
if now - last_print < 0.2:
|
|
return
|
|
last_print = now
|
|
|
|
elapsed = max(now - start_time, 1e-6)
|
|
speed_kb_s = (received / 1024.0) / elapsed
|
|
|
|
if expected is not None and expected > 0:
|
|
percent = (received * 100.0) / expected
|
|
remaining = max(expected - received, 0)
|
|
eta_sec = (remaining / 1024.0) / speed_kb_s if speed_kb_s > 0 else 0.0
|
|
eta_str = f"{int(eta_sec // 60):02d}:{int(eta_sec % 60):02d}"
|
|
print(
|
|
f"\r⬇️ {received}/{expected} B ({percent:5.1f}%) | {speed_kb_s:6.1f} KB/s | ETA {eta_str}",
|
|
end="",
|
|
flush=True,
|
|
)
|
|
else:
|
|
print(f"\r⬇️ {received} B | {speed_kb_s:6.1f} KB/s", end="", flush=True)
|
|
|
|
data = conn.get_file_data(source, progress_callback=_progress)
|
|
|
|
if len(data) > 0:
|
|
print()
|
|
|
|
with open(local_path, "wb") as f:
|
|
f.write(data)
|
|
|
|
total_duration = max(time.monotonic() - start_time, 1e-6)
|
|
avg_speed_kb_s = (len(data) / 1024.0) / total_duration
|
|
print(f"✅ Heruntergeladen: '{source}' -> '{local_path}' ({len(data)} B, {avg_speed_kb_s:.1f} KB/s)")
|
|
return {
|
|
"source": source,
|
|
"target": local_path,
|
|
"size": len(data),
|
|
}
|