Pagination (200/page), batched+checkpointed ingest (batch_size, resume-safe), quality max_files slicing, upload page; Caddy routes for prefect/photo-filter

This commit is contained in:
2026-08-08 13:36:00 +10:00
parent 1814b5c5c4
commit 0d20c29f35
7 changed files with 309 additions and 74 deletions

View File

@@ -96,25 +96,34 @@ def index(request: Request):
@app.get("/review", response_class=HTMLResponse)
def review(request: Request, source: str = None, status: str = None):
def review(request: Request, source: str = None, status: str = None, page: int = 1):
conn = _conn()
per_page = 200
q = "SELECT sha256, path, status, source FROM image_hashes WHERE 1=1"
count_q = "SELECT COUNT(*) FROM image_hashes WHERE 1=1"
params = []
if source:
q += " AND source=?"
count_q += " AND source=?"
params.append(source)
if status:
q += " AND status=?"
count_q += " AND status=?"
params.append(status)
else:
q += " AND status IN ('scanned','review')"
q += " ORDER BY added_at DESC LIMIT 300"
rows = conn.execute(q, params).fetchall()
count_q += " AND status IN ('scanned','review')"
total = conn.execute(count_q, params).fetchone()[0]
pages = max(1, (total + per_page - 1) // per_page)
page = max(1, min(page, pages))
q += " ORDER BY added_at DESC LIMIT ? OFFSET ?"
rows = conn.execute(q, params + [per_page, (page - 1) * per_page]).fetchall()
conn.close()
items = [_load_item(r) for r in rows]
return templates.TemplateResponse(
request, "review.html",
{"items": items, "source": source, "status": status},
{"items": items, "source": source, "status": status,
"page": page, "pages": pages, "total": total},
)
@@ -231,6 +240,47 @@ def thumb_file(name: str):
return FileResponse(f)
@app.get("/upload", response_class=HTMLResponse)
def upload_page(request: Request):
"""Upload page — drop files/archives into the incoming folder."""
return templates.TemplateResponse(request, "upload.html", {})
@app.post("/upload")
async def upload(request: Request):
"""Receive uploaded files → save to /mnt/data/takeout/incoming/."""
import uuid
from starlette.datastructures import UploadFile
form = await request.form()
incoming = STAGING / "takeout" / "incoming"
incoming.mkdir(parents=True, exist_ok=True)
saved = []
for field in form.values():
if isinstance(field, UploadFile) and field.filename:
# sanitize: keep name but avoid path traversal
name = Path(field.filename).name
dest = incoming / f"{uuid.uuid4().hex[:8]}_{name}"
with open(dest, "wb") as f:
while chunk := await field.read(1024 * 1024):
f.write(chunk)
saved.append(dest.name)
# notify
try:
import sys
if str(BASE.parent) not in sys.path:
sys.path.insert(0, str(BASE.parent))
import apprise_helper
apprise_helper.notify(
"📥 photo-pipeline: upload received",
f"{len(saved)} file(s) saved to incoming. Watch flow will process them.",
)
except Exception:
pass
return {"saved": len(saved), "files": saved}
if __name__ == "__main__":
import uvicorn