feat(OMNY): upload tomo PDF report to the samples web folder

Replaces the old, already-broken upload_last_pon.sh shell-out (and
assorted other dead/abandoned upload attempts left commented out in
flomni.py) with a direct HTTP upload: POSTs the just-written PDF,
base64-encoded, to omny-test.psi.ch/samples/upload.php with
filename="new.pdf" so the server assigns the number itself from its
own counter file -- the same counter newmeasurement.php
(TomoIDManager.register(), called via add_sample_database() just
before write_pdf_report()) already incremented and returned as
self.tomo_id. Runs in a background thread so a slow/unreachable host
never delays the calling tomo_scan(), and cross-checks the response
against the expected filename to flag a possible race with a
concurrent registration.

Added to TomoQueueMixin (OMNY_shared) rather than duplicated in both
LamNI and Flomni, following add_sample_database()'s existing pattern
of shared, setup-agnostic tomo-id/database logic.
This commit is contained in:
x01dc
2026-07-27 16:15:04 +02:00
parent b5655fd6eb
commit cdc20457f1
3 changed files with 83 additions and 17 deletions
@@ -2,7 +2,6 @@ import builtins
import datetime
import json
import os
import subprocess
import time
from pathlib import Path
@@ -2124,11 +2123,9 @@ class LamNI(TomoQueueMixin, LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools
file.write(
f"\nAt-each-angle hook source ('{self.at_each_angle_hook}'):\n{hook_source}"
)
# upload_last_pon.sh no longer works and needs a rewrite -- disabled
# for now (mirrors Flomni, which already has this commented out).
# subprocess.run(
# "xterm /work/sls/spec/local/XOMNY/bin/upload/upload_last_pon.sh &", shell=True
# )
# Replaces the old upload_last_pon.sh script (broken, never rewritten --
# see git history) with a direct HTTP upload to the samples web folder.
self._upload_pdf_report_to_samples(user_target)
# Same tolerance as write_to_scilog(): a session without scilog/logbook
# configured (e.g. a dev/sim session) must not crash report generation
# over the logbook upload -- the PDF itself is already written above.
@@ -39,6 +39,7 @@ import builtins
import datetime
import inspect
import json
import threading
import uuid
from typing import Callable
@@ -390,6 +391,82 @@ class TomoQueueMixin:
user=user,
)
# Only omny-test.psi.ch, not v1p0zyg2w9n2k9c1.myfritz.net (the host
# TomoIDManager.OMNY_URL registers measurements against, and the
# webpage generators mirror their own content to): only
# omny-test.psi.ch has the sample counter that actually matches
# self.tomo_id.
_SAMPLES_UPLOAD_HOSTS = ("https://omny-test.psi.ch",)
def _upload_pdf_report_to_samples(self, pdf_path: str) -> None:
"""Upload a just-written PDF report to the OMNY samples web folder
(see _SAMPLES_UPLOAD_HOSTS). Identical for every setup that uses
``self.tomo_id`` (set via add_sample_database() just before
write_pdf_report() calls this) -- shared here so LamNI/Flomni don't
each carry their own copy. Replaces the old, broken
upload_last_pon.sh script both used to shell out to.
POSTs to <host>/samples/upload.php with filename="new.pdf" so the
server assigns the number itself from its own counter file
(countersaver.txt) -- the same counter newmeasurement.php
(TomoIDManager.register()) already incremented and returned as
self.tomo_id. Any other filename would be saved under samples/png/
instead of directly in samples/, per upload.php's own branching, so
"new.pdf" is the only way to land the PDF at samples/<tomo_id>.pdf
(matching the <A HREF="%d.pdf"> link newmeasurement.php writes into
the samples listing).
Runs in a background daemon thread so a slow/unreachable host never
delays the calling tomo_scan().
"""
def _run():
try:
import base64
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
except ImportError:
print("Could not upload PDF report: 'requests' library not installed.")
return
try:
with open(pdf_path, "rb") as f:
filedata = base64.b64encode(f.read()).decode("ascii")
except OSError as exc:
print(f"Could not read PDF report for upload ({pdf_path}): {exc}")
return
expected_name = f"{self.tomo_id}.pdf"
for host in self._SAMPLES_UPLOAD_HOSTS:
url = f"{host}/samples/upload.php"
try:
r = requests.post(
url,
data={"filename": "new.pdf", "filedata": filedata},
timeout=20,
verify=False, # accept self-signed certs
allow_redirects=False, # SSRF hardening
)
if r.status_code != 200:
print(f"PDF upload to {url} -> HTTP {r.status_code}: {r.text[:120]}")
continue
if expected_name not in r.text:
print(
f"PDF upload to {url} succeeded but the server-assigned "
f"filename doesn't match tomo_id {self.tomo_id} (response: "
f"{r.text[:120]!r}) -- a concurrent measurement "
"registration may have raced this upload."
)
else:
print(f"Uploaded PDF report to {url} as {expected_name}.")
except Exception as exc:
print(f"PDF upload to {url} failed: {exc}")
threading.Thread(target=_run, name="PdfUpload", daemon=True).start()
# ── command-job action registry / dispatch ──────────────────────────────
def _validate_action_kwargs(self, action_name: str, kwargs: dict) -> None:
@@ -3,7 +3,6 @@ import datetime
import json
import os
import random
import subprocess
import time
from pathlib import Path
@@ -3817,16 +3816,9 @@ class Flomni(
file.write(
f"\nAt-each-angle hook source ('{self.at_each_angle_hook}'):\n{hook_source}"
)
# subprocess.run(
# "xterm /work/sls/spec/local/XOMNY/bin/upload/upload_last_pon.sh &", shell=True
# )
# status = subprocess.run(f"cp /tmp/spec-e20131-specES1.pdf {user_target}", shell=True)
# msg = bec.tomo_progress.tomo_progressMessage()
# logo_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "LamNI_logo.png")
# msg.add_file(logo_path).add_text("".join(content).replace("\n", "</p><p>")).add_tag(
# ["BEC", "tomo_parameters", f"dataset_id_{dataset_id}", "flOMNI", self.sample_name]
# )
# self.client.tomo_progress.send_tomo_progress_message("~/data/raw/documentation/tomo_scan_ID_{self.tomo_id}.pdf").send()
# Replaces the old upload_last_pon.sh script (broken, never rewritten)
# with a direct HTTP upload to the samples web folder.
self._upload_pdf_report_to_samples(user_target)
import csaxs_bec
# Ensure this is a Path object, not a string