# 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()}