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") self.script_metadata_path = os.path.join(data_path, "launcher_script_metadata.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) # Inicializar launcher_script_metadata.json if not os.path.exists(self.script_metadata_path): default_metadata = { "version": "1.0", "script_metadata": {} } with open(self.script_metadata_path, 'w', encoding='utf-8') as f: json.dump(default_metadata, 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("python_env", "base") # Entorno por defecto 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) # Limpiar metadatos de scripts del grupo eliminado self._cleanup_script_metadata_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 con metadatos""" try: group = self.get_launcher_group(group_id) if not group: return [] directory = group["directory"] if not os.path.isdir(directory): return [] # Cargar metadatos de scripts script_metadata = self._load_script_metadata() 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): # Clave para metadatos metadata_key = f"{group_id}_{file}" metadata = script_metadata.get(metadata_key, {}) # Verificar si est谩 oculto if metadata.get("hidden", False): continue scripts.append({ "name": file, "display_name": metadata.get("display_name", file[:-3]), "description": metadata.get("description", ""), "long_description": metadata.get("long_description", ""), "path": script_path, "size": os.path.getsize(script_path), "modified": datetime.fromtimestamp(os.path.getmtime(script_path)).isoformat(), "hidden": metadata.get("hidden", False) }) return sorted(scripts, key=lambda x: x["display_name"]) except Exception as e: print(f"Error getting scripts for group {group_id}: {e}") return [] def get_all_group_scripts(self, group_id: str) -> List[Dict[str, Any]]: """Obtener TODOS los scripts de un grupo (incluyendo ocultos) para gesti贸n""" try: group = self.get_launcher_group(group_id) if not group: return [] directory = group["directory"] if not os.path.isdir(directory): return [] # Cargar metadatos de scripts script_metadata = self._load_script_metadata() 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): # Clave para metadatos metadata_key = f"{group_id}_{file}" metadata = script_metadata.get(metadata_key, {}) scripts.append({ "name": file, "display_name": metadata.get("display_name", file[:-3]), "description": metadata.get("description", ""), "long_description": metadata.get("long_description", ""), "path": script_path, "size": os.path.getsize(script_path), "modified": datetime.fromtimestamp(os.path.getmtime(script_path)).isoformat(), "hidden": metadata.get("hidden", False) }) return sorted(scripts, key=lambda x: x["name"]) except Exception as e: print(f"Error getting all scripts for group {group_id}: {e}") return [] def get_script_metadata(self, group_id: str, script_name: str) -> Dict[str, Any]: """Obtener metadatos de un script espec铆fico""" try: script_metadata = self._load_script_metadata() metadata_key = f"{group_id}_{script_name}" return script_metadata.get(metadata_key, { "display_name": script_name[:-3] if script_name.endswith('.py') else script_name, "description": "", "long_description": "", "hidden": False }) except Exception as e: print(f"Error getting script metadata for {group_id}/{script_name}: {e}") return {} def update_script_metadata(self, group_id: str, script_name: str, metadata: Dict[str, Any]) -> Dict[str, str]: """Actualizar metadatos de un script""" try: script_metadata = self._load_script_metadata() metadata_key = f"{group_id}_{script_name}" # Actualizar metadatos script_metadata[metadata_key] = { "display_name": metadata.get("display_name", script_name[:-3]), "description": metadata.get("description", ""), "long_description": metadata.get("long_description", ""), "hidden": metadata.get("hidden", False), "updated_date": datetime.now().isoformat() + "Z" } # Guardar self._save_script_metadata(script_metadata) return {"status": "success", "message": "Metadatos actualizados exitosamente"} except Exception as e: return {"status": "error", "message": f"Error actualizando metadatos: {str(e)}"} def get_available_python_envs(self) -> List[Dict[str, str]]: """Obtener lista de entornos de Python/Miniconda disponibles""" try: envs = [{"name": "base", "display_name": "Base (Sistema)", "path": sys.executable}] # Intentar encontrar Miniconda miniconda_paths = [ r"C:\Users\migue\miniconda3", r"C:\ProgramData\miniconda3", r"C:\miniconda3", os.path.expanduser("~/miniconda3"), os.path.expanduser("~/anaconda3") ] for base_path in miniconda_paths: if os.path.exists(base_path): envs_path = os.path.join(base_path, "envs") if os.path.exists(envs_path): for env_name in os.listdir(envs_path): env_path = os.path.join(envs_path, env_name) python_exe = os.path.join(env_path, "python.exe") if os.path.exists(python_exe): envs.append({ "name": env_name, "display_name": f"{env_name} (Miniconda)", "path": python_exe }) break # Solo usar el primer Miniconda encontrado return envs except Exception as e: print(f"Error getting Python environments: {e}") return [{"name": "base", "display_name": "Base (Sistema)", "path": sys.executable}] 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 y entorno espec铆fico""" 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"} # Determinar el ejecutable de Python a usar python_env = group.get("python_env", "base") python_executable = self._get_python_executable(python_env) # Construir comando cmd = [python_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"Entorno Python: {python_env}") 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, "python_env": python_env, "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_python_executable(self, env_name: str) -> str: """Obtener el ejecutable de Python para un entorno espec铆fico""" if env_name == "base": return sys.executable # Buscar en entornos de Miniconda miniconda_paths = [ r"C:\Users\migue\miniconda3", r"C:\ProgramData\miniconda3", r"C:\miniconda3", os.path.expanduser("~/miniconda3"), os.path.expanduser("~/anaconda3") ] for base_path in miniconda_paths: env_path = os.path.join(base_path, "envs", env_name, "python.exe") if os.path.exists(env_path): return env_path # Fallback al sistema return sys.executable def _load_script_metadata(self) -> Dict[str, Any]: """Cargar metadatos de scripts""" try: with open(self.script_metadata_path, 'r', encoding='utf-8') as f: data = json.load(f) return data.get("script_metadata", {}) except Exception as e: print(f"Error loading script metadata: {e}") return {} def _save_script_metadata(self, metadata: Dict[str, Any]): """Guardar metadatos de scripts""" try: data = { "version": "1.0", "script_metadata": metadata, "updated_date": datetime.now().isoformat() + "Z" } with open(self.script_metadata_path, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) except Exception as e: print(f"Error saving script metadata: {e}") def _cleanup_script_metadata_for_group(self, group_id: str): """Limpiar metadatos de scripts de un grupo eliminado""" try: script_metadata = self._load_script_metadata() # Filtrar metadatos del grupo eliminado filtered_metadata = {k: v for k, v in script_metadata.items() if not k.startswith(f"{group_id}_")} self._save_script_metadata(filtered_metadata) except Exception as e: print(f"Error cleaning up script metadata for group {group_id}: {e}") 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}")