fix: restore download logic to session_upload — was embedded in upload_raw
This commit is contained in:
parent
adfb68fe6b
commit
50e969f95e
1 changed files with 51 additions and 57 deletions
108
frontend/app.py
108
frontend/app.py
|
|
@ -196,6 +196,57 @@ def session_upload(
|
||||||
"file_size_gb": file_size_gb,
|
"file_size_gb": file_size_gb,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
else:
|
||||||
|
# ── Download ────────────────────────────────────────────────
|
||||||
|
url = (source_url or "").strip()
|
||||||
|
if not url:
|
||||||
|
return JSONResponse({"phase": "error", "error": "No URL provided."}, status_code=400)
|
||||||
|
|
||||||
|
filename = Path(url).name or f"download_{uuid.uuid4().hex[:8]}"
|
||||||
|
dest = STAGING / filename
|
||||||
|
|
||||||
|
_active_downloads.pop(filename, None)
|
||||||
|
|
||||||
|
usage = shutil.disk_usage(STAGING)
|
||||||
|
free_gb = usage.free / (1024**3)
|
||||||
|
if free_gb < 50:
|
||||||
|
logger.warning("Low disk: %.1f GB free — download may fail", free_gb)
|
||||||
|
|
||||||
|
logger.info("Starting background download: %s → %s", url, dest)
|
||||||
|
try:
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
["wget", "--progress=dot:giga", "-O", str(dest), url],
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
return JSONResponse({"phase": "error", "error": f"Failed to start download: {exc}"}, status_code=500)
|
||||||
|
|
||||||
|
_active_downloads[filename] = {
|
||||||
|
"proc": proc, "dest": dest, "vmid": vmid, "vm_name": vm_name,
|
||||||
|
"start_time": time.time(), "content_length": 0, "_last_logged_bytes": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
content_length = 0
|
||||||
|
def _fetch_cl():
|
||||||
|
nonlocal content_length
|
||||||
|
try:
|
||||||
|
hr = http_requests.head(url, timeout=5, allow_redirects=True)
|
||||||
|
cl = hr.headers.get("Content-Length")
|
||||||
|
if cl:
|
||||||
|
content_length = int(cl)
|
||||||
|
if filename in _active_downloads:
|
||||||
|
_active_downloads[filename]["content_length"] = int(cl)
|
||||||
|
logger.info("Download size: %.1f GiB", int(cl) / (1024**3))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
threading.Thread(target=_fetch_cl, daemon=True).start()
|
||||||
|
|
||||||
|
return JSONResponse({
|
||||||
|
"phase": "downloading",
|
||||||
|
"filename": filename, "vmid": vmid, "vm_name": vm_name,
|
||||||
|
"content_length_gb": round(content_length / (1024**3), 1) if content_length else None,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@app.post("/session/upload-raw")
|
@app.post("/session/upload-raw")
|
||||||
async def upload_raw(request: Request):
|
async def upload_raw(request: Request):
|
||||||
|
|
@ -247,63 +298,6 @@ async def upload_raw(request: Request):
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# ── Download ────────────────────────────────────────────────────
|
|
||||||
url = (source_url or "").strip()
|
|
||||||
if not url:
|
|
||||||
return JSONResponse({"phase": "error", "error": "No URL provided."}, status_code=400)
|
|
||||||
|
|
||||||
filename = Path(url).name or f"download_{uuid.uuid4().hex[:8]}"
|
|
||||||
dest = STAGING / filename
|
|
||||||
|
|
||||||
# Clean up any stale download with same name
|
|
||||||
_active_downloads.pop(filename, None)
|
|
||||||
|
|
||||||
usage = shutil.disk_usage(STAGING)
|
|
||||||
free_gb = usage.free / (1024**3)
|
|
||||||
if free_gb < 50:
|
|
||||||
logger.warning("Low disk: %.1f GB free — download may fail", free_gb)
|
|
||||||
|
|
||||||
# Start wget immediately (HEAD request for size happens in background)
|
|
||||||
logger.info("Starting background download: %s → %s", url, dest)
|
|
||||||
try:
|
|
||||||
proc = subprocess.Popen(
|
|
||||||
["wget", "--progress=dot:giga", "-O", str(dest), url],
|
|
||||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
|
||||||
return JSONResponse({"phase": "error", "error": f"Failed to start download: {exc}"}, status_code=500)
|
|
||||||
|
|
||||||
_active_downloads[filename] = {
|
|
||||||
"proc": proc,
|
|
||||||
"dest": dest,
|
|
||||||
"vmid": vmid,
|
|
||||||
"vm_name": vm_name,
|
|
||||||
"start_time": time.time(),
|
|
||||||
"content_length": 0,
|
|
||||||
"_last_logged_bytes": 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Fire-and-forget HEAD request to get file size for ETA (non-blocking)
|
|
||||||
def _fetch_content_length():
|
|
||||||
try:
|
|
||||||
head_resp = http_requests.head(url, timeout=5, allow_redirects=True)
|
|
||||||
cl = head_resp.headers.get("Content-Length")
|
|
||||||
if cl and filename in _active_downloads:
|
|
||||||
_active_downloads[filename]["content_length"] = int(cl)
|
|
||||||
logger.info("Download size from HEAD: %.1f GiB", int(cl) / (1024**3))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
threading.Thread(target=_fetch_content_length, daemon=True).start()
|
|
||||||
|
|
||||||
return JSONResponse({
|
|
||||||
"phase": "downloading",
|
|
||||||
"filename": filename,
|
|
||||||
"vmid": vmid,
|
|
||||||
"vm_name": vm_name,
|
|
||||||
"content_length_gb": round(content_length / (1024**3), 1) if content_length else None,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/session/progress/{filename}")
|
@app.get("/session/progress/{filename}")
|
||||||
async def session_progress(filename: str):
|
async def session_progress(filename: str):
|
||||||
"""Poll download progress — returns current file size and phase."""
|
"""Poll download progress — returns current file size and phase."""
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue