"""数据库操作层 — 封装所有 SQLite 操作。""" import sqlite3 import os from datetime import datetime DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "books.db") def _connect(): conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row conn.execute("PRAGMA journal_mode=WAL") return conn def init_db(): """初始化数据库和表结构。""" conn = _connect() conn.execute(""" CREATE TABLE IF NOT EXISTS books ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, author TEXT, translator TEXT, publisher TEXT, pub_date TEXT, cover_url TEXT, format TEXT DEFAULT 'paper', status TEXT DEFAULT 'to-read', rating INTEGER, douban_score REAL, goodreads_score REAL, tags TEXT DEFAULT '', notes TEXT, start_date TEXT, finish_date TEXT, created_at TEXT DEFAULT (datetime('now', 'localtime')), updated_at TEXT DEFAULT (datetime('now', 'localtime')) ) """) conn.commit() conn.close() def add_book(title, *, author=None, translator=None, publisher=None, pub_date=None, cover_url=None, format="paper", status="to-read", rating=None, douban_score=None, goodreads_score=None, tags="", notes=None, start_date=None, finish_date=None): """添加一本书,返回新书 ID。""" conn = _connect() cur = conn.execute(""" INSERT INTO books (title, author, translator, publisher, pub_date, cover_url, format, status, rating, douban_score, goodreads_score, tags, notes, start_date, finish_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, (title, author, translator, publisher, pub_date, cover_url, format, status, rating, douban_score, goodreads_score, tags, notes, start_date, finish_date)) conn.commit() book_id = cur.lastrowid conn.close() return book_id def update_book(book_id, **kwargs): """更新书目字段。只更新传入的字段。""" if not kwargs: return allowed = { "title", "author", "translator", "publisher", "pub_date", "cover_url", "format", "status", "rating", "douban_score", "goodreads_score", "tags", "notes", "start_date", "finish_date", } fields = {k: v for k, v in kwargs.items() if k in allowed} if not fields: return fields["updated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") set_clause = ", ".join(f"{k} = ?" for k in fields) values = list(fields.values()) + [book_id] conn = _connect() conn.execute(f"UPDATE books SET {set_clause} WHERE id = ?", values) conn.commit() conn.close() def delete_book(book_id): """删除一本书。""" conn = _connect() conn.execute("DELETE FROM books WHERE id = ?", (book_id,)) conn.commit() conn.close() def get_book(book_id): """获取单本书详情,返回 dict 或 None。""" conn = _connect() row = conn.execute("SELECT * FROM books WHERE id = ?", (book_id,)).fetchone() conn.close() return dict(row) if row else None def list_books(*, status=None, format=None, tag=None): """查询书目列表,支持按状态/格式/标签过滤。""" conn = _connect() query = "SELECT * FROM books WHERE 1=1" params = [] if status: query += " AND status = ?" params.append(status) if format: query += " AND format = ?" params.append(format) if tag: # 逗号分隔的 tags 字段中模糊匹配 query += " AND (',' || tags || ',' LIKE ?)" params.append(f"%,{tag},%") query += " ORDER BY updated_at DESC" rows = conn.execute(query, params).fetchall() conn.close() return [dict(r) for r in rows] def get_stats(): """获取统计数据。""" conn = _connect() total = conn.execute("SELECT COUNT(*) FROM books").fetchone()[0] by_status = {} for row in conn.execute("SELECT status, COUNT(*) as cnt FROM books GROUP BY status"): by_status[row["status"]] = row["cnt"] by_format = {} for row in conn.execute("SELECT format, COUNT(*) as cnt FROM books GROUP BY format"): by_format[row["format"]] = row["cnt"] conn.close() return { "total": total, "by_status": by_status, "by_format": by_format, } def get_all_tags(): """获取所有不重复的标签列表。""" conn = _connect() rows = conn.execute("SELECT tags FROM books WHERE tags != ''").fetchall() conn.close() tag_set = set() for row in rows: for t in row["tags"].split(","): t = t.strip() if t: tag_set.add(t) return sorted(tag_set)