""" 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()