"""Grotti starten. Kern und Seiten liegen daneben.""" import atexit import signal import threading import time from waitress import serve from grotti import db, jobs, library, logutil, store from grotti.marlin_supervisor import enable as enable_marlin from grotti.config import load from web.app import create_app log = logutil.get("grotti.server") def background(cfg): def work(): try: with db.connect(cfg["database_url"]) as conn: library.sync_watch(conn, cfg, force=False) jobs.sync_history(conn, cfg) deleted = db.prune_run_samples(conn, keep_days=90) if deleted: log.info("Proben gelöscht: %s", deleted) conn.commit() except Exception as exc: log.warning("Abgleich: %s", exc) threading.Thread(target=work, name="grotti-abgleich", daemon=True).start() def _retention_loop(cfg, stop_event): """Nächtlich alte run_samples entfernen.""" while not stop_event.wait(3600): hour = time.localtime().tm_hour if hour != 3: continue try: with db.connect(cfg["database_url"]) as conn: n = db.prune_run_samples(conn, keep_days=90) conn.commit() if n: log.info("Retention: %s Proben gelöscht.", n) except Exception as exc: log.warning("Retention: %s", exc) stop_event.wait(3600) def main(): cfg = load() logutil.setup(cfg.get("log_level") or "INFO") cfg["data_dir"].mkdir(parents=True, exist_ok=True) (cfg["data_dir"] / "eingang").mkdir(exist_ok=True) db.prepare(cfg["database_url"]) db.init_pool(cfg["database_url"], min_size=2, max_size=16) with db.connect(cfg["database_url"]) as conn: store.seed_printers(conn, cfg["printers_file"]) store.seed_materials(conn) conn.commit() app = create_app(cfg) watcher = app.extensions["watcher"] dispatcher = app.extensions["print_queue"] send_queue = app.extensions["send_queue"] enable_marlin(cfg["data_dir"]) watcher.start() dispatcher.start() if hasattr(send_queue, "start"): send_queue.start() from grotti.daily_report import DailyReportThread daily = DailyReportThread(cfg["database_url"], cfg, watcher=watcher) daily.start() app.extensions["daily_report"] = daily stop_event = threading.Event() retention = threading.Thread( target=_retention_loop, args=(cfg, stop_event), name="grotti-retention", daemon=True, ) retention.start() background(cfg) def shutdown(*_args): stop_event.set() try: watcher.stop() except Exception: pass try: dispatcher.stop() except Exception: pass try: if hasattr(send_queue, "stop"): send_queue.stop() except Exception: pass try: daily.stop() except Exception: pass db.close_pool() # Der Marlin-Dienst bleibt. Ein Stopp hier würde den Druck abbrechen. atexit.register(shutdown) for sig in (signal.SIGINT, signal.SIGTERM): try: signal.signal(sig, lambda *_: shutdown()) except (ValueError, OSError): pass log.info("Grotti: http://%s:%s/", cfg["host"], cfg["port"]) serve( app, host=cfg["host"], port=cfg["port"], threads=int(cfg.get("http_threads") or 16), channel_timeout=120, ) if __name__ == "__main__": main()