Загрузка данных
# ---*--- Начало обновленного modules/administrator/admin_db_work.py ---*---
import io
import os
import shutil
import sqlite3
from datetime import datetime
from pathlib import Path
class DBFileManager:
def __init__(self):
# ЕДИНСТВЕННАЯ точка определения корня проекта
self.project_root = Path(__file__).resolve().parents[2]
self.databases = {
"database": str(self.project_root / "db" / "database.db"),
"masterdata": str(self.project_root / "db" / "masterdata.db")
}
self.active_flag_path = str(self.project_root / "db" / "active.flag")
self.backup_dir = str(self.project_root / "backups") # Теперь точно сюда
os.makedirs(self.backup_dir, exist_ok=True)
self._ensure_active_flag()
def _ensure_active_flag(self):
try:
if not os.path.exists(self.active_flag_path):
with open(self.active_flag_path, 'w') as f:
f.write("database")
except Exception as e:
print(f"[WARN] Не удалось создать флаг активной БД: {e}")
def _get_active_name(self):
try:
with open(self.active_flag_path, 'r') as f:
name = f.read().strip()
return name if name in self.databases else "database"
except FileNotFoundError:
return "database"
def _set_active_name(self, name):
with open(self.active_flag_path, 'w') as f:
f.write(name)
def get_active_path(self):
return self.databases[self._get_active_name()]
def create_backup(self):
src = self.get_active_path()
ts = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
filename = f"{ts}__{os.path.basename(src)}"
dst = os.path.join(self.backup_dir, filename)
shutil.copy2(src, dst)
return filename
def restore_from_file(self, backup_filename):
import io
project_root = Path(__file__).resolve().parents[2]
src_path = str(project_root / "backups" / backup_filename)
active_name_str = self._get_active_name()
dest_path = str(project_root / "db" / f"{active_name_str}.db")
print(f"[DEBUG RESTORE-DUMP] From Backup: {src_path}")
print(f"[DEBUG RESTORE-DUMP] To Active DB: {dest_path}")
try:
# 1. Читаем бэкап в строку
with sqlite3.connect(src_path) as src_con:
sql_dump_io = io.StringIO()
for line in src_con.iterdump():
sql_dump_io.write(line + '\n')
full_sql_script = sql_dump_io.getvalue()
print("[DEBUG RESTORE-DUMP] Бэкап прочитан в RAM.")
# === ФИЛЬТРАЦИЯ СКРИПТА (Убираем CREATE TABLE) ===
lines = full_sql_script.split('\n')
filtered_lines = []
skip_create_table = False
for line in lines:
if line.strip().upper().startswith('CREATE TABLE'):
skip_create_table = True
continue
if skip_create_table:
if ');' in line:
skip_create_table = False
continue
filtered_lines.append(line)
filtered_script = '\n'.join(filtered_lines)
# ==================================================================================
# 2. Выполнение БЕЗ ручного BEGIN/COMMIT
with sqlite3.connect(dest_path) as dest_con:
cursor = dest_con.cursor()
# Выключаем ключи для скорости очистки
cursor.execute("PRAGMA foreign_keys=OFF;")
# ОЧИСТКА ВСЕХ ТАБЛИЦ ОДНИМ ЗАПРОСОМ (вместо цикла FOR)
# Это быстрее и надежнее. Сбрасываем счетчики AUTOINCREMENT тоже.
cursor.execute("""
DELETE FROM sqlite_sequence;
""")
tables_query = """
SELECT name FROM sqlite_master
WHERE type='table' AND name NOT LIKE 'sqlite_%';
"""
cursor.execute(tables_query)
tables = [row[0] for row in cursor.fetchall()]
delete_statements = "; ".join([f"DELETE FROM {t}" for t in tables]) + ";"
if delete_statements.strip() != ";":
cursor.executescript(delete_statements)
# Вставка данных. executescript сам управляет своими внутренними транзакциями.
cursor.executescript(filtered_script)
# Включаем ключи обратно
cursor.execute("PRAGMA foreign_keys=ON;")
print("[DEBUG RESTORE-DUMP] SUCCESS: Восстановление завершено.")
except Exception as e:
# Выведем ПЕРВИЧНУЮ ошибку в консоль, чтобы понять, почему скрипт упал
print(f"[CRITICAL DUMP ERROR] Первичная ошибка дампа: {str(e)}")
import traceback
traceback.print_exc()
raise RuntimeError(f"Восстановление прервано. Смотрите логи в терминале.")
def migrate_data(self, source_key: str, dest_key: str):
import io
import re
project_root = Path(__file__).resolve().parents[2]
source_path = str(project_root / "db" / f"{source_key}.db")
dest_path = str(project_root / "db" / f"{dest_key}.db")
print(f"[DEBUG MIGRATE-DUMP] From: {source_path}")
print(f"[DEBUG MIGRATE-DUMP] To: {dest_path} (через SQL Dump)")
try:
# 1. Читаем ВСЮ исходную базу в строку
with sqlite3.connect(source_path) as src_con:
sql_dump_io = io.StringIO()
for line in src_con.iterdump():
sql_dump_io.write(line + '\n')
full_sql_script = sql_dump_io.getvalue()
print("[DEBUG DUMP] Данные успешно сериализованы в SQL.")
# 2. Фильтруем скрипт: убираем CREATE TABLE, оставляем INSERT и прочее
lines = full_sql_script.split('\n')
filtered_lines = []
skip_create_table = False
for line in lines:
if line.strip().upper().startswith('CREATE TABLE'):
skip_create_table = True
continue
if skip_create_table:
if ');' in line:
skip_create_table = False
continue
filtered_lines.append(line)
filtered_script = '\n'.join(filtered_lines)
# 3. Очищаем целевую базу и вставляем данные БЕЗ ручного BEGIN/COMMIT
with sqlite3.connect(dest_path) as dest_con:
cursor = dest_con.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
tables = [row[0] for row in cursor.fetchall() if not row[0].startswith('sqlite_')]
cursor.execute("PRAGMA foreign_keys=OFF;")
for table in tables:
cursor.execute(f"DELETE FROM {table};")
cursor.executescript(filtered_script)
cursor.execute("PRAGMA foreign_keys=ON;")
print("[DEBUG MIGRATE-DUMP] SUCCESS: Данные перенесены через фильтрованный SQL-скрипт.")
except Exception as e:
# Сюда упала ошибка SyntaxError - этого блока не хватало
raise RuntimeError(f"Миграция через SQL Dump прервана: {str(e)}")