Spaces:
Sleeping
Sleeping
File size: 6,218 Bytes
a49c70b a326b02 a49c70b a326b02 a49c70b 27809ff a49c70b a326b02 a49c70b 27809ff | 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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | """SQLite database for generation history."""
from __future__ import annotations
import json
import sqlite3
import logging
from datetime import datetime, timedelta, timezone
from pathlib import Path
logger = logging.getLogger(__name__)
_SCHEMA = """
CREATE TABLE IF NOT EXISTS generation_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
job_id TEXT NOT NULL UNIQUE,
session_id TEXT NOT NULL,
original_filename TEXT NOT NULL,
original_path TEXT NOT NULL,
output_path TEXT NOT NULL,
difficulty TEXT NOT NULL,
num_differences INTEGER NOT NULL,
processing_time REAL NOT NULL,
metadata TEXT NOT NULL,
created_at TEXT NOT NULL,
expires_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_job_id ON generation_history(job_id);
CREATE INDEX IF NOT EXISTS idx_expires_at ON generation_history(expires_at);
CREATE TABLE IF NOT EXISTS job_status (
job_id TEXT PRIMARY KEY,
status TEXT NOT NULL,
progress INTEGER NOT NULL DEFAULT 0,
current_step TEXT NOT NULL DEFAULT '',
error TEXT,
result_path TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_job_status_updated ON job_status(updated_at);
"""
def init_db(db_path: str) -> None:
"""Create the database and tables if they don't exist.
Raises OSError if the database directory cannot be created.
"""
db_file = Path(db_path)
try:
# Create parent directory if needed
db_file.parent.mkdir(parents=True, exist_ok=True)
logger.info(f"Database directory ensured: {db_file.parent}")
except OSError as e:
logger.error(f"Failed to create database directory {db_file.parent}: {e}")
raise OSError(
f"Cannot create database directory {db_file.parent}. "
f"Please ensure FLASK_ENV=production is set for deployment."
) from e
try:
# Create database and schema
with sqlite3.connect(db_path) as conn:
conn.executescript(_SCHEMA)
logger.info(f"Database initialized: {db_path}")
except sqlite3.Error as e:
logger.error(f"Failed to initialize database: {e}")
raise
def save_generation(
db_path: str,
job_id: str,
session_id: str,
original_filename: str,
original_path: str,
output_path: str,
difficulty: str,
num_differences: int,
processing_time: float,
metadata: dict,
expiry_hours: int = 24,
) -> int:
"""Save a generation record. Returns the row id."""
now = datetime.now(timezone.utc)
expires = now + timedelta(hours=expiry_hours)
with sqlite3.connect(db_path) as conn:
cursor = conn.execute(
"""INSERT INTO generation_history
(job_id, session_id, original_filename, original_path,
output_path, difficulty, num_differences, processing_time,
metadata, created_at, expires_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
(
job_id,
session_id,
original_filename,
original_path,
output_path,
difficulty,
num_differences,
processing_time,
json.dumps(metadata, ensure_ascii=False),
now.isoformat(),
expires.isoformat(),
),
)
return cursor.lastrowid
def get_generation(db_path: str, job_id: str) -> dict | None:
"""Retrieve a generation record by job_id."""
with sqlite3.connect(db_path) as conn:
conn.row_factory = sqlite3.Row
row = conn.execute(
"SELECT * FROM generation_history WHERE job_id = ?", (job_id,)
).fetchone()
if row is None:
return None
d = dict(row)
d["metadata"] = json.loads(d["metadata"])
return d
def cleanup_expired(db_path: str) -> int:
"""Delete expired records. Returns count of deleted rows."""
now = datetime.now(timezone.utc).isoformat()
with sqlite3.connect(db_path) as conn:
cursor = conn.execute(
"DELETE FROM generation_history WHERE expires_at < ?", (now,)
)
return cursor.rowcount
# Job status persistence functions for JobManager
def save_job_status(
db_path: str,
job_id: str,
status: str,
progress: int = 0,
current_step: str = "",
error: str | None = None,
result_path: str | None = None,
) -> None:
"""Save or update job status in database."""
now = datetime.now(timezone.utc).isoformat()
with sqlite3.connect(db_path) as conn:
# Use INSERT OR REPLACE to handle both new and existing jobs
conn.execute(
"""INSERT OR REPLACE INTO job_status
(job_id, status, progress, current_step, error, result_path,
created_at, updated_at)
VALUES (
?,
?,
?,
?,
?,
?,
COALESCE((SELECT created_at FROM job_status WHERE job_id = ?), ?),
?
)""",
(
job_id,
status,
progress,
current_step,
error,
result_path,
job_id,
now,
now,
),
)
def get_job_status(db_path: str, job_id: str) -> dict | None:
"""Retrieve job status by job_id."""
with sqlite3.connect(db_path) as conn:
conn.row_factory = sqlite3.Row
row = conn.execute(
"SELECT * FROM job_status WHERE job_id = ?", (job_id,)
).fetchone()
if row is None:
return None
return dict(row)
def cleanup_old_job_status(db_path: str, hours: int = 24) -> int:
"""Delete job status records older than specified hours. Returns count of deleted rows."""
cutoff = (datetime.now(timezone.utc) - timedelta(hours=hours)).isoformat()
with sqlite3.connect(db_path) as conn:
cursor = conn.execute(
"DELETE FROM job_status WHERE updated_at < ?", (cutoff,)
)
return cursor.rowcount
|