"""守护进程模式
定期检查订阅漫画的更新,下载新章节并可选推送到设备。
"""
import asyncio
from datetime import datetime
from pathlib import Path
from kobo_manga.config import AppConfig
from kobo_manga.db.database import Database
from kobo_manga.db.queries import list_subscriptions
from kobo_manga.pipeline import MangaPipeline
from kobo_manga.transfer import get_transfer
class UpdateScheduler:
"""订阅更新调度器。"""
def __init__(self, config: AppConfig, db: Database):
self.config = config
self.db = db
self.running = False
async def run_once(self) -> dict:
"""执行一轮更新检查。
Returns:
{checked, new_chapters, errors, kepubs}
"""
pipeline = MangaPipeline(self.config, self.db)
subs = list_subscriptions(self.db)
results: dict = {
"checked": 0,
"new_chapters": 0,
"errors": 0,
"kepubs": [],
}
for sub in subs:
title = sub.get("title", sub["manga_id"])
print(f"\n 检查: {title}")
try:
kepubs = await pipeline.check_and_download_updates(
sub["manga_id"], sub["source"]
)
results["checked"] += 1
results["new_chapters"] += len(kepubs)
results["kepubs"].extend(kepubs)
# 自动推送
if kepubs and (
sub["auto_push"] or self.config.scheduler.auto_push
):
self._push(kepubs)
except Exception as e:
results["errors"] += 1
print(f" [!] {title}: {e}")
return results
async def run_forever(self):
"""守护循环:定期执行 run_once。Ctrl+C 退出。"""
self.running = True
interval = self.config.scheduler.interval
while self.running:
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"\n[{now}] 开始检查订阅更新...")
results = await self.run_once()
print(
f"\n 检查 {results['checked']} 个订阅,"
f"{results['new_chapters']} 个新章节,"
f"{results['errors']} 个错误"
)
if self.running:
next_time = datetime.now().timestamp() + interval
next_str = datetime.fromtimestamp(next_time).strftime(
"%H:%M:%S"
)
print(f" 下次检查: {next_str}")
try:
await asyncio.sleep(interval)
except asyncio.CancelledError:
break
def _push(self, kepubs: list[Path]):
"""推送新章节到设备。"""
try:
transfer = get_transfer(self.config.transfer)
dest_paths = transfer.transfer(kepubs)
print(f" [OK] 推送 {len(dest_paths)} 个文件")
except Exception as e:
print(f" [!] 推送失败: {e}")