Remove bug from display manager of missing function
This commit is contained in:
@@ -1811,6 +1811,55 @@ class DisplayManager:
|
||||
# Screenshot capture subsystem
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def _pending_trigger_is_valid(self, meta: Dict) -> bool:
|
||||
"""Return True only for fresh, actionable pending trigger metadata.
|
||||
|
||||
This prevents a stale/corrupt pending flag from permanently blocking
|
||||
periodic updates (meta.json/latest.jpg) if simclient was down or test
|
||||
data left send_immediately=True behind.
|
||||
"""
|
||||
try:
|
||||
if not meta.get('send_immediately'):
|
||||
return False
|
||||
mtype = str(meta.get('type') or '')
|
||||
if mtype not in ('event_start', 'event_stop'):
|
||||
return False
|
||||
mfile = str(meta.get('file') or '').strip()
|
||||
if not mfile:
|
||||
return False
|
||||
file_path = os.path.join(self.screenshot_dir, mfile)
|
||||
if not os.path.exists(file_path):
|
||||
logging.warning(
|
||||
f"Ignoring stale pending screenshot meta: missing file '{mfile}'"
|
||||
)
|
||||
return False
|
||||
|
||||
captured_at_raw = meta.get('captured_at')
|
||||
if not captured_at_raw:
|
||||
return False
|
||||
captured_at = datetime.fromisoformat(str(captured_at_raw).replace('Z', '+00:00'))
|
||||
age_s = (datetime.now(timezone.utc) - captured_at.astimezone(timezone.utc)).total_seconds()
|
||||
|
||||
# Guard against malformed/future timestamps that could lock
|
||||
# the pipeline by appearing permanently "fresh".
|
||||
if age_s < -5:
|
||||
logging.warning(
|
||||
f"Ignoring invalid pending screenshot meta: future captured_at (age={age_s:.1f}s)"
|
||||
)
|
||||
return False
|
||||
|
||||
# Triggered screenshots should be consumed quickly (<= 1s). Use a
|
||||
# generous safety window to avoid false negatives under load.
|
||||
if age_s > 30:
|
||||
logging.warning(
|
||||
f"Ignoring stale pending screenshot meta: type={mtype}, age={age_s:.1f}s"
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _write_screenshot_meta(self, capture_type: str, final_path: str, send_immediately: bool = False):
|
||||
"""Write screenshots/meta.json atomically so simclient can detect new captures.
|
||||
|
||||
@@ -1824,55 +1873,6 @@ class DisplayManager:
|
||||
send_immediately: True for triggered (event) captures, False for periodic ones
|
||||
"""
|
||||
try:
|
||||
def _pending_trigger_is_valid(meta: Dict) -> bool:
|
||||
"""Return True only for fresh, actionable pending trigger metadata.
|
||||
|
||||
This prevents a stale/corrupt pending flag from permanently blocking
|
||||
periodic updates (meta.json/latest.jpg) if simclient was down or test
|
||||
data left send_immediately=True behind.
|
||||
"""
|
||||
try:
|
||||
if not meta.get('send_immediately'):
|
||||
return False
|
||||
mtype = str(meta.get('type') or '')
|
||||
if mtype not in ('event_start', 'event_stop'):
|
||||
return False
|
||||
mfile = str(meta.get('file') or '').strip()
|
||||
if not mfile:
|
||||
return False
|
||||
file_path = os.path.join(self.screenshot_dir, mfile)
|
||||
if not os.path.exists(file_path):
|
||||
logging.warning(
|
||||
f"Ignoring stale pending screenshot meta: missing file '{mfile}'"
|
||||
)
|
||||
return False
|
||||
|
||||
captured_at_raw = meta.get('captured_at')
|
||||
if not captured_at_raw:
|
||||
return False
|
||||
captured_at = datetime.fromisoformat(str(captured_at_raw).replace('Z', '+00:00'))
|
||||
age_s = (datetime.now(timezone.utc) - captured_at.astimezone(timezone.utc)).total_seconds()
|
||||
|
||||
# Guard against malformed/future timestamps that could lock
|
||||
# the pipeline by appearing permanently "fresh".
|
||||
if age_s < -5:
|
||||
logging.warning(
|
||||
f"Ignoring invalid pending screenshot meta: future captured_at (age={age_s:.1f}s)"
|
||||
)
|
||||
return False
|
||||
|
||||
# Triggered screenshots should be consumed quickly (<= 1s). Use a
|
||||
# generous safety window to avoid false negatives under load.
|
||||
if age_s > 30:
|
||||
logging.warning(
|
||||
f"Ignoring stale pending screenshot meta: type={mtype}, age={age_s:.1f}s"
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
meta_path = os.path.join(self.screenshot_dir, 'meta.json')
|
||||
|
||||
# PROTECTION: Don't overwrite pending event-triggered metadata with periodic capture
|
||||
@@ -1882,7 +1882,7 @@ class DisplayManager:
|
||||
with open(meta_path, 'r', encoding='utf-8') as f:
|
||||
existing_meta = json.load(f)
|
||||
# If there's a pending event-triggered capture, skip this periodic write
|
||||
if _pending_trigger_is_valid(existing_meta):
|
||||
if self._pending_trigger_is_valid(existing_meta):
|
||||
logging.debug(f"Skipping periodic meta.json to preserve pending {existing_meta.get('type')} (send_immediately=True)")
|
||||
return
|
||||
except Exception:
|
||||
@@ -2096,29 +2096,29 @@ class DisplayManager:
|
||||
|
||||
# Maintain latest.jpg as an atomic copy so readers never see a missing
|
||||
# or broken pointer while a new screenshot is being published.
|
||||
# PROTECTION: Don't update latest.jpg for periodic captures if event-triggered is pending
|
||||
should_update_latest = True
|
||||
if capture_type == "periodic":
|
||||
try:
|
||||
meta_path = os.path.join(self.screenshot_dir, 'meta.json')
|
||||
if os.path.exists(meta_path):
|
||||
with open(meta_path, 'r', encoding='utf-8') as f:
|
||||
existing_meta = json.load(f)
|
||||
# If there's a pending event-triggered capture, don't update latest.jpg
|
||||
if _pending_trigger_is_valid(existing_meta):
|
||||
should_update_latest = False
|
||||
logging.debug(f"Skipping latest.jpg update to preserve pending {existing_meta.get('type')} screenshot")
|
||||
except Exception:
|
||||
pass # If we can't read meta, proceed with updating latest.jpg
|
||||
|
||||
latest_link = os.path.join(self.screenshot_dir, 'latest.jpg')
|
||||
if should_update_latest:
|
||||
try:
|
||||
latest_tmp = os.path.join(self.screenshot_dir, 'latest.jpg.tmp')
|
||||
shutil.copyfile(final_path, latest_tmp)
|
||||
os.replace(latest_tmp, latest_link)
|
||||
except Exception as e:
|
||||
logging.debug(f"Could not update latest.jpg: {e}")
|
||||
# PROTECTION: Don't update latest.jpg for periodic captures if event-triggered is pending
|
||||
should_update_latest = True
|
||||
if capture_type == "periodic":
|
||||
try:
|
||||
meta_path = os.path.join(self.screenshot_dir, 'meta.json')
|
||||
if os.path.exists(meta_path):
|
||||
with open(meta_path, 'r', encoding='utf-8') as f:
|
||||
existing_meta = json.load(f)
|
||||
# If there's a pending event-triggered capture, don't update latest.jpg
|
||||
if self._pending_trigger_is_valid(existing_meta):
|
||||
should_update_latest = False
|
||||
logging.debug(f"Skipping latest.jpg update to preserve pending {existing_meta.get('type')} screenshot")
|
||||
except Exception:
|
||||
pass # If we can't read meta, proceed with updating latest.jpg
|
||||
|
||||
latest_link = os.path.join(self.screenshot_dir, 'latest.jpg')
|
||||
if should_update_latest:
|
||||
try:
|
||||
latest_tmp = os.path.join(self.screenshot_dir, 'latest.jpg.tmp')
|
||||
shutil.copyfile(final_path, latest_tmp)
|
||||
os.replace(latest_tmp, latest_link)
|
||||
except Exception as e:
|
||||
logging.debug(f"Could not update latest.jpg: {e}")
|
||||
|
||||
# Rotate old screenshots
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user