WechatOnCloud/bridge/woc_bridge/messaging/login_guard.py

128 lines
4.6 KiB
Python
Raw Normal View History

"""微信登录状态守卫:后台检测登录状态,提供同步属性与事件通知。
独立后台协程定期调用 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.Eventset 表示当前已登录
_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)