Spaces:
Running
Running
File size: 3,746 Bytes
d646dd7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | """
SQLite 数据库管理器 —— 线程安全的单例模式,管理录制历史记录表。
"""
import sqlite3
import os
import threading
from config.settings import DB_PATH, DATA_DIR
class Database:
"""简单的 SQLite 封装,含建表逻辑和线程本地连接。"""
_local = threading.local()
def __init__(self, db_path: str = DB_PATH):
self._db_path = db_path
@property
def conn(self) -> sqlite3.Connection:
"""获取当前线程的数据库连接(自动创建)。"""
if not hasattr(self._local, "conn") or self._local.conn is None:
os.makedirs(os.path.dirname(self._db_path), exist_ok=True)
c = sqlite3.connect(self._db_path)
c.row_factory = sqlite3.Row
c.execute("PRAGMA journal_mode=WAL")
c.execute("PRAGMA foreign_keys=ON")
self._local.conn = c
return self._local.conn
def close(self):
"""关闭当前线程的数据库连接。"""
if hasattr(self._local, "conn") and self._local.conn:
self._local.conn.close()
self._local.conn = None
# ── 建表 SQL ──────────────────────────────────────
SCHEMA_SQL = """
CREATE TABLE IF NOT EXISTS recording (
id TEXT PRIMARY KEY,
camera_index INTEGER NOT NULL,
camera_name TEXT NOT NULL,
file_path TEXT,
episode_index INTEGER DEFAULT 0, -- v1.1.0 池化:任务目录内的全局 episode 序号
file_size_mb REAL DEFAULT 0,
duration_sec REAL DEFAULT 0,
resolution_w INTEGER DEFAULT 0,
resolution_h INTEGER DEFAULT 0,
status TEXT NOT NULL DEFAULT 'completed', -- completed | uploaded | aborted | deleted | uploaded_deleted
started_at TEXT NOT NULL,
finished_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_recording_camera ON recording(camera_index);
CREATE INDEX IF NOT EXISTS idx_recording_date ON recording(started_at);
CREATE TABLE IF NOT EXISTS upload_task (
id TEXT PRIMARY KEY,
session_path TEXT NOT NULL,
session_name TEXT NOT NULL,
episode_index INTEGER DEFAULT 0, -- v1.1.0 池化:全局 episode 序号
status TEXT NOT NULL DEFAULT 'pending', -- pending | uploading | completed | failed | skipped
progress REAL DEFAULT 0.0,
retry_count INTEGER DEFAULT 0,
server_url TEXT NOT NULL,
server_session_id TEXT DEFAULT '',
error_message TEXT DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_upload_status ON upload_task(status);
CREATE INDEX IF NOT EXISTS idx_upload_session ON upload_task(session_path);
"""
def init_schema(self):
"""执行建表语句,并做既有库的列迁移(向后兼容)。"""
self.conn.executescript(self.SCHEMA_SQL)
# CREATE TABLE IF NOT EXISTS 不给既有表加列,需 ALTER 迁移
self._ensure_column("recording", "episode_index", "INTEGER DEFAULT 0")
self._ensure_column("upload_task", "episode_index", "INTEGER DEFAULT 0")
self.conn.commit()
def _ensure_column(self, table: str, column: str, decl: str):
"""表缺列时 ALTER TABLE ADD COLUMN(幂等)。"""
cols = {r[1] for r in self.conn.execute(f"PRAGMA table_info({table})")}
if column not in cols:
self.conn.execute(
f"ALTER TABLE {table} ADD COLUMN {column} {decl}")
# 模块级单例
db = Database()
|