Expire stored source photos after 7 days, keeping grades forever
Storing originals for Regrade grew the database ~16MB per grade at the upload cap. Photos now expire on a configurable window (default 7 days, counted from last grade so regrading resets it) and the file is VACUUMed so the space is actually returned — clearing the column alone only moves pages to the freelist, which would have made the whole feature a no-op on disk. Grades, thumbnails and measurements are never pruned; a pruned card's Regrade falls back to the existing re-pick path, now with a banner saying why.
This commit is contained in:
parent
633cb98462
commit
602bce8ce3
5 changed files with 127 additions and 0 deletions
35
app.py
35
app.py
|
|
@ -21,6 +21,7 @@ import io
|
|||
import json
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import urllib.parse
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
|
@ -43,6 +44,11 @@ MAX_UPLOAD_BYTES = 12 * 1024 * 1024
|
|||
# ~4/3 that as base64, plus JSON overhead. Kept in step with nginx's
|
||||
# client_max_body_size in nginx/card-grader.conf.
|
||||
MAX_BODY_BYTES = 20 * 1024 * 1024
|
||||
# How often to expire stored source photos (see store.prune_source_images).
|
||||
# Six-hourly rather than daily so a long-running container doesn't hold a
|
||||
# week's worth of extra images for up to another day past the window, and
|
||||
# so the first sweep after a restart isn't the only one that ever runs.
|
||||
PRUNE_INTERVAL_SECONDS = 6 * 60 * 60
|
||||
# More angles help — front, back, corner close-ups — but past a handful the
|
||||
# extra photos cost tokens without adding evidence.
|
||||
MAX_GRADE_IMAGES = 6
|
||||
|
|
@ -490,6 +496,26 @@ class Server(ThreadingHTTPServer):
|
|||
daemon_threads = True
|
||||
|
||||
|
||||
def _prune_forever():
|
||||
"""Expire stored source photos on a timer, for the life of the process.
|
||||
|
||||
A daemon thread rather than a cron entry so the retention window holds
|
||||
on any host this runs on, without a second thing to install and keep in
|
||||
step. Never lets an exception end the loop: a failed sweep should cost
|
||||
one cycle, not disable pruning until the next restart.
|
||||
"""
|
||||
while True:
|
||||
try:
|
||||
pruned = store.prune_source_images()
|
||||
if pruned:
|
||||
print("[prune] dropped stored photos from {} grade(s) older "
|
||||
"than {} days".format(pruned, store.SOURCE_IMAGE_RETENTION_DAYS),
|
||||
flush=True)
|
||||
except Exception as exc:
|
||||
print("[prune] failed: {}".format(exc), flush=True)
|
||||
time.sleep(PRUNE_INTERVAL_SECONDS)
|
||||
|
||||
|
||||
def main():
|
||||
store.init()
|
||||
try:
|
||||
|
|
@ -507,8 +533,17 @@ def main():
|
|||
if ip:
|
||||
print(" On your phone: http://{}:{} (same Wi-Fi)".format(ip, PORT))
|
||||
print(" Database: {}".format(store.DB_PATH))
|
||||
if store.SOURCE_IMAGE_RETENTION_DAYS > 0:
|
||||
print(" Photos kept: {} days (grades kept forever)".format(
|
||||
store.SOURCE_IMAGE_RETENTION_DAYS))
|
||||
else:
|
||||
print(" Photos kept: forever (pruning disabled)")
|
||||
print("\n Ctrl-C to stop.\n")
|
||||
|
||||
# Sweep once at boot so a container that restarts often still expires
|
||||
# things, then hand off to the timer.
|
||||
threading.Thread(target=_prune_forever, daemon=True).start()
|
||||
|
||||
try:
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue