#!/usr/bin/env python3 """Monster High picker server (Mac mini). Serves the shoot folder over HTTP on :8090 and persists client picks: GET / -> redirect to the v5 picker page POST /picks -> persist {shoot, picks:[ids]} to ClientPicker/picks-sync.json GET /picks -> return the latest persisted picks Everything else is static file serving from the shoot root. """ import json import os from datetime import datetime, timezone from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer ROOT = "/Volumes/Sam Storage/YellowMellow/2026/Monster High Theme (10:01:2026)" PICKS_FILE = os.path.join(ROOT, "ClientPicker", "picks-sync.json") PICKER_PAGE = "/Monster%20High%20-%20Photo%20Picker.html" MAX_BODY = 1_000_000 class Handler(SimpleHTTPRequestHandler): def __init__(self, *a, **kw): super().__init__(*a, directory=ROOT, **kw) def log_message(self, fmt, *args): print("[%s] %s" % (datetime.now().strftime("%H:%M:%S"), fmt % args), flush=True) def do_GET(self): if self.path in ("/", ""): self.send_response(302) self.send_header("Location", PICKER_PAGE) self.end_headers() return if self.path == "/picks": try: with open(PICKS_FILE, "r") as f: data = json.load(f) except Exception: data = {"shoot": "monster-high", "picks": [], "updated": None} body = json.dumps(data).encode() self.send_response(200) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) return super().do_GET() def do_POST(self): if self.path.rstrip("/") != "/picks": self.send_error(404) return try: length = int(self.headers.get("Content-Length", 0)) if length <= 0 or length > MAX_BODY: raise ValueError("bad body size") payload = json.loads(self.rfile.read(length)) except Exception as e: self.send_error(400, "invalid JSON") print("picks reject: %s" % e, flush=True) return picks = payload.get("picks") if not isinstance(picks, list) or not all(isinstance(x, str) for x in picks): self.send_error(400, "picks must be a list of strings") return record = { "shoot": payload.get("shoot", "monster-high"), "picks": sorted(picks), "count": len(picks), "updated": datetime.now(timezone.utc).isoformat(), } tmp = PICKS_FILE + ".tmp" with open(tmp, "w") as f: json.dump(record, f) os.replace(tmp, PICKS_FILE) body = json.dumps({"ok": True, "count": record["count"]}).encode() self.send_response(200) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) print("picks saved: %d photos at %s" % (record["count"], record["updated"]), flush=True) if __name__ == "__main__": print("serving %s on :8090" % ROOT, flush=True) ThreadingHTTPServer(("0.0.0.0", 8090), Handler).serve_forever()