Source code for controller.utils.interruptor_watcher

# -*- 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()