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:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user