"""SQLite 数据库管理 管理漫画、章节、页面的下载状态,支持断点续传和去重。 """ import sqlite3 from pathlib import Path SCHEMA_SQL = """\ CREATE TABLE IF NOT EXISTS manga ( id TEXT NOT NULL, source TEXT NOT NULL, title TEXT NOT NULL, alt_title TEXT, author TEXT, cover_url TEXT, description TEXT, tags TEXT, url TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')), PRIMARY KEY (id, source) ); CREATE TABLE IF NOT EXISTS chapter ( id TEXT NOT NULL, manga_id TEXT NOT NULL, manga_source TEXT NOT NULL, title TEXT NOT NULL, chapter_number REAL NOT NULL, chapter_type TEXT NOT NULL DEFAULT 'chapter', url TEXT NOT NULL, page_count INTEGER, status TEXT NOT NULL DEFAULT 'pending', download_path TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')), PRIMARY KEY (id, manga_source), FOREIGN KEY (manga_id, manga_source) REFERENCES manga(id, source) ); CREATE INDEX IF NOT EXISTS idx_chapter_manga ON chapter(manga_id, manga_source); CREATE INDEX IF NOT EXISTS idx_chapter_status ON chapter(status); CREATE TABLE IF NOT EXISTS page ( id INTEGER PRIMARY KEY AUTOINCREMENT, chapter_id TEXT NOT NULL, chapter_source TEXT NOT NULL, page_number INTEGER NOT NULL, url TEXT NOT NULL, local_path TEXT, status TEXT NOT NULL DEFAULT 'pending', created_at TEXT NOT NULL DEFAULT (datetime('now')), UNIQUE(chapter_id, chapter_source, page_number), FOREIGN KEY (chapter_id, chapter_source) REFERENCES chapter(id, manga_source) ); CREATE INDEX IF NOT EXISTS idx_page_chapter ON page(chapter_id, chapter_source); CREATE TABLE IF NOT EXISTS subscription ( manga_id TEXT NOT NULL, source TEXT NOT NULL, subscribed_at TEXT NOT NULL DEFAULT (datetime('now')), last_checked TEXT, last_chapter_id TEXT, auto_push INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (manga_id, source), FOREIGN KEY (manga_id, source) REFERENCES manga(id, source) ); """ class Database: """SQLite 状态存储。同步操作,在 async 下载批次之间调用。""" def __init__(self, db_path: str | Path = "kobo_manga.db"): self.db_path = Path(db_path) self.conn: sqlite3.Connection | None = None def initialize(self) -> None: """创建连接并初始化表结构。""" self.conn = sqlite3.connect(str(self.db_path)) self.conn.row_factory = sqlite3.Row self.conn.execute("PRAGMA journal_mode=WAL") self.conn.execute("PRAGMA foreign_keys=ON") self.conn.executescript(SCHEMA_SQL) self._migrate() self.conn.commit() def _migrate(self) -> None: """增量 schema 迁移。""" cursor = self.conn.execute("PRAGMA table_info(chapter)") columns = {row[1] for row in cursor.fetchall()} if "chapter_type" not in columns: self.conn.execute( "ALTER TABLE chapter ADD COLUMN chapter_type TEXT NOT NULL DEFAULT 'chapter'" ) def close(self) -> None: if self.conn: self.conn.close() self.conn = None def __enter__(self) -> "Database": self.initialize() return self def __exit__(self, *args) -> None: self.close()