diff --git a/.gitignore b/.gitignore index ec981db..69aa934 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,7 @@ MANIFEST # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest -*.spec +# *.spec — NO ignorar: pocketsync.spec está versionado como configuración de build # Installer logs pip-log.txt diff --git a/CLAUDE.md b/CLAUDE.md index 67f5ec6..357fc4d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -82,6 +82,40 @@ Las operaciones de copia corren en un hilo daemon separado para no bloquear la U ### Extensibilidad de backends `core/sync_engine.py` define la ABC `SyncEngine`. `RobocopySyncEngine` es la implementación concreta. Para añadir rsync u otro backend: crear un nuevo archivo en `core/` que implemente la misma interfaz. +## Build (generar PocketSync.exe) + +### Requisitos previos (una sola vez) + +```bash +py -m venv .venv +.venv\Scripts\activate +pip install -r requirements.txt +``` + +### Icono (opcional pero recomendado) + +Coloca tu icono en `assets\pocketsync.ico` antes de empaquetar. Sin él, el `.exe` usará el icono genérico de Python. El `.spec` detecta automáticamente si el archivo existe. + +### Empaquetar + +```bash +.venv\Scripts\activate +pyinstaller pocketsync.spec +``` + +El ejecutable final queda en `dist\PocketSync.exe`. No requiere Python instalado en el equipo destino. + +### Archivos de build + +| Archivo | Descripción | +|---|---| +| `requirements.txt` | Solo `pyinstaller>=6.0` | +| `pocketsync.spec` | Configuración de PyInstaller (versionado) | +| `assets/pocketsync.ico` | Icono del `.exe` (no versionado, añadir manualmente) | +| `dist/` | Salida del build (ignorado por git) | +| `build/` | Archivos temporales de PyInstaller (ignorado por git) | +| `.venv/` | Entorno virtual (ignorado por git) | + ## Plataforma Windows 10/11 obligatorio (depende de `robocopy`). Python 3.6+, sin dependencias externas. diff --git a/PocketSync.exe b/PocketSync.exe new file mode 100644 index 0000000..4407927 Binary files /dev/null and b/PocketSync.exe differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..baf3168 --- /dev/null +++ b/README.md @@ -0,0 +1,86 @@ +# PocketSync + +Herramienta de escritorio para sincronizar archivos de emulación retro (ES-DE y ROMs) hacia almacenamiento externo. Soporta múltiples perfiles (uno por consola o dispositivo). + +## Requisitos + +- Windows 10 / 11 +- `robocopy` (incluido en Windows por defecto) +- Para ejecutar desde código fuente: Python 3.6+ + +## Uso rápido (ejecutable) + +Descarga `PocketSync.exe` y ejecútalo directamente. No requiere Python instalado. + +## Uso desde código fuente + +```bash +python pocketsync.py +``` + +## Configurar y usar la app + +1. **Selecciona o crea un perfil** en el desplegable superior (New / Rename / Delete). +2. **Configura las 4 rutas:** + - ES-DE origen → carpeta local de ES-DE + - ES-DE destino → carpeta en el almacenamiento externo + - ROMs origen → carpeta local de ROMs + - ROMs destino → carpeta en el almacenamiento externo +3. **Selecciona los sistemas** a sincronizar (Select All / None para selección rápida). +4. Pulsa **Sync Now**. + +La configuración se guarda automáticamente en `config.json` al cerrar. + +## Generar el ejecutable + +### 1. Crear el entorno virtual e instalar dependencias + +```bash +py -m venv .venv +.venv\Scripts\activate +pip install -r requirements.txt +``` + +### 2. Añadir icono (opcional) + +Coloca un archivo `pocketsync.ico` en la carpeta `assets\`. Sin él, el ejecutable usará el icono genérico de Windows. + +### 3. Empaquetar + +```bash +.venv\Scripts\activate +pyinstaller pocketsync.spec +``` + +El resultado queda en `dist\PocketSync.exe`. + +## Estructura del proyecto + +``` +pocketsync.py # Entry point +pocketsync.spec # Configuración de PyInstaller +requirements.txt # Solo pyinstaller>=6.0 +config.json # Perfiles guardados (se crea al primer uso) +assets/ + pocketsync.ico # Icono del .exe (añadir manualmente) + +core/ + config.py # Gestión de perfiles y persistencia + sync_engine.py # Interfaz abstracta de sincronización + robocopy_engine.py # Implementación con Robocopy + +ui/ + app.py # Ventana principal y lógica de sync + profile_bar.py # Selector de perfiles + path_panel.py # Selectores de ruta + system_list.py # Lista de sistemas + status_bar.py # Barra de estado + summary_panel.py # Resumen de la última sincronización + styles.py # Colores y fuentes +``` + +## Notas + +- La sincronización usa `robocopy /MIR`, que replica exactamente el origen en el destino (borra archivos en destino que no estén en origen). +- Cada perfil guarda sus 4 rutas y la selección de sistemas de forma independiente. +- El proceso de sync corre en un hilo separado para no bloquear la interfaz. diff --git a/assets/.gitkeep b/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/assets/pocketsync.ico b/assets/pocketsync.ico new file mode 100644 index 0000000..cda602d Binary files /dev/null and b/assets/pocketsync.ico differ diff --git a/pocketsync.py b/pocketsync.py index 3e99e9d..ff39770 100644 --- a/pocketsync.py +++ b/pocketsync.py @@ -2,8 +2,13 @@ import os import sys import tkinter as tk -# Asegurar que el directorio raíz esté en el path para imports relativos -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +# Cuando se ejecuta como .exe (PyInstaller onefile), __file__ apunta a la +# carpeta temporal de extracción. sys.executable apunta al .exe real. +if getattr(sys, 'frozen', False): + BASE_DIR = os.path.dirname(sys.executable) +else: + BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + if BASE_DIR not in sys.path: sys.path.insert(0, BASE_DIR) diff --git a/pocketsync.spec b/pocketsync.spec new file mode 100644 index 0000000..972bbe3 --- /dev/null +++ b/pocketsync.spec @@ -0,0 +1,43 @@ +# -*- mode: python ; coding: utf-8 -*- +import os + +icon_path = os.path.join('assets', 'pocketsync.ico') +icon_arg = icon_path if os.path.exists(icon_path) else None + +a = Analysis( + ['pocketsync.py'], + pathex=['.'], + binaries=[], + datas=[], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, +) + +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='PocketSync', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon=icon_arg, + onefile=True, +) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c45a45b --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +pyinstaller>=6.0