ATEM: read the saved-file folder through this Mac's own Remote Login when macOS keeps the page out of Google Drive
A launchd-run python3 gets 'Operation not permitted' on ~/Library/CloudStorage; sshd does not. The page now lists and reads the folder over ssh to localhost with the panel's own key when the local read is blocked (the Ingest Mac's Setup card gains that authorise step). Pick the newest verified on NA Ingest. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+58
-13
@@ -19,6 +19,7 @@ import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -645,15 +646,9 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
if path == "/api/atem-reference/folder":
|
||||
folder = os.path.expanduser((site.cfg.get("atem") or {}).get("source_folder") or "")
|
||||
mounted = bool(folder and os.path.isdir(folder))
|
||||
files = sorted(glob.glob(os.path.join(folder, "*.xml")), key=os.path.getmtime, reverse=True) if mounted else []
|
||||
blocked = False
|
||||
if mounted and not files:
|
||||
try:
|
||||
blocked = not os.listdir(folder) and "/Library/CloudStorage/" in folder # a cloud folder that looks empty to a background process
|
||||
except OSError:
|
||||
blocked = True
|
||||
return self.send(200, {"folder": folder, "mounted": mounted, "blocked": blocked,
|
||||
"files": [{"name": os.path.basename(f), "when": os.path.getmtime(f)} for f in files[:10]]})
|
||||
files, via, blocked = cloud_files(site.cfg, folder) if mounted else ([], "", False)
|
||||
return self.send(200, {"folder": folder, "mounted": mounted, "blocked": blocked, "via": via,
|
||||
"files": [{"name": n, "when": t} for n, t in files[:10]]})
|
||||
if path == "/api/live/midi":
|
||||
snap = site.read_json("snapshot.json", {}) or {}
|
||||
return self.send(200, {"local": snap.get("midi"), "remote": snap.get("remote_midi")})
|
||||
@@ -865,11 +860,15 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
return self.send(200, {"draft": self.masked_draft(draft), "from": src, "errors": errors, "warnings": warnings})
|
||||
if path == "/api/atem-reference/pick-newest":
|
||||
folder = os.path.expanduser((site.cfg.get("atem") or {}).get("source_folder") or "")
|
||||
files = sorted(glob.glob(os.path.join(folder, "*.xml")), key=os.path.getmtime) if folder and os.path.isdir(folder) else []
|
||||
files, via, blocked = cloud_files(site.cfg, folder) if folder and os.path.isdir(folder) else ([], "", False)
|
||||
if not files:
|
||||
return self.send(404, {"error": "no .xml files in the folder (is Google Drive signed in?)"})
|
||||
with open(files[-1], "rb") as f:
|
||||
return self.send(200, dict(self.take_atem(f.read()), file=os.path.basename(files[-1])))
|
||||
return self.send(404, {"error": "no .xml files in the folder (is Google Drive signed in?)" if not blocked else
|
||||
"this page is not allowed to read the cloud folder; use Upload a saved file"})
|
||||
newest = os.path.join(folder, files[0][0])
|
||||
raw = cloud_read(site.cfg, newest, via)
|
||||
if raw is None:
|
||||
return self.send(502, {"error": "could not read %s" % files[0][0]})
|
||||
return self.send(200, dict(self.take_atem(raw), file=files[0][0]))
|
||||
if path == "/api/atem-reference/discard":
|
||||
self.discard_atem()
|
||||
return self.send(200, {"ok": True})
|
||||
@@ -1109,6 +1108,52 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
return "http://%s:%d/" % (host, self.server.server_address[1])
|
||||
|
||||
|
||||
def self_ssh(cfg):
|
||||
"""ssh to this very Mac with the panel's own key: sshd may read the cloud folders a background
|
||||
program is kept out of (macOS privacy), and the key is already on this Mac."""
|
||||
key = os.path.expanduser(cfg.get("ssh_key", "~/.ssh/id_status_panel"))
|
||||
return ["/usr/bin/ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=4", "-o", "StrictHostKeyChecking=accept-new", "-i", key, "localhost"]
|
||||
|
||||
|
||||
def cloud_files(cfg, folder):
|
||||
"""(files [(name, mtime)] newest first, how, blocked). Reads the folder here; when macOS keeps this process
|
||||
out of a cloud folder, asks this Mac over ssh instead."""
|
||||
try:
|
||||
names = [n for n in os.listdir(folder) if n.lower().endswith(".xml")]
|
||||
files = sorted(((n, os.path.getmtime(os.path.join(folder, n))) for n in names), key=lambda x: x[1], reverse=True)
|
||||
if files or "/Library/CloudStorage/" not in folder:
|
||||
return files, "local", False
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
r = subprocess.run(self_ssh(cfg) + ["cd %s && stat -f '%%m %%N' *.xml" % shlex.quote(folder)], capture_output=True, text=True, timeout=15)
|
||||
if r.returncode == 0:
|
||||
files = []
|
||||
for line in r.stdout.splitlines():
|
||||
m, _, n = line.partition(" ")
|
||||
if n.lower().endswith(".xml"):
|
||||
files.append((n, float(m)))
|
||||
return sorted(files, key=lambda x: x[1], reverse=True), "ssh", False
|
||||
log.warning("cloud folder over ssh: %s", (r.stderr or r.stdout).strip()[-200:])
|
||||
except (OSError, subprocess.TimeoutExpired, ValueError) as e:
|
||||
log.warning("cloud folder over ssh: %s", e)
|
||||
return [], "", True
|
||||
|
||||
|
||||
def cloud_read(cfg, path, via):
|
||||
if via != "ssh":
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
return f.read()
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
r = subprocess.run(self_ssh(cfg) + ["cat %s" % shlex.quote(path)], capture_output=True, timeout=30)
|
||||
return r.stdout if r.returncode == 0 else None
|
||||
except (OSError, subprocess.TimeoutExpired):
|
||||
return None
|
||||
|
||||
|
||||
class Server(http.server.ThreadingHTTPServer):
|
||||
daemon_threads = True
|
||||
allow_reuse_address = True
|
||||
|
||||
Reference in New Issue
Block a user