2026-04-11 04:38:11 +02:00

138 lines
4.2 KiB
Python

"""
Scan stream, start/stop, checkpoint, settings, delta
"""
from __future__ import annotations
import threading
from flask import Blueprint, jsonify, request
from routes import state
from app_config import (
_save_settings, _load_settings,
_load_src_toggles, _save_src_toggles,
)
from checkpoint import (
_checkpoint_key, _load_checkpoint, _clear_checkpoint,
_load_delta_tokens, _DELTA_PATH,
)
bp = Blueprint("scan", __name__)
@bp.route("/api/scan/status")
def scan_status():
"""Lightweight status check — is a scan running? What scan_id?"""
import sse as _sse_mod
acquired = state._scan_lock.acquire(blocking=False)
if acquired:
state._scan_lock.release()
return jsonify({
"running": not acquired,
"scan_id": _sse_mod._current_scan_id or None,
})
@bp.route("/api/src_toggles", methods=["GET", "POST"])
def src_toggles():
"""GET: return source toggle state. POST: save."""
if request.method == "POST":
_save_src_toggles(request.get_json() or {})
return jsonify({"ok": True})
return jsonify(_load_src_toggles())
@bp.route("/api/scan/start", methods=["POST"])
def scan_start():
if not state.connector:
return jsonify({"error": "not authenticated"}), 401
if not state._scan_lock.acquire(blocking=False):
return jsonify({"error": "scan already running"}), 409
options = request.get_json() or {}
state._scan_abort.clear()
profile_id = options.pop("profile_id", None)
_save_settings({
"sources": options.get("sources", []),
"user_ids": options.get("user_ids", []),
"options": options.get("options", {}),
}, profile_id=profile_id)
def _run():
from scan_engine import run_scan
try:
run_scan(options)
finally:
state._scan_lock.release()
threading.Thread(target=_run, daemon=True).start()
return jsonify({"status": "started"})
@bp.route("/api/scan/stop", methods=["POST"])
def scan_stop():
state._scan_abort.set()
return jsonify({"status": "stopping"})
@bp.route("/api/scan/checkpoint", methods=["POST"])
def scan_checkpoint_info():
"""Return info about any saved checkpoint for the given scan options.
If check_only=true, just reports whether a scan is currently running."""
options = request.get_json() or {}
if options.get("check_only"):
acquired = state._scan_lock.acquire(blocking=False)
if acquired:
state._scan_lock.release()
return jsonify({"running": not acquired})
key = _checkpoint_key(options)
cp = _load_checkpoint(key)
if not cp:
return jsonify({"exists": False})
return jsonify({
"exists": True,
"scanned_count": len(cp.get("scanned_ids", [])),
"flagged_count": len(cp.get("flagged", [])),
"started_at": cp.get("meta", {}).get("started_at"),
})
@bp.route("/api/scan/clear_checkpoint", methods=["POST"])
def scan_clear_checkpoint():
"""Discard any saved checkpoint so the next scan starts fresh."""
_clear_checkpoint()
return jsonify({"status": "cleared"})
@bp.route("/api/settings/save", methods=["POST"])
def settings_save():
"""Persist scan settings so they can be reused by --headless mode."""
payload = request.get_json() or {}
_save_settings(payload)
return jsonify({"status": "saved"})
@bp.route("/api/settings/load")
def settings_load():
"""Return previously saved scan settings (for --headless setup guidance)."""
s = _load_settings()
if not s:
return jsonify({"exists": False})
return jsonify({"exists": True, "settings": s})
@bp.route("/api/delta/status")
def delta_status():
"""Return info about stored delta tokens."""
tokens = _load_delta_tokens()
return jsonify({
"count": len(tokens),
"keys": list(tokens.keys()),
"exists": len(tokens) > 0,
})
@bp.route("/api/delta/clear", methods=["POST"])
def delta_clear():
"""Discard all stored delta tokens (next scan will be a full scan)."""
try:
if _DELTA_PATH.exists():
_DELTA_PATH.unlink()
except Exception as e:
return jsonify({"error": str(e)}), 500
return jsonify({"status": "cleared"})