"""微信登录状态守卫:后台检测登录状态,提供同步属性与事件通知。 独立后台协程定期调用 detect_login_state(),维护 asyncio.Event 标记当前 登录状态。状态变化时记录日志(避免每轮都输出噪音)。 设计目标: - 后台任务(如 FriendRequestWatcher)在执行 UI 操作前检查 is_logged_in, 未登录时跳过操作,等待登录恢复后自然从上次位置继续处理。 - 游标 / 状态不推进,登录后 DB 中积累的数据自然被重新读取,不丢数据。 - 不替代路由层的登录守卫(routes/send.py 等已有 detect_login_state 前置检查), 仅用于后台长轮询任务的登录前提保护。 """ from __future__ import annotations import asyncio import logging from typing import Optional logger = logging.getLogger("woc-bridge") class LoginGuard: """微信登录状态守卫。 Attributes: _xdotool: XdotoolDriver 实例(调用 detect_login_state) _check_interval: 检测间隔秒数(默认 5.0s) _logged_in_event: asyncio.Event,set 表示当前已登录 _watcher: 后台检测协程 _current_state: 最近一次检测到的登录状态字符串 _last_logged_in: 上次是否登录(用于检测状态变化) """ def __init__( self, xdotool_driver, check_interval: float = 5.0, ) -> None: self._xdotool = xdotool_driver self._check_interval = check_interval self._logged_in_event: asyncio.Event = asyncio.Event() self._watcher: Optional[asyncio.Task] = None self._current_state: str = "unknown" self._last_logged_in: bool = False @property def is_logged_in(self) -> bool: """当前是否已登录(同步属性,取最近一次检测结果)。""" return self._logged_in_event.is_set() @property def current_state(self) -> str: """最近一次检测到的登录状态字符串。""" return self._current_state async def wait_for_login(self, timeout: Optional[float] = None) -> bool: """等待登录状态变为已登录。 Args: timeout: 超时秒数,None 表示无限等待 Returns: True 表示已登录;False 表示超时仍未登录 """ if self._logged_in_event.is_set(): return True try: await asyncio.wait_for( self._logged_in_event.wait(), timeout=timeout ) return True except asyncio.TimeoutError: return False async def start(self) -> None: """启动后台检测协程。""" if self._watcher is None or self._watcher.done(): self._watcher = asyncio.create_task(self._check_loop()) logger.info("LoginGuard 已启动 (check_interval=%.1fs)", self._check_interval) async def stop(self) -> None: """停止后台检测协程。""" if self._watcher is not None and not self._watcher.done(): self._watcher.cancel() try: await self._watcher except asyncio.CancelledError: pass self._watcher = None logger.info("LoginGuard 已停止") async def _check_loop(self) -> None: """后台检测循环:定期调用 detect_login_state 并维护事件状态。""" while True: try: state = await self._xdotool.detect_login_state() logged_in = (state == "logged_in") # 仅在状态变化时记录日志,避免每轮噪音 if logged_in != self._last_logged_in: if logged_in: logger.info( "[LoginGuard] 微信已登录 (state=%s),恢复后台任务", state, ) else: logger.warning( "[LoginGuard] 微信未登录 (state=%s),暂停需要登录的后台任务", state, ) self._last_logged_in = logged_in if logged_in: self._logged_in_event.set() else: self._logged_in_event.clear() self._current_state = state except asyncio.CancelledError: raise except Exception as e: logger.warning("[LoginGuard] 登录状态检测异常: %s", e) self._logged_in_event.clear() self._current_state = "error" await asyncio.sleep(self._check_interval)