Files
Local-Booru/system_module/db.py
T
Hell13Cat 7fe9b73d49 0.1.4s
2026-06-06 10:32:38 +03:00

217 lines
7.9 KiB
Python

import json
import os
import sqlite3
from datetime import datetime
from system_module.gelbooru import gelbooru_date_parse
class DB:
def __init__(self, debug: bool):
if debug:
db_path = "data_test"
else:
db_path = "data"
if not os.path.exists(db_path):
os.makedirs(db_path)
self.conn = sqlite3.connect(os.path.join(db_path, "db.sqlite"), check_same_thread=False)
self.conn.row_factory = sqlite3.Row
self._create_table()
def _create_table(self):
with self.conn:
self.conn.execute("""
CREATE TABLE IF NOT EXISTS files (
md5 TEXT PRIMARY KEY,
local_filename TEXT,
is_video INTEGER,
is_gif INTEGER,
tags TEXT,
rating TEXT,
score INTEGER,
timestamp TEXT,
parsed_time INTEGER,
display_date TEXT,
source TEXT,
width INTEGER,
height INTEGER
)
""")
def _row_to_dict(self, row: sqlite3.Row) -> dict:
if not row:
return None
d = dict(row)
d["is_video"] = bool(d["is_video"])
d["is_gif"] = bool(d["is_gif"])
# Парсинг тегов из JSON-строки обратно в массив
d["tags"] = json.loads(d["tags"]) if d["tags"] else []
# Восстанавливаем другие поля (например, display_date и source),
# если в них были переданы кортежи или списки
for field in ["display_date", "source"]:
val = d.get(field)
if isinstance(val, str) and (val.startswith('[') or val.startswith('{')):
try:
d[field] = json.loads(val)
except ValueError:
pass
return d
def add(self, data_add: dict):
parsed_time = 0
if "timestamp" in data_add:
try:
parsed_time = int(datetime.strptime(data_add["timestamp"], "%a %b %d %H:%M:%S %z %Y").timestamp())
except ValueError:
pass
row_data = {
"md5": data_add.get("md5"),
"local_filename": data_add.get("local_filename", data_add.get("image")),
"is_video": int(data_add.get("is_video", False)),
"is_gif": int(data_add.get("is_gif", False)),
"tags": data_add.get("tags", []),
"rating": data_add.get("rating"),
"score": data_add.get("score"),
"timestamp": data_add.get("timestamp", data_add.get("created_at")),
"parsed_time": parsed_time,
"display_date": data_add.get("display_date"),
"source": data_add.get("source"),
"width": data_add.get("width"),
"height": data_add.get("height")
}
# БЕЗОПАСНАЯ СЕРИАЛИЗАЦИЯ ДЛЯ SQLITE:
# SQLite принимает только числа, строки и байты.
# Любые списки (tags) или кортежи (display_date) мы перегоняем в JSON.
for key, value in row_data.items():
if isinstance(value, (list, tuple, dict)):
row_data[key] = json.dumps(value)
columns = ", ".join(row_data.keys())
placeholders = ", ".join(["?"] * len(row_data))
with self.conn:
self.conn.execute(
f"INSERT OR REPLACE INTO files ({columns}) VALUES ({placeholders})",
tuple(row_data.values())
)
def add_raw(self, data_add: dict):
is_video = data_add["image"].split(".")[-1] in ["webm", "mp4", "avi"]
is_gif = data_add["image"].split(".")[-1] == "gif"
dt_obj = datetime.strptime(data_add["created_at"], "%a %b %d %H:%M:%S %z %Y")
parsed_time = int(dt_obj.timestamp())
add_data = {
"md5": data_add["md5"],
"local_filename": data_add["image"],
"is_video": is_video,
"is_gif": is_gif,
"tags": data_add["tags"].split(" "),
"rating": data_add["rating"],
"score": data_add["score"],
"timestamp": data_add["created_at"],
"parsed_time": parsed_time,
"display_date": gelbooru_date_parse(data_add["created_at"]),
"source": data_add["source"],
"width": data_add["width"],
"height": data_add["height"]
}
self.add(add_data)
def get_all(self):
cursor = self.conn.execute("SELECT * FROM files ORDER BY parsed_time DESC")
return [self._row_to_dict(row) for row in cursor.fetchall()]
def get_search(self, rating_tmp: list[str], tags: list[str]):
mapping_rating = {
"e": "explicit",
"g": "general",
"s": "sensitive",
"q": "questionable"
}
query = "SELECT * FROM files WHERE 1=1"
params = []
ratings = [mapping_rating.get(x, x) for x in rating_tmp if x]
if ratings:
placeholders = ",".join("?" * len(ratings))
query += f" AND rating IN ({placeholders})"
params.extend(ratings)
tags = [t for t in tags if t]
if tags:
for tag in tags:
query += " AND EXISTS (SELECT 1 FROM json_each(files.tags) WHERE value = ?)"
params.append(tag)
query += " ORDER BY parsed_time DESC"
cursor = self.conn.execute(query, tuple(params))
return [self._row_to_dict(row) for row in cursor.fetchall()]
def get_unknow(self):
cursor = self.conn.execute("SELECT * FROM files WHERE tags = '[]' OR tags IS NULL")
return [self._row_to_dict(row) for row in cursor.fetchall()]
def check_uni(self, md5: str):
query = "SELECT 1 FROM files WHERE md5 = ? LIMIT 1"
cursor = self.conn.execute(query, (md5,))
return cursor.fetchone() is not None
def get_top_tag(self, tag_slise: str, count_tag_get: int = 9999999):
query = """
SELECT value AS tag, COUNT(*) AS count
FROM files, json_each(files.tags)
WHERE value LIKE ?
GROUP BY value
ORDER BY count DESC
LIMIT {count_tag_get}
""".format(count_tag_get=count_tag_get)
cursor = self.conn.execute(query, (f"{tag_slise.lower()}%",))
return [{"tag": row["tag"], "count": row["count"]} for row in cursor.fetchall()]
def edit_data(self, id_file: str, edit_data: dict):
if not edit_data:
return
set_clauses = []
params = []
for key, value in edit_data.items():
# Если передаем списки/кортежи, пакуем в JSON
if isinstance(value, (list, tuple, dict)):
value = json.dumps(value)
set_clauses.append(f"{key} = ?")
if key == "timestamp":
params.append(value)
try:
set_clauses.append("parsed_time = ?")
params.append(int(datetime.strptime(value, "%a %b %d %H:%M:%S %z %Y").timestamp()))
except ValueError:
pass
else:
params.append(value)
params.append(id_file)
query = f"UPDATE files SET {', '.join(set_clauses)} WHERE md5 = ?"
with self.conn:
self.conn.execute(query, tuple(params))
def get_from_id(self, id_file: str):
cursor = self.conn.execute("SELECT * FROM files WHERE md5 = ?", (id_file,))
return self._row_to_dict(cursor.fetchone())
def close(self):
self.conn.close()