Загрузка данных


import os
import shutil
import sqlite3
import tkinter as tk
from tkinter import ttk, messagebox
from datetime import datetime

class DBFileManager:
    """Низкоуровневая работа с файлами БД без GUI."""
    
    def __init__(self, active_flag_path="db/active.flag"):
        self.databases = {
            "database": "db/database.db",
            "masterdata": "db/masterdata.db"
        }
        self.active_flag_path = active_flag_path
        self.backup_dir = "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") # По умолчанию активна database.db
        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):
        src = os.path.join(self.backup_dir, backup_filename)
        dest = self.get_active_path()
        
        tmp_dest = dest + ".restore.tmp"
        shutil.copy2(src, tmp_dest)
        
        try:
            con = sqlite3.connect(tmp_dest)
            con.execute("PRAGMA integrity_check;")
            con.close()
        except sqlite3.DatabaseError:
            os.remove(tmp_dest)
            raise RuntimeError("Файл поврежден или не является базой SQLite")
            
        os.replace(tmp_dest, dest)

    def migrate_data(self, source_key: str, dest_key: str):
        if source_key not in self.databases or dest_key not in self.databases:
            raise ValueError("Неизвестная база")
            
        source_path = self.databases[source_key]
        dest_path = self.databases[dest_key]
        
        temp_dest = dest_path + ".migration.tmp"
        if not os.path.exists(dest_path):
            open(dest_path, 'a').close()

        with sqlite3.connect(source_path) as src_con:
            with sqlite3.connect(temp_dest) as dest_con:
                src_con.backup(dest_con)
                
        os.replace(temp_dest, dest_path)

class AdminDBInter(tk.Toplevel):
    def __init__(self, parent):
        super().__init__(parent)
        self.parent_main = parent
        
        self.title("Управление базами данных")
        self.geometry("600x400")
        self.resizable(True, True)
        self.transient(parent)
        self.grab_set()
        
        self.file_manager = DBFileManager()
        self.build_ui()
        self.refresh_backups_list()
        self.sync_active_flag()

    def build_ui(self):
        container = ttk.Frame(self)
        container.pack(fill="both", expand=True, padx=10, pady=10)

        top_panel = ttk.Frame(container)
        top_panel.pack(fill="x", pady=10)

        self.use_master_db = tk.BooleanVar(value=(self.file_manager._get_active_name() == "masterdata"))
        style = ttk.Style()
        style.configure("RedText.TCheckbutton", foreground="red")
        
        ttk.Checkbutton(
            top_panel,
            text="Использовать основную базу данных (masterdata.db)",
            variable=self.use_master_db,
            style="RedText.TCheckbutton",
            command=self.toggle_active_db
        ).pack(side="left", padx=10)

        tools_panel = ttk.Frame(container)
        tools_panel.pack(fill="x", pady=10)

        ttk.Button(tools_panel, text="Создать резервную копию", command=self.do_backup).pack(side="left", padx=5)
        ttk.Button(tools_panel, text="Восстановить из копии", command=self.do_restore).pack(side="left", padx=5)
        ttk.Button(tools_panel, text="Мигрировать данные", command=self.do_migrate).pack(side="left", padx=5)

        backup_columns = ("Дата", "Файл")
        self.backups_tree = ttk.Treeview(container, columns=backup_columns, show="headings", height=10)
        for col in backup_columns:
            self.backups_tree.heading(col, text=col)
            self.backups_tree.column(col, width=270)
        self.backups_tree.pack(fill="both", expand=True, pady=10)

    def refresh_backups_list(self):
        self.backups_tree.delete(*self.backups_tree.get_children())
        if not os.path.exists(self.file_manager.backup_dir):
            return
            
        files = sorted(os.listdir(self.file_manager.backup_dir), reverse=True)
        for fname in files:
            if "__" in fname and fname.endswith(".db"):
                date_part = fname.split("__")[0].replace("_", " ").replace("-", ":").replace(".", ":")
                self.backups_tree.insert("", "end", values=(date_part, fname))

    def sync_active_flag(self):
        is_master = self.file_manager._get_active_name() == "masterdata"
        self.use_master_db.set(is_master)

    def toggle_active_db(self):
        target = "masterdata" if self.use_master_db.get() else "database"
        self.file_manager._set_active_name(target)
        messagebox.showinfo("Готово", f"Теперь активной будет {target}.db\nПерезапустите приложение для полной смены контекста.")

    def do_backup(self):
        try:
            fname = self.file_manager.create_backup()
            self.refresh_backups_list()
            messagebox.showinfo("Бэкап", f"Копия создана:\n{fname}")
        except Exception as e:
            messagebox.showerror("Ошибка", str(e))

    def do_restore(self):
        item = self.backups_tree.selection()
        if not item:
            messagebox.showwarning("Внимание", "Выберите файл в списке слева.")
            return
        fname = self.backups_tree.item(item)["values"][1]
        try:
            self.file_manager.restore_from_file(fname)
            messagebox.showinfo("Успех", f"База восстановлена из {fname}\nПриложение нужно перезапустить.")
        except Exception as e:
            messagebox.showerror("Ошибка", str(e))

    def do_migrate(self):
        answer = messagebox.askyesnocancel(
            "Миграция данных",
            "Перенести ВСЕ данные из НЕАКТИВНОЙ базы в АКТИВНУЮ?\nАктивная база будет ПОЛНОСТЬЮ ЗАТЕРТА данными из другой.\nДействие необратимо."
        )
        if answer is None or not answer:
            return
            
        current = self.file_manager._get_active_name()
        source = "masterdata" if current == "database" else "database"
        
        try:
            self.file_manager.migrate_data(source, current)
            messagebox.showinfo("Миграция", f"Данные перенесены из {source}.db в {current}.db")
        except Exception as e:
            messagebox.showerror("Ошибка", str(e))