From ee7326a6ebe2a34f59dfb67ddee64102a769af23 Mon Sep 17 00:00:00 2001 From: christian Date: Wed, 21 Oct 2026 14:40:18 -0400 Subject: [PATCH] PROP OUT looks in this weekend's Location Assets folder, read on NA Ingest - prop_bundle_folder takes {date} (the plan's weekend, as the tracks folders): any bundle there counts - readiness scripts for this Mac run here (NA ProP's Google Drive showed 261025 empty) Co-Authored-By: Claude Opus 5.5 --- config.json | 4 ++-- readiness.py | 30 +++++++++++++++++++++++------- schema.py | 3 ++- tests/test_readiness.py | 21 ++++++++++++++++++--- 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/config.json b/config.json index 3048b17..d3a924e 100644 --- a/config.json +++ b/config.json @@ -935,8 +935,8 @@ "pco_module": "pco-plan", "pco_service_type": "275974", "projects": "~/Library/CloudStorage/GoogleDrive-media@waterschurch.org/Shared drives/Waters Creative/Ableton/Projects", - "prop_bundle_folder": "~/Library/CloudStorage/GoogleDrive-media@waterschurch.org/Shared drives/Location Assets/ProPresenter", - "prop_bundle_host": "prop", + "prop_bundle_folder": "~/Library/CloudStorage/GoogleDrive-media@waterschurch.org/Shared drives/Location Assets/{date}", + "prop_bundle_host": "ingest", "prop_bundle_pattern": "*.probundle", "prop_host": "prop", "prop_lists": [ diff --git a/readiness.py b/readiness.py index 9aac522..26faada 100644 --- a/readiness.py +++ b/readiness.py @@ -137,9 +137,11 @@ def ssh_argv(cfg, host): def run_remote(cfg, host, script, timeout=60): - """Run a Python script on another Mac (on stdin, at low priority); its last line is JSON.""" - r = subprocess.run(ssh_argv(cfg, host) + ["nice -n 19 /usr/bin/python3 -"], input=script, - capture_output=True, text=True, timeout=timeout) + """Run a Python script on another Mac (on stdin, at low priority), or here when `host` is this Mac + (NA Ingest's Google Drive is current; NA ProP's lagged a day behind, 2026-10-21); its last line is JSON.""" + here = ((cfg.get("hosts") or {}).get(host) or {}).get("self") + argv = ["/usr/bin/nice", "-n", "19", "/usr/bin/python3", "-"] if here else ssh_argv(cfg, host) + ["nice -n 19 /usr/bin/python3 -"] + r = subprocess.run(argv, input=script, capture_output=True, text=True, timeout=timeout) if r.returncode == 255: raise ReadyError("%s UNREACHABLE" % host.upper()) lines = [ln for ln in r.stdout.strip().splitlines() if ln.strip()] @@ -971,8 +973,21 @@ print(json.dumps(sorted([[os.path.basename(f), os.path.getmtime(f)] for f in fil ''' -def eval_prop_bundle(rc, files, since): +def prop_bundle_folder(rc, plan): + """Where this weekend's bundle goes: "{date}" is the weekend's folder (Location Assets/261025, the + user 2026-10-21: "it should be looking at the latest weekends folder in location assets").""" + return str(rc.get("prop_bundle_folder", "")).replace("{date}", datetime.fromtimestamp(plan["time"]).strftime("%y%m%d")) + + +def eval_prop_bundle(rc, files, since, folder=None): line = Line() + if "{date}" in str(rc.get("prop_bundle_folder", "")): + # This weekend's own folder: any bundle in it is this weekend's. + if not files: + line.fault(ERR, "NOT IN " + os.path.basename(str(folder or "").rstrip("/")), "no %s in %s" % (rc.get("prop_bundle_pattern"), folder)) + else: + line.info = "SENT " + datetime.fromtimestamp(files[0][1]).strftime("%a %H:%M").upper() + return line fresh = [f for f in files if f[1] >= since] if not fresh: last = datetime.fromtimestamp(files[0][1]).strftime("%-m/%-d") if files else "NEVER" @@ -1021,8 +1036,8 @@ def look(cfg, now=None, data_dir=None, sources=None): resolume=lambda: http_json(rc.get("resolume_url", "").rstrip("/") + "/api/v1/composition", timeout=6), macros=lambda paths: run_remote(cfg, rc["prop_host"], MACRO_SCRIPT % json.dumps({"paths": paths}), timeout=60), companion=lambda n: companion_page(cfg, n), - prop_bundle=lambda: run_remote(cfg, rc["prop_bundle_host"], BUNDLE_SCRIPT % json.dumps( - {"folder": rc.get("prop_bundle_folder", ""), "pattern": rc.get("prop_bundle_pattern", "*.probundle")}), timeout=30)) + prop_bundle=lambda plan: run_remote(cfg, rc["prop_bundle_host"], BUNDLE_SCRIPT % json.dumps( + {"folder": prop_bundle_folder(rc, plan), "pattern": rc.get("prop_bundle_pattern", "*.probundle")}), timeout=30)) src.update(sources or {}) lines = {} @@ -1103,7 +1118,8 @@ def look(cfg, now=None, data_dir=None, sources=None): lines["mics"] = mics if rc.get("prop_bundle_host"): try: - lines["prop_bundle"] = eval_prop_bundle(rc, src["prop_bundle"](), week_start(now, rc.get("reset_day", "mon"))) + lines["prop_bundle"] = eval_prop_bundle(rc, src["prop_bundle"](plan), week_start(now, rc.get("reset_day", "mon")), + prop_bundle_folder(rc, plan)) except Exception as e: # noqa: BLE001 failed(["prop_bundle"], e) out = {"time": now, "items": {k: v.as_dict() for k, v in lines.items()}} diff --git a/schema.py b/schema.py index a2633a3..18fbad5 100644 --- a/schema.py +++ b/schema.py @@ -399,7 +399,8 @@ field("ready.resolume_deck", "str", "", "The deck Resolume must be on.", "Ready" field("ready.prop_host", "str", None, "The ProPresenter computer: its macros and presentation files are read to find " "the Resolume columns Sunday's slides call up.", "Ready") field("ready.prop_bundle_host", "str", None, "The computer that saves the ProPresenter bundle.", "Ready") -field("ready.prop_bundle_folder", "path", "", "Where the bundle for the locations goes.", "Ready") +field("ready.prop_bundle_folder", "path", "", "Where the bundle for the locations goes. {date} is this weekend's folder " + "(Location Assets/{date} -> Location Assets/261025): any bundle in it counts as sent.", "Ready") field("ready.prop_bundle_pattern", "str", "*.probundle", "The bundle's file name pattern.", "Ready", advanced=True) field("ready.vocal_positions", "list", ["Vocalist", "[Leader]"], "PCO positions that need a vocal mic.", "Ready") field("ready.mic_positions", "dict", {}, "Other PCO positions and the mic they need (MC -> MC).", "Ready") diff --git a/tests/test_readiness.py b/tests/test_readiness.py index 83a1209..b36a5f8 100644 --- a/tests/test_readiness.py +++ b/tests/test_readiness.py @@ -338,10 +338,19 @@ class TestLineupCrossBundle(unittest.TestCase): def test_the_macro_reader_runs(self): compile(rd.MACRO_SCRIPT % json.dumps({"paths": ["/x.pro"]}), "remote", "exec") + def test_prop_bundle_in_this_weekends_folder(self): + rc = dict(RC, prop_bundle_folder="~/Shared drives/Location Assets/{date}") + plan = {"time": datetime(2026, 10, 25, 9, 0).timestamp()} + folder = rd.prop_bundle_folder(rc, plan) + self.assertEqual(folder, "~/Shared drives/Location Assets/261025") + self.assertEqual(msgs(rd.eval_prop_bundle(rc, [["WORSHIP BUNDLE.probundle", WEDNESDAY - 86400 * 10]], WEDNESDAY, folder)), []) + self.assertEqual(msgs(rd.eval_prop_bundle(rc, [], WEDNESDAY, folder)), ["NOT IN 261025"]) + def test_prop_bundle_this_week(self): since = rd.week_start(WEDNESDAY) - self.assertEqual(msgs(rd.eval_prop_bundle(RC, [["Message Templates.probundle", WEDNESDAY - 3600]], since)), []) - self.assertEqual(msgs(rd.eval_prop_bundle(RC, [["Old.probundle", since - 86400]], since)), ["NOT SENT (LAST 10/11)"]) + rc = dict(RC, prop_bundle_folder="~/Shared drives/Location Assets/ProPresenter") # one folder for every week + self.assertEqual(msgs(rd.eval_prop_bundle(rc, [["Message Templates.probundle", WEDNESDAY - 3600]], since)), []) + self.assertEqual(msgs(rd.eval_prop_bundle(rc, [["Old.probundle", since - 86400]], since)), ["NOT SENT (LAST 10/11)"]) def test_mics_from_pco(self): self.assertEqual(rd.mics_needed(RC, plan()), "2 VOX MC TIM") @@ -351,7 +360,7 @@ class TestLook(unittest.TestCase): def sources(self, **over): src = dict(pco=plan, prop=lambda p: prop(), ableton=lambda p: ableton(), lineup=lambda: lineup(["Jonah Perdue", "Krista Perdue", "Kevin Michel", "Anthony Hoyt", "Tim Hatch"]), - resolume=composition, prop_bundle=lambda: [["Message Templates.probundle", WEDNESDAY]], + resolume=composition, prop_bundle=lambda plan: [["Message Templates.probundle", WEDNESDAY]], macros=macros_found, companion=lambda n: COMPANION.get(n, {})) src.update(over) return src @@ -609,3 +618,9 @@ class TestOtherCampuses(unittest.TestCase): finally: rd.http_json, rd.pco_get, rd.pco_login = real_http, real_get, real_login self.assertEqual(len([c for c in calls if "/1113969/" in c[1] and c[0] == "POST"]), len(res["songs"])) # one note per location song + + +class TestRunHere(unittest.TestCase): + def test_a_script_for_this_mac_runs_here_without_ssh(self): + cfg = {"hosts": {"ingest": {"self": True}}} + self.assertEqual(rd.run_remote(cfg, "ingest", "import json; print(json.dumps({'ok': 1}))"), {"ok": 1})