refactor: move staging to /mnt/converter/tmp/{guid}/in + out

This commit is contained in:
Claus Lohmar 2026-07-23 10:07:57 +00:00
parent fc380f7d08
commit a28e2af674
5 changed files with 30 additions and 25 deletions

5
.gitignore vendored
View file

@ -1,6 +1,5 @@
# Staging data — not code
/in/
/out/
# Transient session data — not code
/tmp/
/logs/
# Python

View file

@ -15,8 +15,9 @@ from pathlib import Path
from dataclasses import dataclass
from typing import Optional
STAGING_IN = Path("/mnt/converter/in")
STAGING_OUT = Path("/mnt/converter/out")
STAGING_ROOT = Path("/mnt/converter/tmp")
STAGING_IN = STAGING_ROOT / "in"
STAGING_OUT = STAGING_ROOT / "out"
logger = logging.getLogger("backend.converter")
@ -44,8 +45,11 @@ class DiskInfo:
def extract_if_needed(filename: str) -> Path:
"""Extract an archive into STAGING_IN if it's compressed. Returns the path
to the extracted directory (or the original file if not compressed)."""
filepath = STAGING_IN / filename
to the extracted directory (or the original file if not compressed).
Accepts both absolute paths and paths relative to STAGING_IN."""
filepath = Path(filename)
if not filepath.is_absolute():
filepath = STAGING_IN / filename
if not filepath.exists():
raise FileNotFoundError(f"Source not found: {filepath}")

View file

@ -61,6 +61,7 @@ for d in \
"$CONVERTER_ROOT/in" \
"$CONVERTER_ROOT/out" \
"$CONVERTER_ROOT/logs" \
"$CONVERTER_ROOT/tmp" \
"$CONVERTER_ROOT/backend" \
"$CONVERTER_ROOT/frontend" \
"$CONVERTER_ROOT/frontend/templates" \

View file

@ -30,8 +30,9 @@ from converter import extract_if_needed, discover_disk, detect_efi
logger = logging.getLogger("backend.provisioner")
STAGING_IN = Path("/mnt/converter/in")
STAGING_OUT = Path("/mnt/converter/out")
STAGING_ROOT = Path("/mnt/converter/tmp")
STAGING_IN = STAGING_ROOT / "in"
STAGING_OUT = STAGING_ROOT / "out"
def _staging_in(session_id: str = "") -> Path:
return STAGING_IN / session_id if session_id else STAGING_IN

View file

@ -61,11 +61,11 @@ BASE = Path(__file__).parent
app.mount("/static", StaticFiles(directory=str(BASE / "static")), name="static")
_jinja = Environment(loader=FileSystemLoader(str(BASE / "templates")), autoescape=True)
STAGING = Path("/mnt/converter/in")
STAGING_ROOT = Path("/mnt/converter/tmp")
def _staging(session_id: str = "") -> Path:
"""Session-aware staging directory."""
return STAGING / session_id if session_id else STAGING
"""Session-aware staging directory. Returns /mnt/converter/tmp/{guid}/ or /mnt/converter/tmp/"""
return STAGING_ROOT / session_id if session_id else STAGING_ROOT
def _get_session_id(request: Request) -> str:
"""Extract session ID from cookie or query param."""
@ -156,8 +156,8 @@ def session_upload(
):
"""Phase 1 — acquire the source file. Returns JSON so the frontend can
show progress, then call /session/analyze separately."""
staging = _staging(session_id)
staging.mkdir(parents=True, exist_ok=True)
sdir = _staging(session_id) / "in"
sdir.mkdir(parents=True, exist_ok=True)
err = _validate_vmid(vmid)
if err:
return JSONResponse({"phase": "error", "error": err}, status_code=400)
@ -170,7 +170,7 @@ def session_upload(
return JSONResponse({"phase": "error", "error": "No file uploaded."}, status_code=400)
filename = source_file.filename
dest = staging / filename
dest = sdir / filename
content_length = request.headers.get("content-length")
if content_length:
@ -217,11 +217,11 @@ def session_upload(
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
dest = sdir / filename
_active_downloads.pop(filename, None)
usage = shutil.disk_usage(staging)
usage = shutil.disk_usage(_staging(session_id))
free_gb = usage.free / (1024**3)
if free_gb < 50:
logger.warning("Low disk: %.1f GB free — download may fail", free_gb)
@ -279,9 +279,9 @@ async def upload_raw(request: Request):
if err:
return JSONResponse({"phase": "error", "error": err}, status_code=400)
staging = _staging(session_id)
staging.mkdir(parents=True, exist_ok=True)
dest = staging / filename
sdir = _staging(session_id) / "in"
sdir.mkdir(parents=True, exist_ok=True)
dest = sdir / filename
content_length = request.headers.get("content-length")
if content_length:
estimated_gb = int(content_length) / (1024**3)
@ -322,7 +322,7 @@ async def session_progress(filename: str):
if not info:
# Check if file exists on disk (download already completed in a
# previous session, or it was an upload)
dest = STAGING / filename
dest = _staging("") / "in" / filename
if dest.exists():
return JSONResponse({
"phase": "complete",
@ -572,9 +572,9 @@ def scp_start(
if not filename:
return JSONResponse({"phase": "error", "error": "Invalid remote path."}, status_code=400)
session_dir = STAGING / session_id if session_id else STAGING
session_dir.mkdir(parents=True, exist_ok=True)
dest = session_dir / filename
sdir = _staging(session_id) / "in"
sdir.mkdir(parents=True, exist_ok=True)
dest = sdir / filename
# Check disk space
usage = shutil.disk_usage(STAGING)
@ -620,7 +620,7 @@ def scp_progress(session_id: str, filename: str):
key = f"{session_id}/{filename}"
info = _active_scp.get(key)
if not info:
dest = STAGING / session_id / filename
dest = _staging(session_id) / "in" / filename
if dest.exists():
return JSONResponse({
"phase": "complete",