新增BlueBubbles适配器全套核心工具类与服务,包含会话管理、消息去重、Webhook验证、缓存系统、账号配置解析、聊天消息处理等完整功能模块,支持iMessage消息收发、群管理、反应特效、语音合成等能力,提供完善的健康检查与配置校验流程。
113 lines
3.3 KiB
Python
113 lines
3.3 KiB
Python
from __future__ import annotations
|
|
|
|
import time
|
|
from typing import Any
|
|
|
|
from yuxi.utils.logging_config import logger
|
|
|
|
|
|
class UnifiedCache:
|
|
def __init__(self, name: str, max_entries: int = 2048, ttl_seconds: float = 600.0):
|
|
self.name = name
|
|
self._max_entries = max_entries
|
|
self._ttl_seconds = ttl_seconds
|
|
self._cache: dict[str, tuple[Any, float]] = {}
|
|
self._hits = 0
|
|
self._misses = 0
|
|
self._evictions = 0
|
|
|
|
def get(self, key: str) -> Any | None:
|
|
entry = self._cache.get(key)
|
|
if entry is None:
|
|
self._misses += 1
|
|
return None
|
|
value, ts = entry
|
|
if time.monotonic() - ts > self._ttl_seconds:
|
|
del self._cache[key]
|
|
self._evictions += 1
|
|
self._misses += 1
|
|
return None
|
|
self._hits += 1
|
|
return value
|
|
|
|
def set(self, key: str, value: Any) -> None:
|
|
self._cache[key] = (value, time.monotonic())
|
|
self._trim()
|
|
|
|
def delete(self, key: str) -> None:
|
|
self._cache.pop(key, None)
|
|
|
|
def clear(self) -> None:
|
|
self._cache.clear()
|
|
|
|
def _trim(self) -> None:
|
|
if len(self._cache) <= self._max_entries:
|
|
return
|
|
now = time.monotonic()
|
|
expired = [k for k, v in self._cache.items() if now - v[1] > self._ttl_seconds]
|
|
for k in expired:
|
|
del self._cache[k]
|
|
self._evictions += 1
|
|
if len(self._cache) > self._max_entries:
|
|
oldest = sorted(self._cache.items(), key=lambda x: x[1][1])[: len(self._cache) - self._max_entries]
|
|
for k, _ in oldest:
|
|
del self._cache[k]
|
|
self._evictions += 1
|
|
|
|
@property
|
|
def size(self) -> int:
|
|
return len(self._cache)
|
|
|
|
@property
|
|
def stats(self) -> dict[str, Any]:
|
|
return {
|
|
"name": self.name,
|
|
"size": self.size,
|
|
"max_entries": self._max_entries,
|
|
"ttl_seconds": self._ttl_seconds,
|
|
"hits": self._hits,
|
|
"misses": self._misses,
|
|
"evictions": self._evictions,
|
|
"hit_ratio": self._hits / max(self._hits + self._misses, 1),
|
|
}
|
|
|
|
|
|
class CacheManager:
|
|
def __init__(self, max_cache_entries: int = 2048):
|
|
self._caches: dict[str, UnifiedCache] = {}
|
|
self._max_cache_entries = max_cache_entries
|
|
|
|
def register(
|
|
self,
|
|
name: str,
|
|
max_entries: int | None = None,
|
|
ttl_seconds: float = 600.0,
|
|
) -> UnifiedCache:
|
|
cache = UnifiedCache(
|
|
name=name,
|
|
max_entries=max_entries or self._max_cache_entries,
|
|
ttl_seconds=ttl_seconds,
|
|
)
|
|
self._caches[name] = cache
|
|
return cache
|
|
|
|
def get_cache(self, name: str) -> UnifiedCache | None:
|
|
return self._caches.get(name)
|
|
|
|
def clear_all(self) -> None:
|
|
for cache in self._caches.values():
|
|
cache.clear()
|
|
|
|
def get_all_stats(self) -> dict[str, dict[str, Any]]:
|
|
return {name: cache.stats for name, cache in self._caches.items()}
|
|
|
|
def log_stats(self) -> None:
|
|
for name, cache in self._caches.items():
|
|
stats = cache.stats
|
|
logger.debug(
|
|
"[BlueBubbles] Cache '%s': %d entries, hit_ratio=%.2f",
|
|
name,
|
|
stats["size"],
|
|
stats["hit_ratio"],
|
|
)
|