AutoBackups/src/app.py

271 lines
9.6 KiB
Python

"""
AutoBackups - Aplicación Principal Flask
Sistema automatizado de backup para proyectos Simatic S7
"""
import sys
import os
import logging
from pathlib import Path
from datetime import datetime
from flask import Flask, render_template, request, jsonify, redirect, url_for
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
import atexit
# Agregar el directorio src al path para imports
current_dir = Path(__file__).parent
src_dir = current_dir
sys.path.insert(0, str(src_dir))
# Imports de módulos propios
from models.config_model import Config
from models.project_model import ProjectManager
from utils.file_utils import DiskSpaceChecker
from services.basic_backup_service import BasicBackupService
from routes import register_api_routes, register_web_routes
# Crear instancia Flask
app = Flask(__name__, template_folder="../templates", static_folder="../static")
class AutoBackupsFlaskApp:
"""Aplicación principal de AutoBackups con Flask"""
def __init__(self):
self.config = None
self.project_manager = None
self.disk_checker = None
self.backup_service = None
self.scheduler = None
self.logger = None
# Inicializar aplicación
self._setup_logging()
self._load_configuration()
self._initialize_services()
self._setup_scheduler()
self._register_routes()
def _setup_logging(self):
"""Configurar sistema de logging"""
try:
# Crear directorio de logs si no existe
logs_dir = Path(__file__).parent.parent / ".logs"
logs_dir.mkdir(exist_ok=True)
# Nombre del archivo de log con timestamp
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
log_filename = logs_dir / f"autobackups_{timestamp}.log"
# Configurar logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(log_filename, encoding="utf-8"),
logging.StreamHandler(sys.stdout),
],
)
self.logger = logging.getLogger(__name__)
self.logger.info(f"AutoBackups Flask iniciado - Log: {log_filename}")
except Exception as e:
print(f"Error configurando logging: {e}")
sys.exit(1)
def _load_configuration(self):
"""Cargar configuración del sistema"""
try:
self.logger.info("Cargando configuración...")
self.config = Config()
self.logger.info("Configuración cargada exitosamente")
# Mostrar información básica de configuración
dirs_count = len(self.config.observation_directories)
self.logger.info(f"Directorios de observación: {dirs_count}")
self.logger.info(f"Destino: {self.config.backup_destination}")
except Exception as e:
self.logger.error(f"Error cargando configuración: {e}")
sys.exit(1)
def _initialize_services(self):
"""Inicializar servicios principales"""
try:
self.logger.info("Inicializando servicios...")
# Project Manager
self.project_manager = ProjectManager()
self.logger.info("Project Manager inicializado")
# Disk Space Checker
self.disk_checker = DiskSpaceChecker()
self.logger.info("Disk Space Checker inicializado")
# Basic Backup Service
self.backup_service = BasicBackupService(self.config)
self.logger.info("Basic Backup Service inicializado")
self.logger.info("Todos los servicios inicializados correctamente")
except Exception as e:
self.logger.error(f"Error inicializando servicios: {e}")
sys.exit(1)
def _setup_scheduler(self):
"""Configurar scheduler para tareas automáticas"""
try:
self.scheduler = BackgroundScheduler(timezone="America/Mexico_City")
# Agregar job de escaneo cada hora
self.scheduler.add_job(
func=self.scheduled_project_scan,
trigger=CronTrigger(minute=0), # Cada hora en el minuto 0
id="project_scan",
name="Escaneo de proyectos automático",
replace_existing=True,
)
# Iniciar scheduler
self.scheduler.start()
self.logger.info("Scheduler configurado e iniciado")
# Asegurar que el scheduler se detenga al cerrar la aplicación
atexit.register(lambda: self.scheduler.shutdown())
except Exception as e:
self.logger.error(f"Error configurando scheduler: {e}")
def _register_routes(self):
"""Registrar rutas de Flask usando módulos separados"""
register_web_routes(app, self)
register_api_routes(app, self)
self.logger.info("Rutas Flask registradas desde módulos")
def scheduled_project_scan(self):
"""Escaneo programado de proyectos"""
try:
self.logger.info("Iniciando escaneo programado de proyectos...")
projects_found = self.discover_projects()
self.logger.info(f"Escaneo completado: {projects_found} proyectos")
except Exception as e:
self.logger.error(f"Error en escaneo programado: {e}")
def discover_projects(self) -> int:
"""Descubrir proyectos en directorios de observación"""
try:
self.logger.info("Iniciando descubrimiento de proyectos...")
# Usar el servicio básico de backup
if hasattr(self, "backup_service"):
projects = self.backup_service.discover_projects_basic()
else:
projects = []
# Obtener proyectos existentes para evitar duplicados
existing_projects = self.project_manager.get_all_projects()
existing_paths = {project.path for project in existing_projects}
# Agregar solo proyectos nuevos al manager
new_projects_count = 0
for project_info in projects:
project_path = project_info.get("path")
if project_path not in existing_paths:
self.project_manager.add_or_update_project(project_info)
new_projects_count += 1
self.logger.debug(
f"Nuevo proyecto agregado: {project_info.get('name')}"
)
else:
self.logger.debug(
f"Proyecto ya existe, omitido: {project_info.get('name')}"
)
msg = f"Descubrimiento completado: {len(projects)} proyectos encontrados, {new_projects_count} nuevos agregados"
self.logger.info(msg)
return new_projects_count
except Exception as e:
self.logger.error(f"Error en descubrimiento de proyectos: {e}")
return 0
def check_system_requirements(self) -> bool:
"""Verificar requerimientos del sistema"""
try:
self.logger.info("Verificando requerimientos del sistema...")
# Verificar espacio en disco
backup_destination = self.config.backup_destination
min_space_mb = self.config.get_min_free_space_mb()
free_space_mb = self.disk_checker.get_free_space_mb(backup_destination)
if free_space_mb < min_space_mb:
self.logger.error(
f"Espacio insuficiente en destino de backup. "
f"Disponible: {free_space_mb:.1f}MB, "
f"Requerido: {min_space_mb}MB"
)
return False
self.logger.info(f"Espacio en disco OK: {free_space_mb:.1f}MB disponibles")
return True
except Exception as e:
self.logger.error(f"Error verificando requerimientos del sistema: {e}")
return False
# Instancia global de la aplicación
autobackups_app = None
def main():
"""Función principal"""
global autobackups_app
try:
print("AutoBackups - Sistema de Backup Automatizado (Flask)")
print("=" * 55)
# 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()
print(f"Descubrimiento inicial: {projects_found} proyectos encontrados")
# 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"\nServidor web iniciando en http://{host}:{port}")
print("Presiona Ctrl+C para detener el servidor")
# Iniciar servidor Flask
app.run(host=host, port=port, debug=debug, use_reloader=False)
except KeyboardInterrupt:
print("\nAplicación interrumpida por el usuario")
if autobackups_app and autobackups_app.scheduler:
autobackups_app.scheduler.shutdown()
except Exception as e:
print(f"Error inesperado: {e}")
if autobackups_app and autobackups_app.scheduler:
autobackups_app.scheduler.shutdown()
sys.exit(1)
if __name__ == "__main__":
main()