Remove bug from display manager of missing function
This commit is contained in:
@@ -1811,6 +1811,55 @@ class DisplayManager:
|
|||||||
# Screenshot capture subsystem
|
# 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):
|
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.
|
"""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
|
send_immediately: True for triggered (event) captures, False for periodic ones
|
||||||
"""
|
"""
|
||||||
try:
|
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')
|
meta_path = os.path.join(self.screenshot_dir, 'meta.json')
|
||||||
|
|
||||||
# PROTECTION: Don't overwrite pending event-triggered metadata with periodic capture
|
# 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:
|
with open(meta_path, 'r', encoding='utf-8') as f:
|
||||||
existing_meta = json.load(f)
|
existing_meta = json.load(f)
|
||||||
# If there's a pending event-triggered capture, skip this periodic write
|
# 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)")
|
logging.debug(f"Skipping periodic meta.json to preserve pending {existing_meta.get('type')} (send_immediately=True)")
|
||||||
return
|
return
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -2096,29 +2096,29 @@ class DisplayManager:
|
|||||||
|
|
||||||
# Maintain latest.jpg as an atomic copy so readers never see a missing
|
# Maintain latest.jpg as an atomic copy so readers never see a missing
|
||||||
# or broken pointer while a new screenshot is being published.
|
# or broken pointer while a new screenshot is being published.
|
||||||
# PROTECTION: Don't update latest.jpg for periodic captures if event-triggered is pending
|
# PROTECTION: Don't update latest.jpg for periodic captures if event-triggered is pending
|
||||||
should_update_latest = True
|
should_update_latest = True
|
||||||
if capture_type == "periodic":
|
if capture_type == "periodic":
|
||||||
try:
|
try:
|
||||||
meta_path = os.path.join(self.screenshot_dir, 'meta.json')
|
meta_path = os.path.join(self.screenshot_dir, 'meta.json')
|
||||||
if os.path.exists(meta_path):
|
if os.path.exists(meta_path):
|
||||||
with open(meta_path, 'r', encoding='utf-8') as f:
|
with open(meta_path, 'r', encoding='utf-8') as f:
|
||||||
existing_meta = json.load(f)
|
existing_meta = json.load(f)
|
||||||
# If there's a pending event-triggered capture, don't update latest.jpg
|
# If there's a pending event-triggered capture, don't update latest.jpg
|
||||||
if _pending_trigger_is_valid(existing_meta):
|
if self._pending_trigger_is_valid(existing_meta):
|
||||||
should_update_latest = False
|
should_update_latest = False
|
||||||
logging.debug(f"Skipping latest.jpg update to preserve pending {existing_meta.get('type')} screenshot")
|
logging.debug(f"Skipping latest.jpg update to preserve pending {existing_meta.get('type')} screenshot")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # If we can't read meta, proceed with updating latest.jpg
|
pass # If we can't read meta, proceed with updating latest.jpg
|
||||||
|
|
||||||
latest_link = os.path.join(self.screenshot_dir, 'latest.jpg')
|
latest_link = os.path.join(self.screenshot_dir, 'latest.jpg')
|
||||||
if should_update_latest:
|
if should_update_latest:
|
||||||
try:
|
try:
|
||||||
latest_tmp = os.path.join(self.screenshot_dir, 'latest.jpg.tmp')
|
latest_tmp = os.path.join(self.screenshot_dir, 'latest.jpg.tmp')
|
||||||
shutil.copyfile(final_path, latest_tmp)
|
shutil.copyfile(final_path, latest_tmp)
|
||||||
os.replace(latest_tmp, latest_link)
|
os.replace(latest_tmp, latest_link)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.debug(f"Could not update latest.jpg: {e}")
|
logging.debug(f"Could not update latest.jpg: {e}")
|
||||||
|
|
||||||
# Rotate old screenshots
|
# Rotate old screenshots
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user