feat: SCP probes remote file size via SSH before pull
Runs 'ssh stat -c%s' or 'ls -l' to get total bytes before starting SCP transfer. Progress bar now shows real percentage and ETA instead of indeterminate spinner.
This commit is contained in:
parent
cd5f31505a
commit
a7ab9243c0
2 changed files with 37 additions and 3 deletions
|
|
@ -472,8 +472,25 @@ def scp_start(
|
|||
"-P", str(scp_port),
|
||||
remote, str(dest),
|
||||
]
|
||||
|
||||
logger.info("Starting SCP pull: %s → %s", remote, dest)
|
||||
|
||||
# ── Probe file size via SSH ────────────────────────────────────
|
||||
file_size = 0
|
||||
try:
|
||||
probe_cmd = [
|
||||
"sshpass", "-e",
|
||||
"ssh", "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=10",
|
||||
"-p", str(scp_port), f"{scp_user}@{scp_host}",
|
||||
f"stat -c%s '{scp_path}' 2>/dev/null || ls -l '{scp_path}' 2>/dev/null | awk '{{print $5}}'"
|
||||
]
|
||||
probe = subprocess.run(probe_cmd, env=env, capture_output=True, text=True, timeout=15)
|
||||
if probe.returncode == 0 and probe.stdout.strip().isdigit():
|
||||
file_size = int(probe.stdout.strip())
|
||||
logger.info("Remote file size: %.1f GiB", file_size / (1024**3))
|
||||
except Exception:
|
||||
logger.info("Could not probe remote file size")
|
||||
|
||||
# ── Start SCP ──────────────────────────────────────────────────
|
||||
try:
|
||||
proc = subprocess.Popen(cmd, env=env, stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE, text=True)
|
||||
|
|
@ -485,12 +502,14 @@ def scp_start(
|
|||
"proc": proc,
|
||||
"dest": dest,
|
||||
"start_time": time.time(),
|
||||
"total_size": file_size,
|
||||
}
|
||||
|
||||
return JSONResponse({
|
||||
"phase": "pulling",
|
||||
"filename": filename,
|
||||
"session_id": session_id,
|
||||
"total_size_gb": round(file_size / (1024**3), 1) if file_size else None,
|
||||
})
|
||||
|
||||
|
||||
|
|
@ -540,12 +559,25 @@ def scp_progress(session_id: str, filename: str):
|
|||
|
||||
elapsed = max(time.time() - info.get("start_time", 0), 1)
|
||||
speed_mbps = round(current_bytes / elapsed / 1_000_000, 1)
|
||||
total = info.get("total_size", 0)
|
||||
|
||||
eta_str = None
|
||||
pct = 0
|
||||
if total > 0 and current_bytes > 0:
|
||||
pct = min(round((current_bytes / total) * 100), 99)
|
||||
remaining = total - current_bytes
|
||||
eta = remaining / max(current_bytes / elapsed, 1)
|
||||
if eta > 60:
|
||||
eta_str = f"~{round(eta / 60)} min remaining"
|
||||
|
||||
return JSONResponse({
|
||||
"phase": "pulling",
|
||||
"file_size_bytes": current_bytes,
|
||||
"file_size_gb": round(current_bytes / (1024**3), 1),
|
||||
"speed_mbps": speed_mbps,
|
||||
"pct": pct,
|
||||
"eta": eta_str,
|
||||
"total_size_gb": round(total / (1024**3), 1) if total else None,
|
||||
})
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -95,7 +95,8 @@ async function startScpPull(e) {
|
|||
}
|
||||
|
||||
const filename = data.filename;
|
||||
setScpPhase('Pulling file from remote server...', 0, true);
|
||||
const totalGb = data.total_size_gb;
|
||||
setScpPhase('Pulling file...' + (totalGb ? ' (' + totalGb + ' GiB total)' : ''), 0, true);
|
||||
for (;;) {
|
||||
await new Promise(r => setTimeout(r, 2000));
|
||||
try {
|
||||
|
|
@ -123,7 +124,8 @@ async function startScpPull(e) {
|
|||
const gb = (pdata.file_size_gb || 0).toFixed(1);
|
||||
let label = 'Pulling file... ' + gb + ' GiB';
|
||||
if (pdata.speed_mbps) label += ' (' + pdata.speed_mbps + ' MB/s)';
|
||||
setScpPhase(label, 0, true);
|
||||
if (pdata.eta) label += ' — ' + pdata.eta;
|
||||
setScpPhase(label, pdata.pct || 0, !pdata.pct);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue