"""Updater routes - GitHub update checking and application updates.""" from __future__ import annotations from flask import Blueprint, Response, jsonify, request from utils.logging import get_logger from utils.responses import api_error from utils.updater import ( check_for_updates, dismiss_update, get_update_status, perform_update, restart_application, ) logger = get_logger("intercept.routes.updater") updater_bp = Blueprint("updater", __name__, url_prefix="/updater") @updater_bp.route("/check", methods=["GET"]) def check_updates() -> Response: """ Check for updates from GitHub. Uses caching to avoid excessive API calls. Will only hit GitHub if the cache is stale (default: 6 hours). Query parameters: force: Set to 'true' to bypass cache and check GitHub directly Returns: JSON with update status information """ force = request.args.get("force", "").lower() == "true" try: result = check_for_updates(force=force) return jsonify(result) except Exception as e: logger.error(f"Error checking for updates: {e}") return api_error(str(e), 500) @updater_bp.route("/status", methods=["GET"]) def update_status() -> Response: """ Get current update status from cache. This endpoint does NOT trigger a GitHub check - it only returns cached data. Use /check to trigger a fresh check. Returns: JSON with cached update status """ try: result = get_update_status() return jsonify(result) except Exception as e: logger.error(f"Error getting update status: {e}") return api_error(str(e), 500) @updater_bp.route("/update", methods=["POST"]) def do_update() -> Response: """ Perform a git pull to update the application. Request body (JSON): stash_changes: If true, stash local changes before pulling Returns: JSON with update result information """ data = request.json or {} stash_changes = data.get("stash_changes", False) try: result = perform_update(stash_changes=stash_changes) if result.get("success"): return jsonify(result) else: # Return appropriate status code based on error type error = result.get("error", "") if error == "local_changes": return jsonify(result), 409 # Conflict elif error == "merge_conflict": return jsonify(result), 409 elif result.get("manual_update"): return jsonify(result), 400 else: return jsonify(result), 500 except Exception as e: logger.error(f"Error performing update: {e}") return api_error(str(e), 500) @updater_bp.route("/dismiss", methods=["POST"]) def dismiss_notification() -> Response: """ Dismiss update notification for a specific version. The notification will not be shown again until a newer version is available. Request body (JSON): version: The version to dismiss notifications for Returns: JSON with success status """ data = request.json or {} version = data.get("version") if not version: return api_error("Version is required", 400) try: result = dismiss_update(version) return jsonify(result) except Exception as e: logger.error(f"Error dismissing update: {e}") return api_error(str(e), 500) @updater_bp.route("/restart", methods=["POST"]) def restart_app() -> Response: """ Restart the application. This endpoint triggers a graceful restart of the application: 1. Stops all running decoder processes 2. Cleans up global state 3. Replaces the current process with a fresh instance The response may not be received by the client since the process is replaced immediately. Clients should poll /health until the server responds again. Returns: JSON with restart status (may not be delivered) """ import threading logger.info("Restart requested via API") # Send response before restarting # Use a short delay to allow the response to be sent def delayed_restart(): import time time.sleep(0.5) # Allow response to be sent restart_application() # Start restart in a background thread so we can return a response restart_thread = threading.Thread(target=delayed_restart, daemon=False) restart_thread.start() return jsonify({"success": True, "message": "Application is restarting. Please wait...", "action": "restart"})