# -*- coding: utf-8 -*-
"""
:File: EuljiroWorship/controller/utils/interruptor_watcher.py
:Author: Benjamin Jaedon Choi - https://github.com/saintbenjamin
:Affiliated Church: The Eulji-ro Presbyterian Church [대한예수교장로회(통합) 을지로교회]
:Address: The Eulji-ro Presbyterian Church, 24-10, Eulji-ro 20-gil, Jung-gu, Seoul 04549, South Korea
:Telephone: +82-2-2266-3070
:E-mail: euljirochurch [at] G.M.A.I.L. (replace [at] with @ and G.M.A.I.L as you understood.)
:License: MIT License with Attribution Requirement (see LICENSE file for details); Copyright (c) 2025 The Eulji-ro Presbyterian Church.
Watches :py:data:`core.config.paths.VERSE_FILE` and emits a `Qt <https://doc.qt.io/qt-6/index.html>`_ signal when the emergency text is cleared.
This module defines a lightweight polling watcher used by the slide controller.
It monitors the emergency verse file (:py:data:`core.config.paths.VERSE_FILE`) and detects the state
transition from **non-empty** to **empty**. When that transition occurs, it emits
``InterruptorWatcher.interruptor_cleared``, allowing the controller to restore the
previous slide session and exit emergency mode.
Key behavior:
- Polls the verse file at a configurable interval (``poll_interval``)
- Emits a signal only on the transition: ``non-empty -> empty``
- Provides a stop mechanism for clean thread shutdown
"""
import os
import time
from PySide6.QtCore import QObject, Signal
from controller.utils.slide_controller_data_manager import SlideControllerDataManager
from core.config import paths
[docs]
class InterruptorWatcher(QObject):
"""
Monitors the emergency verse output file and emits a signal when it is cleared.
This watcher is designed to run inside a `QThread <https://doc.qt.io/qt-6/qthread.html>`_ loop (polling-based).
It reads :py:data:`core.config.paths.VERSE_FILE` periodically and tracks the last observed content.
When the file transitions from non-empty content to an empty string, it emits
``interruptor_cleared``.
Attributes:
interruptor_cleared (Signal):
Emitted when the verse file becomes empty after previously containing text.
"""
interruptor_cleared = Signal()
[docs]
def __init__(self, poll_interval=1):
"""
Initialize the watcher.
Args:
poll_interval (int | float):
Time in seconds between file polls.
Returns:
None
"""
super().__init__()
self.verse_file = paths.VERSE_FILE
self.poll_interval = poll_interval
self._running = True
self._last_content = None
[docs]
def stop(self):
"""
Stop the watcher loop.
This method signals the polling loop in ``run()`` to exit cleanly.
It is intended to be called during controller shutdown.
Args:
None
Returns:
None
"""
self._running = False
[docs]
def run(self):
"""
Start monitoring the verse output file.
This method runs a polling loop while ``_running`` is True:
- If the verse file exists, read and strip its content.
- If the content transitions from non-empty to empty, emit ``interruptor_cleared``.
- Sleep for ``poll_interval`` seconds between polls.
Note:
- This is a polling-based watcher (not filesystem event-based).
- Intended to be executed in a background `QThread <https://doc.qt.io/qt-6/qthread.html>`_.
Args:
None
Returns:
None
"""
while self._running:
try:
if os.path.exists(self.verse_file):
with open(self.verse_file, "r", encoding="utf-8") as f:
content = f.read().strip()
# Detect transition from non-empty to empty
if content == "" and self._last_content not in (None, ""):
print("[✓] Detected interruptor cleared.")
self.interruptor_cleared.emit()
self._last_content = content
time.sleep(self.poll_interval)
except Exception as e:
print("[!] InterruptorWatcher error:", e)
[docs]
def restore_last_slide(self):
"""
Restore the previous slide state after emergency caption is cleared.
This is a thin helper that delegates restoration to :class:`controller.utils.slide_controller_data_manager.SlideControllerDataManager`.
It is not used directly by the watcher loop unless called externally.
Args:
None
Returns:
None
"""
slide_manager = SlideControllerDataManager(paths.SLIDE_FILE)
slide_manager.restore_last_slide()