Source code for labfrog.offline_queue

# SPDX-FileCopyrightText: 2026 Helmholtz-Zentrum Dresden-Rossendorf e.V (HZDR)
# SPDX-License-Identifier: Apache-2.0

"""The page that shows what is waiting to reach MongoDB, and sends it."""

from __future__ import annotations

import logging

from flask import Blueprint, flash, redirect, render_template, request, url_for
from flask_login import login_required

from labfrog.db import get_db
from labfrog.offline_spool import (
    database_is_reachable,
    load_pending,
    pending_count,
    replay_pending,
    safe_reason,
    spool_dir,
)

blueprint = Blueprint("offline_queue", __name__)


[docs] @blueprint.route("/offline_queue", methods=["GET"]) @login_required def offline_queue(): """List the entries saved on this computer while the database was away.""" entries = load_pending() reachable, reason = (False, "not checked") try: reachable, reason = database_is_reachable(get_db()) except Exception as exc: logging.exception("Offline queue could not check the database") reason = safe_reason(str(exc)) return render_template( "offline_queue.html", entries=entries, spool_dir=str(spool_dir()), database_reachable=reachable, database_reason=reason, )
[docs] @blueprint.route("/offline_queue/sync", methods=["POST"]) @login_required def sync_offline_queue(): """Send the queued entries to MongoDB, keeping anything that will not go.""" try: shots = get_db()["shots"] except Exception as exc: logging.exception("Offline sync could not reach the database") flash( f"Still cannot reach the database, so nothing was sent. " f"Your entries are untouched. ({safe_reason(str(exc))})", "error", ) return redirect(url_for("offline_queue.offline_queue")) reachable, reason = database_is_reachable(get_db()) if not reachable: flash( f"Still cannot reach the database, so nothing was sent. " f"Your entries are untouched. ({reason})", "error", ) return redirect(url_for("offline_queue.offline_queue")) results = replay_pending(shots) synced = results["synced_count"] failed = results["failed_count"] if synced and not failed: flash( f"Sent {synced} queued entr{'y' if synced == 1 else 'ies'} to the " "database. They were moved to the 'synced' folder rather than " "deleted, so you can still check them.", "success", ) elif synced and failed: flash( f"Sent {synced} entr{'y' if synced == 1 else 'ies'}, but {failed} " "could not be sent and are still queued. The reason is saved next " "to each one.", "warning", ) elif failed: flash( f"{failed} queued entr{'y' if failed == 1 else 'ies'} could not be " "sent. Nothing was lost; the reason is saved next to each one.", "error", ) else: flash("Nothing was waiting to be sent.", "info") return redirect(url_for("offline_queue.offline_queue"))
[docs] def offline_queue_banner_state() -> dict: """Return the small banner state every data-entry page shows. Kept cheap: it counts files, it does not touch MongoDB, because this runs on pages that must render even when the database is the thing that is broken. """ try: count = pending_count() except Exception: return {"pending": 0, "url": None} return { "pending": count, "url": url_for("offline_queue.offline_queue") if count else None, }
[docs] @blueprint.app_context_processor def inject_offline_queue_state(): """Expose the queue state to templates without each route passing it.""" if request.blueprint == "offline_queue": return {"offline_queue_state": {"pending": 0, "url": None}} return {"offline_queue_state": offline_queue_banner_state()}