Files
pocketsync/ui/app.py
T
2026-02-21 23:08:04 +01:00

222 lines
8.1 KiB
Python

import os
import threading
import tkinter as tk
from core.config import ConfigManager, Profile
from core.robocopy_engine import RobocopySyncEngine
from core.sync_engine import SyncEngine
from ui import styles
from ui.path_panel import PathPanel
from ui.profile_bar import ProfileBar
from ui.status_bar import StatusBar
from ui.summary_panel import SummaryPanel
from ui.system_list import SystemList
class PocketSyncApp:
def __init__(self, root: tk.Tk, config_manager: ConfigManager):
self.root = root
self.cm = config_manager
self.engine: SyncEngine = RobocopySyncEngine()
self._build_ui()
self._load_profile(self.cm.active_profile)
# ------------------------------------------------------------------
# Construcción de la UI
# ------------------------------------------------------------------
def _build_ui(self) -> None:
self.root.title("PocketSync (© 2026 JailDesigner)")
self.root.geometry(f"{styles.WINDOW_WIDTH}x{styles.WINDOW_HEIGHT}")
self.root.configure(bg=styles.APP_BG)
# --- Barra de perfiles ---
self.profile_bar = ProfileBar(self.root, on_change=self._on_profile_change)
self.profile_bar.pack(fill="x", padx=styles.PAD_X, pady=(8, 2))
self.profile_bar.set_callbacks(
on_new=self._on_new_profile,
on_rename=self._on_rename_profile,
on_delete=self._on_delete_profile,
)
self._refresh_profile_bar()
# --- Rutas de origen ---
self.src_panel = PathPanel(self.root, title="Rutas de origen")
self.src_panel.pack(fill="x", padx=styles.PAD_X, pady=styles.PAD_Y)
self.src_panel.set_roms_callback(self._on_roms_src_change)
# --- Lista de sistemas ---
self.system_list = SystemList(self.root)
self.system_list.pack(fill="both", expand=True, padx=styles.PAD_X)
# --- Rutas de destino ---
self.dst_panel = PathPanel(self.root, title="Rutas de destino")
self.dst_panel.pack(fill="x", padx=styles.PAD_X, pady=styles.PAD_Y)
# --- Botón Sync ---
tk.Button(
self.root,
text="Sincronizar",
font=styles.FONT_BUTTON,
command=self._run_sync,
).pack(pady=6)
# --- Barra de estado ---
self.status_bar = StatusBar(self.root)
self.status_bar.pack(fill="x", padx=styles.PAD_X, pady=styles.PAD_Y)
# --- Panel de resumen ---
self.summary = SummaryPanel(self.root)
self.summary.pack(fill="both", expand=True, padx=styles.PAD_X, pady=styles.PAD_Y)
self.root.protocol("WM_DELETE_WINDOW", self._on_close)
# ------------------------------------------------------------------
# Gestión de perfiles
# ------------------------------------------------------------------
def _refresh_profile_bar(self) -> None:
self.profile_bar.refresh(self.cm.profile_names(), self.cm.active_profile_name)
def _collect_ui_into_profile(self) -> None:
"""Escribe el estado actual de la UI en el perfil activo."""
p = self.cm.active_profile
p.esde_src = self.src_panel.get_esde()
p.roms_src = self.src_panel.get_roms()
p.esde_dst = self.dst_panel.get_esde()
p.roms_dst = self.dst_panel.get_roms()
p.selected = self.system_list.get_selected()
def _load_profile(self, profile: Profile) -> None:
self.src_panel.set_esde(profile.esde_src)
self.src_panel.set_roms(profile.roms_src)
self.dst_panel.set_esde(profile.esde_dst)
self.dst_panel.set_roms(profile.roms_dst)
if os.path.isdir(profile.roms_src):
self.system_list.populate(profile.roms_src)
else:
self.system_list.populate("")
self.system_list.set_selected(profile.selected)
def _on_profile_change(self, name: str) -> None:
self._collect_ui_into_profile()
self.cm.active_profile_name = name
self._load_profile(self.cm.active_profile)
self._refresh_profile_bar()
def _on_new_profile(self, name: str) -> bool:
if self.cm.get_profile(name) is not None:
return False
self._collect_ui_into_profile()
self.cm.add_profile(name)
self.cm.active_profile_name = name
self._load_profile(self.cm.active_profile)
self._refresh_profile_bar()
return True
def _on_rename_profile(self, old: str, new: str) -> bool:
ok = self.cm.rename_profile(old, new)
if ok:
self._refresh_profile_bar()
return ok
def _on_delete_profile(self, name: str) -> bool:
ok = self.cm.delete_profile(name)
if ok:
self._load_profile(self.cm.active_profile)
self._refresh_profile_bar()
return ok
# ------------------------------------------------------------------
# Callbacks de widgets
# ------------------------------------------------------------------
def _on_roms_src_change(self, path: str) -> None:
self.system_list.populate(path)
# ------------------------------------------------------------------
# Loop de sincronización
# ------------------------------------------------------------------
def _run_sync(self) -> None:
thread = threading.Thread(target=self._sync_thread)
thread.daemon = True
thread.start()
def _sync_thread(self) -> None:
selected = self.system_list.get_selected()
if not selected:
self.summary.append("No hay sistemas seleccionados.")
return
esde_src = self.src_panel.get_esde()
roms_src = self.src_panel.get_roms()
esde_dst = self.dst_panel.get_esde()
roms_dst = self.dst_panel.get_roms()
if not all([esde_src, roms_src, esde_dst, roms_dst]):
self.summary.append("ERROR: Debes configurar todas las rutas antes de continuar.")
return
self.summary.clear()
self.summary.append("=" * 60)
self.summary.append("INICIANDO PROCESO DE COPIA")
self.summary.append(f"Total de sistemas a procesar: {len(selected)}")
self.summary.append("=" * 60)
total = len(selected)
for idx, system in enumerate(selected, 1):
self.status_bar.set_system(f"Sistema: {idx}/{total} - {system.upper()}")
self.summary.append(f"\nSISTEMA [{idx}/{total}]: {system.upper()}")
# Fase 1: ROMs
self.status_bar.set_phase("Fase: [1/3] Copiando ROMs...")
self.summary.append(" [1/3] Copiando ROMs...")
self.engine.sync_folder(
os.path.join(roms_src, system),
os.path.join(roms_dst, system),
on_file=self.status_bar.set_file,
on_summary=self.summary.append,
)
# Fase 2: gamelists
self.status_bar.set_phase("Fase: [2/3] Copiando gamelists...")
self.summary.append(" [2/3] Copiando gamelists...")
self.engine.sync_folder(
os.path.join(esde_src, "gamelists", system),
os.path.join(esde_dst, "gamelists", system),
on_file=self.status_bar.set_file,
on_summary=self.summary.append,
)
# Fase 3: media
self.status_bar.set_phase("Fase: [3/3] Copiando media...")
self.summary.append(" [3/3] Copiando media...")
self.engine.sync_folder(
os.path.join(esde_src, "downloaded_media", system),
os.path.join(esde_dst, "downloaded_media", system),
on_file=self.status_bar.set_file,
on_summary=self.summary.append,
)
self.summary.append(f" Sistema '{system}' completado\n")
self.status_bar.reset()
self.summary.append("=" * 60)
self.summary.append("PROCESO COMPLETADO EXITOSAMENTE")
self.summary.append("=" * 60)
# ------------------------------------------------------------------
# Cierre
# ------------------------------------------------------------------
def _on_close(self) -> None:
self._collect_ui_into_profile()
self.cm.save()
self.root.destroy()