Initial commit

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
dinlo
2026-05-31 18:43:18 +08:00
commit b553c957f3
30 changed files with 1879 additions and 0 deletions
+231
View File
@@ -0,0 +1,231 @@
# database/db_manager.py
import sqlite3
from pathlib import Path
from typing import Optional, List, Dict, Any
from contextlib import contextmanager
from utils.logger import logger
from database.schema import PRAGMA_FOREIGN_KEYS, CREATE_TABLES_SQL, CREATE_INDEXES_SQL
class DBManager:
def __init__(self, db_path: str = "comfygallery.db"):
self.db_path = db_path
self._initialize_db()
@contextmanager
def _get_connection(self):
conn = None
try:
conn = sqlite3.connect(self.db_path)
conn.execute(PRAGMA_FOREIGN_KEYS)
conn.row_factory = sqlite3.Row
yield conn
except sqlite3.Error as e:
logger.error(f"Ошибка соединения с БД {self.db_path}: {e}")
if conn:
conn.rollback()
raise
finally:
if conn:
conn.close()
def _initialize_db(self) -> bool:
try:
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.executescript(CREATE_TABLES_SQL)
cursor.executescript(CREATE_INDEXES_SQL)
conn.commit()
return True
except sqlite3.Error as e:
logger.critical(f"Критическая ошибка инициализации БД: {e}")
return False
def add_folder(self, folder_path: str, parent_id: Optional[int] = None) -> Optional[int]:
if not folder_path:
return None
normalized_path = str(Path(folder_path).resolve().as_posix())
try:
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute(
"INSERT INTO folders (path, parent_id) VALUES (?, ?) "
"ON CONFLICT(path) DO UPDATE SET parent_id=excluded.parent_id RETURNING id",
(normalized_path, parent_id)
)
result = cursor.fetchone()
if not result:
cursor.execute("SELECT id FROM folders WHERE path = ?", (normalized_path,))
result = cursor.fetchone()
conn.commit()
return result[0] if result else None
except sqlite3.Error as e:
logger.error(f"Ошибка при добавлении папки {normalized_path}: {e}")
return None
def remove_folder_by_path(self, folder_path: str) -> bool:
normalized_path = str(Path(folder_path).resolve().as_posix())
try:
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute("DELETE FROM folders WHERE path = ?", (normalized_path,))
conn.commit()
return cursor.rowcount > 0
except sqlite3.Error as e:
logger.error(f"Ошибка при удалении папки {normalized_path} из БД: {e}")
return False
def register_file(self, folder_id: int, filename: str, filepath: str, size: int, mtime: float) -> Optional[int]:
if not filename or not filepath:
return None
normalized_filepath = str(Path(filepath).resolve().as_posix())
try:
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute(
"""
INSERT INTO files (folder_id, filename, filepath, size, mtime)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(filepath) DO UPDATE SET
size = excluded.size,
mtime = excluded.mtime,
folder_id = excluded.folder_id
RETURNING id
""",
(folder_id, filename, normalized_filepath, size, mtime)
)
result = cursor.fetchone()
conn.commit()
return result[0] if result else None
except sqlite3.Error as e:
logger.error(f"Ошибка регистрации файла {normalized_filepath}: {e}")
return None
def remove_file_by_path(self, filepath: str) -> bool:
normalized_filepath = str(Path(filepath).resolve().as_posix())
try:
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute("DELETE FROM files WHERE filepath = ?", (normalized_filepath,))
conn.commit()
return cursor.rowcount > 0
except sqlite3.Error as e:
logger.error(f"Ошибка удаления файла {normalized_filepath} из БД: {e}")
return False
def save_metadata(self, file_id: int, meta_payload: Dict[str, Any]) -> bool:
try:
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute(
"""
INSERT INTO metadata (
file_id, prompt_json, workflow_json, positive_prompt,
negative_prompt, seed, model_name, sampler, steps, cfg
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(file_id) DO UPDATE SET
prompt_json = excluded.prompt_json,
workflow_json = excluded.workflow_json,
positive_prompt = excluded.positive_prompt,
negative_prompt = excluded.negative_prompt,
seed = excluded.seed,
model_name = excluded.model_name,
sampler = excluded.sampler,
steps = excluded.steps,
cfg = excluded.cfg
""",
(
file_id,
meta_payload.get("prompt_json"),
meta_payload.get("workflow_json"),
meta_payload.get("positive_prompt"),
meta_payload.get("negative_prompt"),
meta_payload.get("seed"),
meta_payload.get("model_name"),
meta_payload.get("sampler"),
meta_payload.get("steps"),
meta_payload.get("cfg")
)
)
conn.commit()
return True
except sqlite3.Error as e:
logger.error(f"Ошибка при сохранении метаданных файла ID {file_id}: {e}")
return False
def get_files_in_folder(self, folder_path: str, search_query: str = "") -> List[Dict[str, Any]]:
normalized_path = str(Path(folder_path).resolve().as_posix())
try:
with self._get_connection() as conn:
cursor = conn.cursor()
if search_query:
like_query = f"%{search_query}%"
cursor.execute(
"""
SELECT f.id, f.filename, f.filepath, f.rating, f.favorite,
CASE WHEN m.workflow_json IS NOT NULL AND m.workflow_json != '' THEN 1 ELSE 0 END as has_workflow
FROM files f
JOIN folders fo ON f.folder_id = fo.id
LEFT JOIN metadata m ON f.id = m.file_id
WHERE fo.path = ? AND (f.filename LIKE ? OR m.positive_prompt LIKE ? OR m.negative_prompt LIKE ?)
ORDER BY f.filename ASC
""",
(normalized_path, like_query, like_query, like_query)
)
else:
cursor.execute(
"""
SELECT f.id, f.filename, f.filepath, f.rating, f.favorite,
CASE WHEN m.workflow_json IS NOT NULL AND m.workflow_json != '' THEN 1 ELSE 0 END as has_workflow
FROM files f
JOIN folders fo ON f.folder_id = fo.id
LEFT JOIN metadata m ON f.id = m.file_id
WHERE fo.path = ?
ORDER BY f.filename ASC
""",
(normalized_path,)
)
return [dict(row) for row in cursor.fetchall()]
except sqlite3.Error as e:
logger.error(f"Ошибка получения файлов в папке {normalized_path}: {e}")
return []
def get_file_details(self, file_id: int) -> Optional[Dict[str, Any]]:
try:
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute(
"""
SELECT f.id, f.filename, f.filepath, f.rating, f.favorite,
m.prompt_json, m.workflow_json, m.positive_prompt, m.negative_prompt,
m.seed, m.model_name, m.sampler, m.steps, m.cfg
FROM files f
LEFT JOIN metadata m ON f.id = m.file_id
WHERE f.id = ?
""",
(file_id,)
)
row = cursor.fetchone()
return dict(row) if row else None
except sqlite3.Error as e:
logger.error(f"Ошибка получения деталей файла ID {file_id}: {e}")
return None
def update_file_details(self, file_id: int, positive: str, negative: str, rating: int) -> bool:
try:
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute("UPDATE files SET rating = ? WHERE id = ?", (rating, file_id))
cursor.execute(
"""
UPDATE metadata
SET positive_prompt = ?, negative_prompt = ?
WHERE file_id = ?
""",
(positive, negative, file_id)
)
conn.commit()
return True
except sqlite3.Error as e:
logger.error(f"Ошибка при сохранении правок для файла ID {file_id}: {e}")
return False