Files
bookshelf/db.py
T
2026-03-25 13:20:39 +08:00

154 lines
4.8 KiB
Python

"""数据库操作层 — 封装所有 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)