refactor: move staging to /mnt/converter/tmp/{guid}/in + out
This commit is contained in:
parent
fc380f7d08
commit
a28e2af674
5 changed files with 30 additions and 25 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -1,6 +1,5 @@
|
||||||
# Staging data — not code
|
# Transient session data — not code
|
||||||
/in/
|
/tmp/
|
||||||
/out/
|
|
||||||
/logs/
|
/logs/
|
||||||
|
|
||||||
# Python
|
# Python
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,9 @@ from pathlib import Path
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
STAGING_IN = Path("/mnt/converter/in")
|
STAGING_ROOT = Path("/mnt/converter/tmp")
|
||||||
STAGING_OUT = Path("/mnt/converter/out")
|
STAGING_IN = STAGING_ROOT / "in"
|
||||||
|
STAGING_OUT = STAGING_ROOT / "out"
|
||||||
|
|
||||||
logger = logging.getLogger("backend.converter")
|
logger = logging.getLogger("backend.converter")
|
||||||
|
|
||||||
|
|
@ -44,7 +45,10 @@ class DiskInfo:
|
||||||
|
|
||||||
def extract_if_needed(filename: str) -> Path:
|
def extract_if_needed(filename: str) -> Path:
|
||||||
"""Extract an archive into STAGING_IN if it's compressed. Returns the 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)."""
|
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
|
filepath = STAGING_IN / filename
|
||||||
if not filepath.exists():
|
if not filepath.exists():
|
||||||
raise FileNotFoundError(f"Source not found: {filepath}")
|
raise FileNotFoundError(f"Source not found: {filepath}")
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ for d in \
|
||||||
"$CONVERTER_ROOT/in" \
|
"$CONVERTER_ROOT/in" \
|
||||||
"$CONVERTER_ROOT/out" \
|
"$CONVERTER_ROOT/out" \
|
||||||
"$CONVERTER_ROOT/logs" \
|
"$CONVERTER_ROOT/logs" \
|
||||||
|
"$CONVERTER_ROOT/tmp" \
|
||||||
"$CONVERTER_ROOT/backend" \
|
"$CONVERTER_ROOT/backend" \
|
||||||
"$CONVERTER_ROOT/frontend" \
|
"$CONVERTER_ROOT/frontend" \
|
||||||
"$CONVERTER_ROOT/frontend/templates" \
|
"$CONVERTER_ROOT/frontend/templates" \
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,9 @@ from converter import extract_if_needed, discover_disk, detect_efi
|
||||||
|
|
||||||
logger = logging.getLogger("backend.provisioner")
|
logger = logging.getLogger("backend.provisioner")
|
||||||
|
|
||||||
STAGING_IN = Path("/mnt/converter/in")
|
STAGING_ROOT = Path("/mnt/converter/tmp")
|
||||||
STAGING_OUT = Path("/mnt/converter/out")
|
STAGING_IN = STAGING_ROOT / "in"
|
||||||
|
STAGING_OUT = STAGING_ROOT / "out"
|
||||||
|
|
||||||
def _staging_in(session_id: str = "") -> Path:
|
def _staging_in(session_id: str = "") -> Path:
|
||||||
return STAGING_IN / session_id if session_id else STAGING_IN
|
return STAGING_IN / session_id if session_id else STAGING_IN
|
||||||
|
|
|
||||||
|
|
@ -61,11 +61,11 @@ BASE = Path(__file__).parent
|
||||||
app.mount("/static", StaticFiles(directory=str(BASE / "static")), name="static")
|
app.mount("/static", StaticFiles(directory=str(BASE / "static")), name="static")
|
||||||
|
|
||||||
_jinja = Environment(loader=FileSystemLoader(str(BASE / "templates")), autoescape=True)
|
_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:
|
def _staging(session_id: str = "") -> Path:
|
||||||
"""Session-aware staging directory."""
|
"""Session-aware staging directory. Returns /mnt/converter/tmp/{guid}/ or /mnt/converter/tmp/"""
|
||||||
return STAGING / session_id if session_id else STAGING
|
return STAGING_ROOT / session_id if session_id else STAGING_ROOT
|
||||||
|
|
||||||
def _get_session_id(request: Request) -> str:
|
def _get_session_id(request: Request) -> str:
|
||||||
"""Extract session ID from cookie or query param."""
|
"""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
|
"""Phase 1 — acquire the source file. Returns JSON so the frontend can
|
||||||
show progress, then call /session/analyze separately."""
|
show progress, then call /session/analyze separately."""
|
||||||
staging = _staging(session_id)
|
sdir = _staging(session_id) / "in"
|
||||||
staging.mkdir(parents=True, exist_ok=True)
|
sdir.mkdir(parents=True, exist_ok=True)
|
||||||
err = _validate_vmid(vmid)
|
err = _validate_vmid(vmid)
|
||||||
if err:
|
if err:
|
||||||
return JSONResponse({"phase": "error", "error": err}, status_code=400)
|
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)
|
return JSONResponse({"phase": "error", "error": "No file uploaded."}, status_code=400)
|
||||||
|
|
||||||
filename = source_file.filename
|
filename = source_file.filename
|
||||||
dest = staging / filename
|
dest = sdir / filename
|
||||||
|
|
||||||
content_length = request.headers.get("content-length")
|
content_length = request.headers.get("content-length")
|
||||||
if content_length:
|
if content_length:
|
||||||
|
|
@ -217,11 +217,11 @@ def session_upload(
|
||||||
return JSONResponse({"phase": "error", "error": "No URL provided."}, status_code=400)
|
return JSONResponse({"phase": "error", "error": "No URL provided."}, status_code=400)
|
||||||
|
|
||||||
filename = Path(url).name or f"download_{uuid.uuid4().hex[:8]}"
|
filename = Path(url).name or f"download_{uuid.uuid4().hex[:8]}"
|
||||||
dest = staging / filename
|
dest = sdir / filename
|
||||||
|
|
||||||
_active_downloads.pop(filename, None)
|
_active_downloads.pop(filename, None)
|
||||||
|
|
||||||
usage = shutil.disk_usage(staging)
|
usage = shutil.disk_usage(_staging(session_id))
|
||||||
free_gb = usage.free / (1024**3)
|
free_gb = usage.free / (1024**3)
|
||||||
if free_gb < 50:
|
if free_gb < 50:
|
||||||
logger.warning("Low disk: %.1f GB free — download may fail", free_gb)
|
logger.warning("Low disk: %.1f GB free — download may fail", free_gb)
|
||||||
|
|
@ -279,9 +279,9 @@ async def upload_raw(request: Request):
|
||||||
if err:
|
if err:
|
||||||
return JSONResponse({"phase": "error", "error": err}, status_code=400)
|
return JSONResponse({"phase": "error", "error": err}, status_code=400)
|
||||||
|
|
||||||
staging = _staging(session_id)
|
sdir = _staging(session_id) / "in"
|
||||||
staging.mkdir(parents=True, exist_ok=True)
|
sdir.mkdir(parents=True, exist_ok=True)
|
||||||
dest = staging / filename
|
dest = sdir / filename
|
||||||
content_length = request.headers.get("content-length")
|
content_length = request.headers.get("content-length")
|
||||||
if content_length:
|
if content_length:
|
||||||
estimated_gb = int(content_length) / (1024**3)
|
estimated_gb = int(content_length) / (1024**3)
|
||||||
|
|
@ -322,7 +322,7 @@ async def session_progress(filename: str):
|
||||||
if not info:
|
if not info:
|
||||||
# Check if file exists on disk (download already completed in a
|
# Check if file exists on disk (download already completed in a
|
||||||
# previous session, or it was an upload)
|
# previous session, or it was an upload)
|
||||||
dest = STAGING / filename
|
dest = _staging("") / "in" / filename
|
||||||
if dest.exists():
|
if dest.exists():
|
||||||
return JSONResponse({
|
return JSONResponse({
|
||||||
"phase": "complete",
|
"phase": "complete",
|
||||||
|
|
@ -572,9 +572,9 @@ def scp_start(
|
||||||
if not filename:
|
if not filename:
|
||||||
return JSONResponse({"phase": "error", "error": "Invalid remote path."}, status_code=400)
|
return JSONResponse({"phase": "error", "error": "Invalid remote path."}, status_code=400)
|
||||||
|
|
||||||
session_dir = STAGING / session_id if session_id else STAGING
|
sdir = _staging(session_id) / "in"
|
||||||
session_dir.mkdir(parents=True, exist_ok=True)
|
sdir.mkdir(parents=True, exist_ok=True)
|
||||||
dest = session_dir / filename
|
dest = sdir / filename
|
||||||
|
|
||||||
# Check disk space
|
# Check disk space
|
||||||
usage = shutil.disk_usage(STAGING)
|
usage = shutil.disk_usage(STAGING)
|
||||||
|
|
@ -620,7 +620,7 @@ def scp_progress(session_id: str, filename: str):
|
||||||
key = f"{session_id}/{filename}"
|
key = f"{session_id}/{filename}"
|
||||||
info = _active_scp.get(key)
|
info = _active_scp.get(key)
|
||||||
if not info:
|
if not info:
|
||||||
dest = STAGING / session_id / filename
|
dest = _staging(session_id) / "in" / filename
|
||||||
if dest.exists():
|
if dest.exists():
|
||||||
return JSONResponse({
|
return JSONResponse({
|
||||||
"phase": "complete",
|
"phase": "complete",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue