"""Transactional outbox worker. Fake receipts persist; no messages leave the stack.""" import json import logging import os import threading import time from http.server import BaseHTTPRequestHandler, HTTPServer from psycopg.types.json import Jsonb from .adapters import FakeTiny, FakeWhatsApp, LocalS3Storage, require_runtime from .core.secrets import load as load_secret_files from .core.db import connect from .printjobs import render_loop from .scanning import ClamAV, scan_loop load_secret_files() require_runtime() adapters = {'tiny': FakeTiny(), 'whatsapp': FakeWhatsApp()} # Not yet confirmed against the client's account; see app/tiny.py. if os.environ.get('TINY_ADAPTER') == 'tiny': from .tiny import TinyOrders adapters['tiny'] = TinyOrders() last_tick = 0.0 last_cleanup = 0.0 last_tiny_keepalive = 0.0 storage = LocalS3Storage() scan_thread = None render_thread = None def cleanup(): """Delete only expired object bytes; retain order/file metadata and history.""" with connect() as c: rows = c.execute("""SELECT * FROM dtf_local.uploads WHERE purged_at IS NULL AND (expires_at<=now() OR (NOT complete AND created_at 60: cleanup() last_cleanup = time.monotonic() tiny_keepalive() except Exception: logging.exception('Local worker tick failed') time.sleep(1) class Health(BaseHTTPRequestHandler): def do_GET(self): scanner = False try: scanner = bool(scan_thread and scan_thread.is_alive() and ClamAV().ping()) except Exception: pass renderer = bool(render_thread and render_thread.is_alive()) healthy = time.monotonic()-last_tick < 15 and scanner and renderer self.send_response(200 if self.path == '/health' and healthy else 503) self.end_headers() self.wfile.write(json.dumps({'worker': 'ok' if healthy else 'unavailable', 'scanner': 'ok' if scanner else 'unavailable', 'print_files': 'ok' if renderer else 'unavailable'}).encode()) def log_message(self, *args): pass if __name__ == '__main__': scan_thread = threading.Thread(target=scan_loop,args=(storage,),daemon=True) scan_thread.start() render_thread = threading.Thread(target=render_loop,args=(storage,),daemon=True) render_thread.start() threading.Thread(target=loop, daemon=True).start() HTTPServer(('0.0.0.0',8002),Health).serve_forever()