feat: Implement system tray integration and enhance application features

This commit is contained in:
Miguel 2025-09-02 12:53:45 +02:00
parent bcd4f126ae
commit 5d1dd2ea52
10 changed files with 702 additions and 24 deletions

View File

@ -1,7 +1,7 @@
# AutoBackups Project # AutoBackups Project
## Overview ## Overview
AutoBackups is a Python application designed to automate the backup of Simatic S7 projects and other user-defined directories. The application leverages the Everything API to efficiently locate files and manage backups based on user-defined configurations. AutoBackups is a Python application designed to automate the backup of Simatic S7 projects and other user-defined directories. The application leverages the Everything API to efficiently locate files and manage backups based on user-defined configurations. Features a system tray integration for easy access and background operation.
## Features ## Features
- Monitors specified directories for Simatic S7 files (*.s7p) and other user-defined directories. - Monitors specified directories for Simatic S7 files (*.s7p) and other user-defined directories.
@ -10,6 +10,8 @@ AutoBackups is a Python application designed to automate the backup of Simatic S
- Skips projects with files in use and retries after one hour. - Skips projects with files in use and retries after one hour.
- Maintains a two-stage hash system: first checking *.s7p files, then all files to avoid unnecessary backups. - Maintains a two-stage hash system: first checking *.s7p files, then all files to avoid unnecessary backups.
- User-friendly web interface built with Flask for configuration and status monitoring. - User-friendly web interface built with Flask for configuration and status monitoring.
- **System tray integration** with options to open browser and close application.
- **Custom favicon** for both frontend and system tray.
- Comprehensive logging system with timestamped log files. - Comprehensive logging system with timestamped log files.
## Project Structure ## Project Structure
@ -24,14 +26,19 @@ AutoBackups is a Python application designed to automate the backup of Simatic S
│ ├── services │ ├── services
│ │ └── __init__.py # Business logic for backups │ │ └── __init__.py # Business logic for backups
│ └── utils │ └── utils
│ └── __init__.py # Utility functions for hashing and file access │ ├── __init__.py # Utility functions for hashing and file access
│ └── system_tray.py # System tray functionality
├── static ├── static
│ ├── css # CSS files for styling │ ├── css # CSS files for styling
│ ├── icons
│ │ └── favicon.png # Application icon (used in web and system tray)
│ └── js # JavaScript files for client-side functionality │ └── js # JavaScript files for client-side functionality
├── templates ├── templates
│ └── index.html # Main HTML template for the web interface │ └── index.html # Main HTML template for the web interface
├── config.json # Global configuration settings for the application ├── config.json # Global configuration settings for the application
├── projects.json # Per-project configuration, schedules, and hash storage ├── projects.json # Per-project configuration, schedules, and hash storage
├── launch_tray.py # System tray launcher
├── launch_with_tray.bat # Windows batch file for easy launching
├── requirements.txt # Python dependencies ├── requirements.txt # Python dependencies
└── README.md # Project documentation └── README.md # Project documentation
``` ```
@ -57,13 +64,39 @@ AutoBackups is a Python application designed to automate the backup of Simatic S
4. Configure the application by editing `config.json` to specify directories to monitor and backup settings. 4. Configure the application by editing `config.json` to specify directories to monitor and backup settings.
## Usage ## Usage
### Regular Mode
- Start the Flask application: - Start the Flask application:
``` ```
python src/app.py python src/app.py
``` ```
- Access the web interface at `http://localhost:5120` to configure settings and monitor backup statuses. - Access the web interface at `http://localhost:5120` to configure settings and monitor backup statuses.
### System Tray Mode (Recommended)
- Start the application with system tray integration:
```
python launch_tray.py
```
- Or use the Windows batch file:
```
launch_with_tray.bat
```
#### System Tray Features:
- **Auto-start**: Application starts in the background with system tray icon
- **Quick Access**: Double-click the tray icon to open the web interface
- **Right-click Menu**:
- "Open AutoBackups" - Opens the web interface in your default browser
- "Status" - Shows application status (future feature)
- "Exit" - Safely closes the application
- **Custom Icon**: Uses `static/icons/favicon.png` for both web interface and system tray
- **Silent Operation**: Application runs silently in the background
#### System Tray Requirements:
- Windows 10/11 (tested)
- Python packages: `pystray`, `pillow` (automatically installed)
- Valid favicon.png file in `static/icons/`
## Contributing ## Contributing
Contributions are welcome! Please submit a pull request or open an issue for any enhancements or bug fixes. Contributions are welcome! Please submit a pull request or open an issue for any enhancements or bug fixes.

View File

@ -5,6 +5,12 @@
"type": "siemens_s7", "type": "siemens_s7",
"enabled": true, "enabled": true,
"description": "Directorio principal de proyectos Siemens" "description": "Directorio principal de proyectos Siemens"
},
{
"path": "C:/Trabajo/SIDEL/05 - E5.007161 - Modifica O&U - SAE346",
"type": "siemens_s7",
"enabled": true,
"description": "SAE346"
} }
], ],
"backup_destination": "D:\\Backups\\AutoBackups", "backup_destination": "D:\\Backups\\AutoBackups",

94
launch_tray.py Normal file
View File

@ -0,0 +1,94 @@
"""
AutoBackups System Tray Launcher
Lanza la aplicación AutoBackups con icono en el system tray
"""
import sys
from pathlib import Path
import threading
import time
import logging
# Agregar el directorio src al path para imports
current_dir = Path(__file__).parent
src_dir = current_dir / "src"
sys.path.insert(0, str(src_dir))
# Imports de la aplicación
from src.app import AutoBackupsFlaskApp, app
def run_flask_app(autobackups_app, host, port, debug=False):
"""Ejecuta la aplicación Flask en un hilo separado"""
try:
app.run(host=host, port=port, debug=debug, use_reloader=False)
except Exception as e:
logging.error(f"Error ejecutando Flask app: {e}")
def main():
"""Función principal para el launcher del system tray"""
try:
print("AutoBackups - System Tray Mode")
print("=" * 30)
# Crear y configurar aplicación Flask
autobackups_app = AutoBackupsFlaskApp()
# Verificar requerimientos del sistema
if not autobackups_app.check_system_requirements():
print("Error en la verificación de requerimientos del sistema.")
sys.exit(1)
# Ejecutar descubrimiento inicial de proyectos
projects_found = autobackups_app.discover_projects()
msg = f"Descubrimiento inicial: {projects_found} proyectos encontrados"
print(msg)
# Configurar Flask
host = autobackups_app.config.web_interface.get("host", "127.0.0.1")
port = autobackups_app.config.web_interface.get("port", 5000)
debug = autobackups_app.config.web_interface.get("debug", False)
print(f"Servidor web iniciando en http://{host}:{port}")
# Iniciar system tray
if autobackups_app.start_system_tray():
print("✓ System tray iniciado correctamente")
else:
print("⚠ System tray no disponible, continuando...")
# Ejecutar Flask en un hilo separado
flask_thread = threading.Thread(
target=run_flask_app, args=(autobackups_app, host, port, debug), daemon=True
)
flask_thread.start()
print("✓ Aplicación iniciada")
print("• Accede desde el icono del system tray")
print("• Usa Ctrl+C para salir desde la consola")
print("• O usa 'Exit' desde el menú del system tray")
# Mantener el programa corriendo
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\nCerrando aplicación...")
except KeyboardInterrupt:
print("\nAplicación interrumpida por el usuario")
except Exception as e:
print(f"Error inesperado: {e}")
sys.exit(1)
finally:
# Limpieza
if "autobackups_app" in locals():
if autobackups_app.scheduler:
autobackups_app.scheduler.shutdown()
if autobackups_app.system_tray:
autobackups_app.system_tray.stop_tray()
if __name__ == "__main__":
main()

24
launch_with_tray.bat Normal file
View File

@ -0,0 +1,24 @@
@echo off
echo AutoBackups - Launching with System Tray...
echo.
REM Activate conda environment
call conda activate autobackups
REM Check if activation was successful
if errorlevel 1 (
echo Error: Could not activate conda environment 'autobackups'
echo Please make sure the environment exists and conda is installed.
pause
exit /b 1
)
REM Launch the application with system tray
python launch_tray.py
REM Pause to see any error messages
if errorlevel 1 (
echo.
echo Application exited with an error.
pause
)

View File

@ -1,8 +1,8 @@
{ {
"metadata": { "metadata": {
"version": "1.0", "version": "1.0",
"last_updated": "2025-09-02T07:15:16.655495+00:00", "last_updated": "2025-09-02T10:51:48.579987+00:00",
"total_projects": 3 "total_projects": 9
}, },
"projects": [ "projects": [
{ {
@ -21,10 +21,10 @@
"next_scheduled_backup": "" "next_scheduled_backup": ""
}, },
"backup_history": { "backup_history": {
"last_backup_date": "2025-09-02T08:48:07.555782+00:00", "last_backup_date": "2025-09-02T10:52:24.182464+00:00",
"last_backup_file": "D:\\Backups\\AutoBackups\\Ssae0452 Last Version Walter\\2025-09-02\\10-48-04_projects.zip", "last_backup_file": "D:\\Backups\\AutoBackups\\Ssae0452 Last Version Walter\\2025-09-02\\12-52-22_projects.zip",
"backup_count": 1, "backup_count": 2,
"last_successful_backup": "2025-09-02T08:48:07.555782+00:00" "last_successful_backup": "2025-09-02T10:52:24.182464+00:00"
}, },
"hash_info": { "hash_info": {
"last_s7p_hash": "", "last_s7p_hash": "",
@ -42,7 +42,7 @@
"next_retry": null, "next_retry": null,
"files_in_use": false, "files_in_use": false,
"exclusivity_check_passed": true, "exclusivity_check_passed": true,
"last_status_update": "2025-09-02T08:48:07.555782+00:00" "last_status_update": "2025-09-02T10:52:24.182464+00:00"
}, },
"discovery_info": { "discovery_info": {
"discovered_date": "", "discovered_date": "",
@ -66,10 +66,10 @@
"next_scheduled_backup": "" "next_scheduled_backup": ""
}, },
"backup_history": { "backup_history": {
"last_backup_date": "2025-09-02T08:48:07.539194+00:00", "last_backup_date": "2025-09-02T10:52:24.159989+00:00",
"last_backup_file": "D:\\Backups\\AutoBackups\\LineaB\\Ssae04_11 - compiled\\2025-09-02\\10-48-04_projects.zip", "last_backup_file": "D:\\Backups\\AutoBackups\\LineaB\\Ssae04_11 - compiled\\2025-09-02\\12-52-22_projects.zip",
"backup_count": 2, "backup_count": 3,
"last_successful_backup": "2025-09-02T08:48:07.539194+00:00" "last_successful_backup": "2025-09-02T10:52:24.159989+00:00"
}, },
"hash_info": { "hash_info": {
"last_s7p_hash": "", "last_s7p_hash": "",
@ -87,7 +87,7 @@
"next_retry": null, "next_retry": null,
"files_in_use": false, "files_in_use": false,
"exclusivity_check_passed": true, "exclusivity_check_passed": true,
"last_status_update": "2025-09-02T08:48:07.539194+00:00" "last_status_update": "2025-09-02T10:52:24.159989+00:00"
}, },
"discovery_info": { "discovery_info": {
"discovered_date": "", "discovered_date": "",
@ -111,10 +111,10 @@
"next_scheduled_backup": "" "next_scheduled_backup": ""
}, },
"backup_history": { "backup_history": {
"last_backup_date": "2025-09-02T08:48:07.592782+00:00", "last_backup_date": "2025-09-02T10:52:24.172317+00:00",
"last_backup_file": "D:\\Backups\\AutoBackups\\LineaB\\Ssae04_14 - TIA\\2025-09-02\\10-48-04_projects.zip", "last_backup_file": "D:\\Backups\\AutoBackups\\LineaB\\Ssae04_14 - TIA\\2025-09-02\\12-52-22_projects.zip",
"backup_count": 1, "backup_count": 2,
"last_successful_backup": "2025-09-02T08:48:07.592782+00:00" "last_successful_backup": "2025-09-02T10:52:24.172317+00:00"
}, },
"hash_info": { "hash_info": {
"last_s7p_hash": "", "last_s7p_hash": "",
@ -132,7 +132,277 @@
"next_retry": null, "next_retry": null,
"files_in_use": false, "files_in_use": false,
"exclusivity_check_passed": true, "exclusivity_check_passed": true,
"last_status_update": "2025-09-02T08:48:07.592782+00:00" "last_status_update": "2025-09-02T10:52:24.172317+00:00"
},
"discovery_info": {
"discovered_date": "",
"discovery_method": "",
"auto_discovered": true
}
},
{
"id": "project_c437a7bb_20250902_125148",
"name": "Inverter",
"path": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\InLavoro\\PLC\\Varios\\Inverter",
"type": "siemens_s7",
"s7p_file": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\InLavoro\\PLC\\Varios\\Inverter\\InverterGSD.s7p",
"observation_directory": "C:/Trabajo/SIDEL/05 - E5.007161 - Modifica O&U - SAE346",
"relative_path": "InLavoro\\PLC\\Varios\\Inverter",
"backup_path": "InLavoro\\PLC\\Varios\\Inverter",
"schedule_config": {
"schedule": "daily",
"schedule_time": "02:00",
"enabled": true,
"next_scheduled_backup": ""
},
"backup_history": {
"last_backup_date": "2025-09-02T10:52:22.919831+00:00",
"last_backup_file": "D:\\Backups\\AutoBackups\\InLavoro\\PLC\\Varios\\Inverter\\2025-09-02\\12-52-22_projects.zip",
"backup_count": 1,
"last_successful_backup": "2025-09-02T10:52:22.919831+00:00"
},
"hash_info": {
"last_s7p_hash": "",
"last_full_hash": "",
"last_s7p_timestamp": "",
"last_s7p_size": 0,
"last_scan_timestamp": "",
"file_count": 0,
"total_size_bytes": 0
},
"status": {
"current_status": "ready",
"last_error": null,
"retry_count": 0,
"next_retry": null,
"files_in_use": false,
"exclusivity_check_passed": true,
"last_status_update": "2025-09-02T10:52:22.919831+00:00"
},
"discovery_info": {
"discovered_date": "",
"discovery_method": "",
"auto_discovered": true
}
},
{
"id": "project_315c5dfb_20250902_125148",
"name": "Ssae03_2",
"path": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\InLavoro\\PLC\\Varios\\daSAP\\PLC SAE346\\Ssae03_2",
"type": "siemens_s7",
"s7p_file": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\InLavoro\\PLC\\Varios\\daSAP\\PLC SAE346\\Ssae03_2\\Ssae0346.s7p",
"observation_directory": "C:/Trabajo/SIDEL/05 - E5.007161 - Modifica O&U - SAE346",
"relative_path": "InLavoro\\PLC\\Varios\\daSAP\\PLC SAE346\\Ssae03_2",
"backup_path": "InLavoro\\PLC\\Varios\\daSAP\\PLC SAE346\\Ssae03_2",
"schedule_config": {
"schedule": "daily",
"schedule_time": "02:00",
"enabled": true,
"next_scheduled_backup": ""
},
"backup_history": {
"last_backup_date": "2025-09-02T10:52:23.703364+00:00",
"last_backup_file": "D:\\Backups\\AutoBackups\\InLavoro\\PLC\\Varios\\daSAP\\PLC SAE346\\Ssae03_2\\2025-09-02\\12-52-22_projects.zip",
"backup_count": 1,
"last_successful_backup": "2025-09-02T10:52:23.703364+00:00"
},
"hash_info": {
"last_s7p_hash": "",
"last_full_hash": "",
"last_s7p_timestamp": "",
"last_s7p_size": 0,
"last_scan_timestamp": "",
"file_count": 0,
"total_size_bytes": 0
},
"status": {
"current_status": "ready",
"last_error": null,
"retry_count": 0,
"next_retry": null,
"files_in_use": false,
"exclusivity_check_passed": true,
"last_status_update": "2025-09-02T10:52:23.703364+00:00"
},
"discovery_info": {
"discovered_date": "",
"discovery_method": "",
"auto_discovered": true
}
},
{
"id": "project_8d1cfcd7_20250902_125148",
"name": "Rve116_PEPSI_OCTOBER L7",
"path": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\InLavoro\\PLC\\Varios\\Filler\\Rve116_PEPSI_OCTOBER L7",
"type": "siemens_s7",
"s7p_file": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\InLavoro\\PLC\\Varios\\Filler\\Rve116_PEPSI_OCTOBER L7\\Rve116.s7p",
"observation_directory": "C:/Trabajo/SIDEL/05 - E5.007161 - Modifica O&U - SAE346",
"relative_path": "InLavoro\\PLC\\Varios\\Filler\\Rve116_PEPSI_OCTOBER L7",
"backup_path": "InLavoro\\PLC\\Varios\\Filler\\Rve116_PEPSI_OCTOBER L7",
"schedule_config": {
"schedule": "daily",
"schedule_time": "02:00",
"enabled": true,
"next_scheduled_backup": ""
},
"backup_history": {
"last_backup_date": "2025-09-02T10:52:23.563211+00:00",
"last_backup_file": "D:\\Backups\\AutoBackups\\InLavoro\\PLC\\Varios\\Filler\\Rve116_PEPSI_OCTOBER L7\\2025-09-02\\12-52-22_projects.zip",
"backup_count": 1,
"last_successful_backup": "2025-09-02T10:52:23.563211+00:00"
},
"hash_info": {
"last_s7p_hash": "",
"last_full_hash": "",
"last_s7p_timestamp": "",
"last_s7p_size": 0,
"last_scan_timestamp": "",
"file_count": 0,
"total_size_bytes": 0
},
"status": {
"current_status": "ready",
"last_error": null,
"retry_count": 0,
"next_retry": null,
"files_in_use": false,
"exclusivity_check_passed": true,
"last_status_update": "2025-09-02T10:52:23.563211+00:00"
},
"discovery_info": {
"discovered_date": "",
"discovery_method": "",
"auto_discovered": true
}
},
{
"id": "project_42543170_20250902_125148",
"name": "TestCOM",
"path": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\InLavoro\\PLC\\Varios\\Filler\\TestCOM",
"type": "siemens_s7",
"s7p_file": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\InLavoro\\PLC\\Varios\\Filler\\TestCOM\\TestCOM.s7p",
"observation_directory": "C:/Trabajo/SIDEL/05 - E5.007161 - Modifica O&U - SAE346",
"relative_path": "InLavoro\\PLC\\Varios\\Filler\\TestCOM",
"backup_path": "InLavoro\\PLC\\Varios\\Filler\\TestCOM",
"schedule_config": {
"schedule": "daily",
"schedule_time": "02:00",
"enabled": true,
"next_scheduled_backup": ""
},
"backup_history": {
"last_backup_date": "2025-09-02T10:52:23.522388+00:00",
"last_backup_file": "D:\\Backups\\AutoBackups\\InLavoro\\PLC\\Varios\\Filler\\TestCOM\\2025-09-02\\12-52-23_projects.zip",
"backup_count": 1,
"last_successful_backup": "2025-09-02T10:52:23.522388+00:00"
},
"hash_info": {
"last_s7p_hash": "",
"last_full_hash": "",
"last_s7p_timestamp": "",
"last_s7p_size": 0,
"last_scan_timestamp": "",
"file_count": 0,
"total_size_bytes": 0
},
"status": {
"current_status": "ready",
"last_error": null,
"retry_count": 0,
"next_retry": null,
"files_in_use": false,
"exclusivity_check_passed": true,
"last_status_update": "2025-09-02T10:52:23.522388+00:00"
},
"discovery_info": {
"discovered_date": "",
"discovery_method": "",
"auto_discovered": true
}
},
{
"id": "project_86a0b02d_20250902_125148",
"name": "SAE446Mi",
"path": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\InLavoro\\PLC\\Varios\\Last Backup\\SAE446Mi",
"type": "siemens_s7",
"s7p_file": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\InLavoro\\PLC\\Varios\\Last Backup\\SAE446Mi\\SAE446Mi.s7p",
"observation_directory": "C:/Trabajo/SIDEL/05 - E5.007161 - Modifica O&U - SAE346",
"relative_path": "InLavoro\\PLC\\Varios\\Last Backup\\SAE446Mi",
"backup_path": "InLavoro\\PLC\\Varios\\Last Backup\\SAE446Mi",
"schedule_config": {
"schedule": "daily",
"schedule_time": "02:00",
"enabled": true,
"next_scheduled_backup": ""
},
"backup_history": {
"last_backup_date": "2025-09-02T10:52:24.414156+00:00",
"last_backup_file": "D:\\Backups\\AutoBackups\\InLavoro\\PLC\\Varios\\Last Backup\\SAE446Mi\\2025-09-02\\12-52-23_projects.zip",
"backup_count": 1,
"last_successful_backup": "2025-09-02T10:52:24.414156+00:00"
},
"hash_info": {
"last_s7p_hash": "",
"last_full_hash": "",
"last_s7p_timestamp": "",
"last_s7p_size": 0,
"last_scan_timestamp": "",
"file_count": 0,
"total_size_bytes": 0
},
"status": {
"current_status": "ready",
"last_error": null,
"retry_count": 0,
"next_retry": null,
"files_in_use": false,
"exclusivity_check_passed": true,
"last_status_update": "2025-09-02T10:52:24.414156+00:00"
},
"discovery_info": {
"discovered_date": "",
"discovery_method": "",
"auto_discovered": true
}
},
{
"id": "project_06372986_20250902_125148",
"name": "SAE446Mi",
"path": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\Reporte\\LastWork\\PLC_Sae446_Mixer_20241217\\SAE446Mi",
"type": "siemens_s7",
"s7p_file": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\Reporte\\LastWork\\PLC_Sae446_Mixer_20241217\\SAE446Mi\\SAE446Mi.s7p",
"observation_directory": "C:/Trabajo/SIDEL/05 - E5.007161 - Modifica O&U - SAE346",
"relative_path": "Reporte\\LastWork\\PLC_Sae446_Mixer_20241217\\SAE446Mi",
"backup_path": "Reporte\\LastWork\\PLC_Sae446_Mixer_20241217\\SAE446Mi",
"schedule_config": {
"schedule": "daily",
"schedule_time": "02:00",
"enabled": true,
"next_scheduled_backup": ""
},
"backup_history": {
"last_backup_date": "2025-09-02T10:52:24.343664+00:00",
"last_backup_file": "D:\\Backups\\AutoBackups\\Reporte\\LastWork\\PLC_Sae446_Mixer_20241217\\SAE446Mi\\2025-09-02\\12-52-23_projects.zip",
"backup_count": 1,
"last_successful_backup": "2025-09-02T10:52:24.343664+00:00"
},
"hash_info": {
"last_s7p_hash": "",
"last_full_hash": "",
"last_s7p_timestamp": "",
"last_s7p_size": 0,
"last_scan_timestamp": "",
"file_count": 0,
"total_size_bytes": 0
},
"status": {
"current_status": "ready",
"last_error": null,
"retry_count": 0,
"next_retry": null,
"files_in_use": false,
"exclusivity_check_passed": true,
"last_status_update": "2025-09-02T10:52:24.343664+00:00"
}, },
"discovery_info": { "discovery_info": {
"discovered_date": "", "discovered_date": "",

View File

@ -14,6 +14,10 @@ psutil==5.9.5 # System utilities (disk space monitoring)
filelock==3.12.4 # File locking for concurrent access filelock==3.12.4 # File locking for concurrent access
# tkinter is included with Python standard library # tkinter is included with Python standard library
# System Tray Integration
pystray==0.19.5 # System tray icon support for Windows
Pillow==11.3.0 # Image processing for icons
# Web Interface # Web Interface
Jinja2==3.1.2 # Template engine for Flask Jinja2==3.1.2 # Template engine for Flask

51
setup.bat Normal file
View File

@ -0,0 +1,51 @@
@echo off
echo AutoBackups - Setup Script
echo ============================
echo.
echo [1/4] Activating conda environment...
call conda activate autobackups
if errorlevel 1 (
echo Error: Could not activate conda environment 'autobackups'
echo.
echo Creating new environment...
call conda create --name autobackups python=3.12 -y
if errorlevel 1 (
echo Error: Could not create conda environment
pause
exit /b 1
)
call conda activate autobackups
)
echo [2/4] Installing Python dependencies...
pip install -r requirements.txt
if errorlevel 1 (
echo Error: Could not install dependencies
pause
exit /b 1
)
echo [3/4] Checking favicon file...
if not exist "static\icons\favicon.png" (
echo Warning: favicon.png not found in static\icons\
echo System tray icon may not display correctly
)
echo [4/4] Testing system tray functionality...
python -c "import pystray, PIL; print('✓ System tray dependencies OK')"
if errorlevel 1 (
echo Error: System tray dependencies not working
pause
exit /b 1
)
echo.
echo ✓ Setup completed successfully!
echo.
echo Usage:
echo Regular mode: python src\app.py
echo System tray mode: python launch_tray.py
echo Or use: launch_with_tray.bat
echo.
pause

View File

@ -22,6 +22,7 @@ sys.path.insert(0, str(src_dir))
from models.config_model import Config from models.config_model import Config
from models.project_model import ProjectManager from models.project_model import ProjectManager
from utils.file_utils import DiskSpaceChecker from utils.file_utils import DiskSpaceChecker
from utils.system_tray import SystemTrayManager
from services.basic_backup_service import BasicBackupService from services.basic_backup_service import BasicBackupService
from routes import register_api_routes, register_web_routes from routes import register_api_routes, register_web_routes
@ -40,6 +41,7 @@ class AutoBackupsFlaskApp:
self.backup_service = None self.backup_service = None
self.scheduler = None self.scheduler = None
self.logger = None self.logger = None
self.system_tray = None
# Inicializar aplicación # Inicializar aplicación
self._setup_logging() self._setup_logging()
@ -47,6 +49,7 @@ class AutoBackupsFlaskApp:
self._initialize_services() self._initialize_services()
self._setup_scheduler() self._setup_scheduler()
self._register_routes() self._register_routes()
self._setup_system_tray()
def _setup_logging(self): def _setup_logging(self):
"""Configurar sistema de logging""" """Configurar sistema de logging"""
@ -145,6 +148,31 @@ class AutoBackupsFlaskApp:
register_api_routes(app, self) register_api_routes(app, self)
self.logger.info("Rutas Flask registradas desde módulos") self.logger.info("Rutas Flask registradas desde módulos")
def _setup_system_tray(self):
"""Inicializa el icono de system tray"""
try:
host = self.config.web_interface.get("host", "127.0.0.1")
port = self.config.web_interface.get("port", 5000)
self.system_tray = SystemTrayManager(self, host, port)
self.logger.info("System tray configurado correctamente")
except Exception as e:
self.logger.error(f"Error configurando system tray: {e}")
self.system_tray = None
def start_system_tray(self):
"""Inicia el icono de system tray"""
if self.system_tray:
success = self.system_tray.start_tray()
if success:
self.logger.info("System tray iniciado correctamente")
return True
else:
self.logger.warning("No se pudo iniciar el system tray")
return False
return False
def scheduled_project_scan(self): def scheduled_project_scan(self):
"""Escaneo programado de proyectos""" """Escaneo programado de proyectos"""
try: try:
@ -250,19 +278,30 @@ def main():
debug = autobackups_app.config.web_interface.get("debug", False) debug = autobackups_app.config.web_interface.get("debug", False)
print(f"\nServidor web iniciando en http://{host}:{port}") print(f"\nServidor web iniciando en http://{host}:{port}")
# Iniciar system tray
autobackups_app.start_system_tray()
print("Presiona Ctrl+C para detener el servidor") print("Presiona Ctrl+C para detener el servidor")
print("Aplicación disponible en el system tray")
# Iniciar servidor Flask # Iniciar servidor Flask
app.run(host=host, port=port, debug=debug, use_reloader=False) app.run(host=host, port=port, debug=debug, use_reloader=False)
except KeyboardInterrupt: except KeyboardInterrupt:
print("\nAplicación interrumpida por el usuario") print("\nAplicación interrumpida por el usuario")
if autobackups_app and autobackups_app.scheduler: if autobackups_app:
autobackups_app.scheduler.shutdown() if autobackups_app.scheduler:
autobackups_app.scheduler.shutdown()
if autobackups_app.system_tray:
autobackups_app.system_tray.stop_tray()
except Exception as e: except Exception as e:
print(f"Error inesperado: {e}") print(f"Error inesperado: {e}")
if autobackups_app and autobackups_app.scheduler: if autobackups_app:
autobackups_app.scheduler.shutdown() if autobackups_app.scheduler:
autobackups_app.scheduler.shutdown()
if autobackups_app.system_tray:
autobackups_app.system_tray.stop_tray()
sys.exit(1) sys.exit(1)

154
src/utils/system_tray.py Normal file
View File

@ -0,0 +1,154 @@
"""
System Tray functionality for AutoBackups application
Provides system tray icon with options to open browser and close application
"""
import threading
import webbrowser
import os
from pathlib import Path
import pystray
from PIL import Image
import logging
class SystemTrayManager:
"""Manages system tray icon and menu for AutoBackups"""
def __init__(self, app_instance, host="127.0.0.1", port=5000):
"""
Initialize system tray manager
Args:
app_instance: The main AutoBackups Flask application instance
host: Web server host (default: 127.0.0.1)
port: Web server port (default: 5000)
"""
self.app_instance = app_instance
self.host = host
self.port = port
self.icon = None
self.logger = logging.getLogger(__name__)
# Get icon path
self.icon_path = self._get_icon_path()
def _get_icon_path(self):
"""Get the path to the favicon.png icon"""
current_dir = Path(__file__).parent.parent.parent
icon_path = current_dir / "static" / "icons" / "favicon.png"
if not icon_path.exists():
self.logger.warning(f"Icon file not found at {icon_path}")
return None
return str(icon_path)
def _load_icon_image(self):
"""Load the icon image from file"""
if not self.icon_path:
# Create a simple default icon if file not found
return Image.new("RGB", (64, 64), color="blue")
try:
return Image.open(self.icon_path)
except Exception as e:
self.logger.error(f"Error loading icon image: {e}")
# Return a simple default icon
return Image.new("RGB", (64, 64), color="blue")
def _open_browser(self, icon, item):
"""Open web browser to the application URL"""
url = f"http://{self.host}:{self.port}"
try:
webbrowser.open(url)
self.logger.info(f"Opened browser to {url}")
except Exception as e:
self.logger.error(f"Error opening browser: {e}")
def _quit_application(self, icon, item):
"""Quit the application"""
try:
self.logger.info("Shutting down application from system tray")
# Stop the scheduler if it exists
if (
self.app_instance
and hasattr(self.app_instance, "scheduler")
and self.app_instance.scheduler
):
self.app_instance.scheduler.shutdown(wait=False)
# Stop the system tray icon
if self.icon:
self.icon.stop()
# Exit the application
os._exit(0)
except Exception as e:
self.logger.error(f"Error during application shutdown: {e}")
os._exit(1)
def _show_status(self, icon, item):
"""Show application status (placeholder for future implementation)"""
# This could show a status window or notification in the future
self.logger.info("Status requested from system tray")
def create_tray_icon(self):
"""Create and configure the system tray icon"""
try:
# Load the icon image
image = self._load_icon_image()
# Create menu items
menu = pystray.Menu(
pystray.MenuItem(
"Open AutoBackups",
self._open_browser,
# This will be the action when double-clicking the icon
default=True,
),
pystray.MenuItem("Status", self._show_status),
pystray.Menu.SEPARATOR,
pystray.MenuItem("Exit", self._quit_application),
)
# Create the icon
self.icon = pystray.Icon(
"AutoBackups", image, "AutoBackups - Automatic Backup System", menu
)
self.logger.info("System tray icon created successfully")
return self.icon
except Exception as e:
self.logger.error(f"Error creating system tray icon: {e}")
return None
def start_tray(self):
"""Start the system tray icon in a separate thread"""
if not self.icon:
self.icon = self.create_tray_icon()
if self.icon:
try:
# Run the tray icon in a separate thread
tray_thread = threading.Thread(target=self.icon.run, daemon=True)
tray_thread.start()
self.logger.info("System tray started successfully")
return True
except Exception as e:
self.logger.error(f"Error starting system tray: {e}")
return False
return False
def stop_tray(self):
"""Stop the system tray icon"""
if self.icon:
try:
self.icon.stop()
self.logger.info("System tray stopped")
except Exception as e:
self.logger.error(f"Error stopping system tray: {e}")

View File

@ -5,6 +5,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}AutoBackups{% endblock %}</title> <title>{% block title %}AutoBackups{% endblock %}</title>
<!-- Favicon -->
<link rel="icon" type="image/png" href="{{ url_for('static', filename='icons/favicon.png') }}">
<!-- Bootstrap CSS --> <!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons --> <!-- Bootstrap Icons -->