import os import json import subprocess import sys from typing import Dict, Any, List, Optional from datetime import datetime import uuid class LauncherManager: def __init__(self, data_path: str): self.data_path = data_path self.launcher_config_path = os.path.join(data_path, "launcher_scripts.json") self.favorites_path = os.path.join(data_path, "launcher_favorites.json") self.history_path = os.path.join(data_path, "launcher_history.json") # Inicializar archivos si no existen self._initialize_files() def _initialize_files(self): """Crear archivos de configuraci贸n por defecto si no existen""" # Inicializar launcher_scripts.json if not os.path.exists(self.launcher_config_path): default_config = { "version": "1.0", "groups": [], "categories": { "Herramientas": { "color": "#3B82F6", "icon": "馃敡", "subcategories": ["Generales", "Desarrollo", "Sistema"] }, "An谩lisis": { "color": "#10B981", "icon": "馃搳", "subcategories": ["Datos", "Estad铆sticas", "Visualizaci贸n"] }, "Utilidades": { "color": "#8B5CF6", "icon": "鈿欙笍", "subcategories": ["Archivos", "Texto", "Conversi贸n"] }, "Desarrollo": { "color": "#F59E0B", "icon": "馃捇", "subcategories": ["Code", "Testing", "Deploy"] }, "Visualizaci贸n": { "color": "#EF4444", "icon": "馃搱", "subcategories": ["Gr谩ficos", "Reportes", "Dashboard"] }, "Otros": { "color": "#6B7280", "icon": "馃搧", "subcategories": ["Miscel谩neos"] } }, "settings": { "default_execution_directory": "script_directory", "enable_argument_validation": True, "max_history_entries": 100, "auto_cleanup_days": 30 } } with open(self.launcher_config_path, 'w', encoding='utf-8') as f: json.dump(default_config, f, indent=2, ensure_ascii=False) # Inicializar launcher_favorites.json if not os.path.exists(self.favorites_path): default_favorites = {"favorites": []} with open(self.favorites_path, 'w', encoding='utf-8') as f: json.dump(default_favorites, f, indent=2, ensure_ascii=False) # Inicializar launcher_history.json if not os.path.exists(self.history_path): default_history = { "history": [], "settings": { "max_entries": 100, "auto_cleanup_days": 30, "track_execution_time": True, "track_arguments": True } } with open(self.history_path, 'w', encoding='utf-8') as f: json.dump(default_history, f, indent=2, ensure_ascii=False) def get_launcher_groups(self) -> List[Dict[str, Any]]: """Obtener todos los grupos de scripts GUI""" try: with open(self.launcher_config_path, 'r', encoding='utf-8') as f: config = json.load(f) return config.get("groups", []) except Exception as e: print(f"Error loading launcher groups: {e}") return [] def get_launcher_group(self, group_id: str) -> Optional[Dict[str, Any]]: """Obtener un grupo espec铆fico por ID""" groups = self.get_launcher_groups() for group in groups: if group.get("id") == group_id: return group return None def add_launcher_group(self, group_data: Dict[str, Any]) -> Dict[str, str]: """Agregar nuevo grupo de scripts GUI""" try: # Validar datos requeridos required_fields = ["name", "directory"] for field in required_fields: if not group_data.get(field): return {"status": "error", "message": f"Campo requerido: {field}"} # Validar que el directorio existe if not os.path.isdir(group_data["directory"]): return {"status": "error", "message": "El directorio especificado no existe"} # Generar ID 煤nico si no se proporciona if not group_data.get("id"): group_data["id"] = str(uuid.uuid4())[:8] # Verificar que el ID no exista if self.get_launcher_group(group_data["id"]): return {"status": "error", "message": "Ya existe un grupo con este ID"} # Agregar campos por defecto current_time = datetime.now().isoformat() + "Z" group_data.setdefault("description", "") group_data.setdefault("category", "Otros") group_data.setdefault("version", "1.0") group_data.setdefault("author", "") group_data.setdefault("tags", []) group_data.setdefault("created_date", current_time) group_data["updated_date"] = current_time # Cargar configuraci贸n y agregar grupo with open(self.launcher_config_path, 'r', encoding='utf-8') as f: config = json.load(f) config["groups"].append(group_data) with open(self.launcher_config_path, 'w', encoding='utf-8') as f: json.dump(config, f, indent=2, ensure_ascii=False) return {"status": "success", "message": "Grupo agregado exitosamente"} except Exception as e: return {"status": "error", "message": f"Error agregando grupo: {str(e)}"} def update_launcher_group(self, group_id: str, group_data: Dict[str, Any]) -> Dict[str, str]: """Actualizar grupo existente""" try: with open(self.launcher_config_path, 'r', encoding='utf-8') as f: config = json.load(f) # Buscar y actualizar el grupo group_found = False for i, group in enumerate(config["groups"]): if group["id"] == group_id: # Mantener ID y fechas de creaci贸n group_data["id"] = group_id group_data["created_date"] = group.get("created_date", datetime.now().isoformat() + "Z") group_data["updated_date"] = datetime.now().isoformat() + "Z" config["groups"][i] = group_data group_found = True break if not group_found: return {"status": "error", "message": "Grupo no encontrado"} with open(self.launcher_config_path, 'w', encoding='utf-8') as f: json.dump(config, f, indent=2, ensure_ascii=False) return {"status": "success", "message": "Grupo actualizado exitosamente"} except Exception as e: return {"status": "error", "message": f"Error actualizando grupo: {str(e)}"} def delete_launcher_group(self, group_id: str) -> Dict[str, str]: """Eliminar grupo de scripts GUI""" try: with open(self.launcher_config_path, 'r', encoding='utf-8') as f: config = json.load(f) # Filtrar el grupo a eliminar original_count = len(config["groups"]) config["groups"] = [g for g in config["groups"] if g["id"] != group_id] if len(config["groups"]) == original_count: return {"status": "error", "message": "Grupo no encontrado"} with open(self.launcher_config_path, 'w', encoding='utf-8') as f: json.dump(config, f, indent=2, ensure_ascii=False) # Limpiar favoritos del grupo eliminado self._cleanup_favorites_for_group(group_id) return {"status": "success", "message": "Grupo eliminado exitosamente"} except Exception as e: return {"status": "error", "message": f"Error eliminando grupo: {str(e)}"} def get_group_scripts(self, group_id: str) -> List[Dict[str, Any]]: """Obtener scripts de un grupo espec铆fico""" try: group = self.get_launcher_group(group_id) if not group: return [] directory = group["directory"] if not os.path.isdir(directory): return [] scripts = [] for file in os.listdir(directory): if file.endswith('.py') and not file.startswith('_'): script_path = os.path.join(directory, file) if os.path.isfile(script_path): scripts.append({ "name": file, "display_name": file[:-3], # Sin extensi贸n .py "path": script_path, "size": os.path.getsize(script_path), "modified": datetime.fromtimestamp(os.path.getmtime(script_path)).isoformat() }) return sorted(scripts, key=lambda x: x["name"]) except Exception as e: print(f"Error getting scripts for group {group_id}: {e}") return [] def execute_gui_script(self, group_id: str, script_name: str, script_args: List[str], broadcast_func) -> Dict[str, Any]: """Ejecutar script GUI con argumentos opcionales""" try: group = self.get_launcher_group(group_id) if not group: return {"status": "error", "message": "Grupo no encontrado"} script_path = os.path.join(group["directory"], script_name) if not os.path.isfile(script_path): return {"status": "error", "message": "Script no encontrado"} # Construir comando cmd = [sys.executable, script_path] + script_args working_dir = group["directory"] # Por defecto directorio del script broadcast_func(f"Ejecutando script GUI: {script_name}") broadcast_func(f"Comando: {' '.join(cmd)}") broadcast_func(f"Directorio: {working_dir}") # Ejecutar script start_time = datetime.now() process = subprocess.Popen( cmd, cwd=working_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == "win32" else 0 ) # Registrar en historial execution_id = str(uuid.uuid4())[:8] self._add_to_history({ "id": execution_id, "group_id": group_id, "script_name": script_name, "executed_date": start_time.isoformat() + "Z", "arguments": script_args, "working_directory": working_dir, "status": "running", "pid": process.pid }) broadcast_func(f"Script GUI ejecutado con PID: {process.pid}") broadcast_func(f"ID de ejecuci贸n: {execution_id}") return { "status": "success", "message": "Script GUI ejecutado exitosamente", "execution_id": execution_id, "pid": process.pid } except Exception as e: error_msg = f"Error ejecutando script GUI: {str(e)}" broadcast_func(error_msg) return {"status": "error", "message": error_msg} def get_favorites(self) -> List[Dict[str, Any]]: """Obtener lista de favoritos""" try: with open(self.favorites_path, 'r', encoding='utf-8') as f: data = json.load(f) return data.get("favorites", []) except Exception as e: print(f"Error loading favorites: {e}") return [] def toggle_favorite(self, group_id: str, script_name: str) -> Dict[str, str]: """Agregar o quitar script de favoritos""" try: with open(self.favorites_path, 'r', encoding='utf-8') as f: data = json.load(f) favorites = data.get("favorites", []) favorite_id = f"{group_id}_{script_name}" # Buscar si ya existe existing_favorite = None for i, fav in enumerate(favorites): if fav["group_id"] == group_id and fav["script_name"] == script_name: existing_favorite = i break if existing_favorite is not None: # Quitar de favoritos favorites.pop(existing_favorite) action = "removed" else: # Agregar a favoritos favorites.append({ "id": favorite_id, "group_id": group_id, "script_name": script_name, "added_date": datetime.now().isoformat() + "Z", "execution_count": 0, "last_executed": None }) action = "added" data["favorites"] = favorites with open(self.favorites_path, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) return {"status": "success", "action": action} except Exception as e: return {"status": "error", "message": f"Error managing favorite: {str(e)}"} def get_history(self) -> List[Dict[str, Any]]: """Obtener historial de ejecuci贸n""" try: with open(self.history_path, 'r', encoding='utf-8') as f: data = json.load(f) return data.get("history", []) except Exception as e: print(f"Error loading history: {e}") return [] def clear_history(self) -> Dict[str, str]: """Limpiar historial de ejecuci贸n""" try: with open(self.history_path, 'r', encoding='utf-8') as f: data = json.load(f) data["history"] = [] with open(self.history_path, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) return {"status": "success", "message": "Historial limpiado exitosamente"} except Exception as e: return {"status": "error", "message": f"Error clearing history: {str(e)}"} def get_categories(self) -> Dict[str, Any]: """Obtener categor铆as disponibles""" try: with open(self.launcher_config_path, 'r', encoding='utf-8') as f: config = json.load(f) return config.get("categories", {}) except Exception as e: print(f"Error loading categories: {e}") return {} def _add_to_history(self, entry: Dict[str, Any]): """Agregar entrada al historial""" try: with open(self.history_path, 'r', encoding='utf-8') as f: data = json.load(f) history = data.get("history", []) history.insert(0, entry) # Agregar al inicio # Limitar tama帽o del historial max_entries = data.get("settings", {}).get("max_entries", 100) if len(history) > max_entries: history = history[:max_entries] data["history"] = history with open(self.history_path, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) except Exception as e: print(f"Error adding to history: {e}") def _cleanup_favorites_for_group(self, group_id: str): """Limpiar favoritos de un grupo eliminado""" try: with open(self.favorites_path, 'r', encoding='utf-8') as f: data = json.load(f) # Filtrar favoritos del grupo eliminado data["favorites"] = [f for f in data.get("favorites", []) if f.get("group_id") != group_id] with open(self.favorites_path, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) except Exception as e: print(f"Error cleaning up favorites for group {group_id}: {e}")