Freeze-Fixed

This commit is contained in:
2026-09-07 11:53:19 +02:00
parent d0e022484e
commit f38da358ad
5 changed files with 122 additions and 19 deletions

View File

@@ -1,19 +1,21 @@
import hashlib
import hmac
import io
import os
import re
import secrets
import shutil
import tempfile
import time
import zipfile
from pathlib import Path
from typing import List
from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request, UploadFile, File
from fastapi.responses import FileResponse, StreamingResponse
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from starlette.background import BackgroundTask
from starlette.concurrency import run_in_threadpool
from starlette.middleware.sessions import SessionMiddleware
PROJECTS_DIR = Path(os.environ.get("PROJECTS_DIR", "/data/projects")).resolve()
@@ -180,13 +182,19 @@ async def upload_files(name: str, files: List[UploadFile] = File(...)):
continue
filename = safe_filename(upload.filename)
dest = path / filename
with open(dest, "wb") as out:
shutil.copyfileobj(upload.file, out)
# Run the actual (blocking) disk copy in a worker thread so a large
# file doesn't freeze the whole server for the duration of the copy.
await run_in_threadpool(_copy_upload_to_disk, upload.file, dest)
saved.append(filename)
return {"saved": saved, "skipped": skipped}
def _copy_upload_to_disk(src_file, dest_path: Path) -> None:
with open(dest_path, "wb") as out:
shutil.copyfileobj(src_file, out, length=4 * 1024 * 1024)
@api.get("/projects/{name}/files/{filename}")
def get_file(name: str, filename: str):
path = safe_project_path(name)
@@ -199,10 +207,13 @@ def get_file(name: str, filename: str):
@api.get("/download-all")
def download_all_projects():
projects = [p for p in PROJECTS_DIR.iterdir() if p.is_dir()]
files_found = False
buffer = io.BytesIO()
with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as zf:
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".zip")
tmp_path = Path(tmp.name)
tmp.close()
files_found = False
with zipfile.ZipFile(tmp_path, "w", zipfile.ZIP_DEFLATED) as zf:
for project in projects:
for f in project.iterdir():
if f.is_file() and f.suffix.lower() in ALLOWED_EXTENSIONS:
@@ -210,13 +221,14 @@ def download_all_projects():
files_found = True
if not files_found:
tmp_path.unlink(missing_ok=True)
raise HTTPException(404, "No files to download")
buffer.seek(0)
return StreamingResponse(
buffer,
return FileResponse(
tmp_path,
media_type="application/zip",
headers={"Content-Disposition": 'attachment; filename="alle-projekte.zip"'},
filename="alle-projekte.zip",
background=BackgroundTask(lambda: tmp_path.unlink(missing_ok=True)),
)
@@ -230,17 +242,19 @@ def download_project(name: str):
if not files:
raise HTTPException(404, "Project has no files to download")
buffer = io.BytesIO()
with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as zf:
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".zip")
tmp_path = Path(tmp.name)
tmp.close()
with zipfile.ZipFile(tmp_path, "w", zipfile.ZIP_DEFLATED) as zf:
for f in files:
zf.write(f, arcname=f.name)
buffer.seek(0)
zip_filename = f"{name}.zip"
return StreamingResponse(
buffer,
return FileResponse(
tmp_path,
media_type="application/zip",
headers={"Content-Disposition": f'attachment; filename="{zip_filename}"'},
filename=f"{name}.zip",
background=BackgroundTask(lambda: tmp_path.unlink(missing_ok=True)),
)