import os import json import subprocess import sys import threading import time from typing import Dict, Any, List, Optional from datetime import datetime import uuid import glob class CSharpLauncherManager: def __init__(self, data_path: str): self.data_path = data_path self.launcher_config_path = os.path.join(data_path, "csharp_launcher_projects.json") self.favorites_path = os.path.join(data_path, "csharp_launcher_favorites.json") self.script_metadata_path = os.path.join(data_path, "csharp_launcher_metadata.json") # Procesos en ejecución para C# self.running_processes = {} self.process_lock = threading.Lock() # Inicializar archivos si no existen self._initialize_files() def _initialize_files(self): """Crear archivos de configuración por defecto si no existen""" # Inicializar csharp_launcher_projects.json if not os.path.exists(self.launcher_config_path): default_config = { "version": "1.0", "projects": [], "categories": { "Aplicaciones": { "color": "#3B82F6", "icon": "🖥️", "subcategories": ["Desktop", "Consola", "WPF"] }, "Herramientas": { "color": "#10B981", "icon": "🔧", "subcategories": ["Utilidades", "Automatización", "Sistema"] }, "Análisis": { "color": "#8B5CF6", "icon": "📊", "subcategories": ["Datos", "Reportes", "Business Intelligence"] }, "Desarrollo": { "color": "#F59E0B", "icon": "💻", "subcategories": ["Testing", "Build Tools", "DevOps"] }, "APIs": { "color": "#EF4444", "icon": "🌐", "subcategories": ["REST", "GraphQL", "Microservicios"] }, "Otros": { "color": "#6B7280", "icon": "📁", "subcategories": ["Misceláneos"] } }, "settings": { "default_execution_directory": "project_directory", "search_debug_first": True, "show_build_output": True } } with open(self.launcher_config_path, 'w', encoding='utf-8') as f: json.dump(default_config, f, indent=2, ensure_ascii=False) # Inicializar csharp_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 csharp_launcher_metadata.json if not os.path.exists(self.script_metadata_path): default_metadata = { "version": "1.0", "executable_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_csharp_projects(self) -> List[Dict[str, Any]]: """Obtener todos los proyectos C#""" try: with open(self.launcher_config_path, 'r', encoding='utf-8') as f: config = json.load(f) return config.get("projects", []) except Exception as e: print(f"Error loading C# projects: {e}") return [] def get_csharp_project(self, project_id: str) -> Optional[Dict[str, Any]]: """Obtener un proyecto específico por ID""" projects = self.get_csharp_projects() for project in projects: if project.get("id") == project_id: return project return None def add_csharp_project(self, project_data: Dict[str, Any]) -> Dict[str, str]: """Agregar nuevo proyecto C#""" try: # Validar datos requeridos required_fields = ["name", "directory"] for field in required_fields: if not project_data.get(field): return {"status": "error", "message": f"Campo requerido: {field}"} # Validar que el directorio existe if not os.path.isdir(project_data["directory"]): return {"status": "error", "message": "El directorio especificado no existe"} # Generar ID único si no se proporciona if not project_data.get("id"): project_data["id"] = str(uuid.uuid4())[:8] # Verificar que el ID no exista if self.get_csharp_project(project_data["id"]): return {"status": "error", "message": "Ya existe un proyecto con este ID"} # Agregar campos por defecto current_time = datetime.now().isoformat() + "Z" project_data.setdefault("description", "") project_data.setdefault("category", "Otros") project_data.setdefault("version", "1.0") project_data.setdefault("author", "") project_data.setdefault("tags", []) project_data.setdefault("dotnet_version", "") # Versión de .NET project_data.setdefault("created_date", current_time) project_data["updated_date"] = current_time # Cargar configuración y agregar proyecto with open(self.launcher_config_path, 'r', encoding='utf-8') as f: config = json.load(f) config["projects"].append(project_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": "Proyecto agregado exitosamente"} except Exception as e: return {"status": "error", "message": f"Error agregando proyecto: {str(e)}"} def update_csharp_project(self, project_id: str, project_data: Dict[str, Any]) -> Dict[str, str]: """Actualizar proyecto existente""" try: with open(self.launcher_config_path, 'r', encoding='utf-8') as f: config = json.load(f) # Buscar y actualizar el proyecto project_found = False for i, project in enumerate(config["projects"]): if project["id"] == project_id: # Mantener ID y fechas de creación project_data["id"] = project_id project_data["created_date"] = project.get("created_date", datetime.now().isoformat() + "Z") project_data["updated_date"] = datetime.now().isoformat() + "Z" config["projects"][i] = project_data project_found = True break if not project_found: return {"status": "error", "message": "Proyecto 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": "Proyecto actualizado exitosamente"} except Exception as e: return {"status": "error", "message": f"Error actualizando proyecto: {str(e)}"} def delete_csharp_project(self, project_id: str) -> Dict[str, str]: """Eliminar proyecto C#""" try: with open(self.launcher_config_path, 'r', encoding='utf-8') as f: config = json.load(f) # Filtrar el proyecto a eliminar original_count = len(config["projects"]) config["projects"] = [p for p in config["projects"] if p["id"] != project_id] if len(config["projects"]) == original_count: return {"status": "error", "message": "Proyecto no encontrado"} with open(self.launcher_config_path, 'w', encoding='utf-8') as f: json.dump(config, f, indent=2, ensure_ascii=False) # Limpiar metadatos y favoritos relacionados self._cleanup_metadata_for_project(project_id) self._cleanup_favorites_for_project(project_id) self._cleanup_favorites_for_project(project_id) return {"status": "success", "message": "Proyecto eliminado exitosamente"} except Exception as e: return {"status": "error", "message": f"Error eliminando proyecto: {str(e)}"} def execute_csharp_executable(self, project_id: str, exe_name: str, exe_args: List[str], broadcast_func, working_dir: str = None) -> Dict[str, Any]: """Ejecutar un ejecutable C#""" try: project = self.get_csharp_project(project_id) if not project: return {"status": "error", "message": "Proyecto no encontrado"} # Buscar el ejecutable executables = self.get_all_project_executables(project_id) exe_info = None for exe in executables: if exe["filename"] == exe_name: exe_info = exe break if not exe_info: return {"status": "error", "message": f"Ejecutable '{exe_name}' no encontrado"} exe_path = exe_info["full_path"] # Determinar directorio de trabajo if working_dir and os.path.isdir(working_dir): work_dir = working_dir else: work_dir = os.path.dirname(exe_path) # Preparar comando cmd = [exe_path] + exe_args execution_id = str(uuid.uuid4())[:8] broadcast_func(f"🚀 Ejecutando: {exe_info['display_name']}") broadcast_func(f"📁 Directorio: {work_dir}") broadcast_func(f"⚡ Comando: {' '.join(cmd)}") broadcast_func("=" * 50) try: # Ejecutar el proceso process = subprocess.Popen( cmd, cwd=work_dir, creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == "win32" else 0 ) # Almacenar información del proceso with self.process_lock: self.running_processes[process.pid] = { "project_id": project_id, "exe_name": exe_name, "display_name": exe_info['display_name'], "start_time": datetime.now(), "process": process } broadcast_func(f"✅ Proceso iniciado con PID: {process.pid}") return { "status": "success", "message": f"Ejecutable '{exe_info['display_name']}' iniciado", "pid": process.pid, "execution_id": execution_id } except subprocess.SubprocessError as e: return {"status": "error", "message": f"Error ejecutando el proceso: {str(e)}"} except Exception as e: return {"status": "error", "message": f"Error inesperado: {str(e)}"} except Exception as e: return {"status": "error", "message": f"Error ejecutando ejecutable: {str(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 C# favorites: {e}") return [] def toggle_favorite(self, project_id: str, exe_name: str) -> Dict[str, str]: """Agregar o quitar de favoritos""" try: with open(self.favorites_path, 'r', encoding='utf-8') as f: data = json.load(f) favorites = data.get("favorites", []) favorite_key = f"{project_id}_{exe_name}" # Buscar si ya existe existing_favorite = None for fav in favorites: if fav.get("project_id") == project_id and fav.get("exe_name") == exe_name: existing_favorite = fav break if existing_favorite: # Quitar de favoritos favorites.remove(existing_favorite) message = "Removido de favoritos" is_favorite = False else: # Agregar a favoritos favorites.append({ "id": favorite_key, "project_id": project_id, "exe_name": exe_name, "added_date": datetime.now().isoformat() + "Z" }) message = "Agregado a favoritos" is_favorite = True 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", "message": message, "is_favorite": is_favorite} except Exception as e: return {"status": "error", "message": f"Error toggle favorite: {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 C# categories: {e}") return {} def get_running_processes(self) -> List[Dict[str, Any]]: """Obtener procesos C# en ejecución""" processes = [] with self.process_lock: for pid, info in list(self.running_processes.items()): try: # Verificar si el proceso sigue activo process = info["process"] if process.poll() is None: processes.append({ "pid": pid, "project_id": info["project_id"], "exe_name": info["exe_name"], "display_name": info["display_name"], "start_time": info["start_time"].isoformat() + "Z" }) else: # Proceso terminado, remover de la lista del self.running_processes[pid] except: # Error verificando proceso, remover del self.running_processes[pid] return processes def terminate_process(self, pid: int) -> Dict[str, str]: """Cerrar un proceso C#""" try: with self.process_lock: if pid in self.running_processes: process_info = self.running_processes[pid] process = process_info["process"] try: process.terminate() process.wait(timeout=5) del self.running_processes[pid] return { "status": "success", "message": f"Proceso {process_info['display_name']} (PID: {pid}) terminado" } except subprocess.TimeoutExpired: process.kill() del self.running_processes[pid] return { "status": "success", "message": f"Proceso {process_info['display_name']} (PID: {pid}) forzado a cerrar" } else: return {"status": "error", "message": "Proceso no encontrado en ejecución"} except Exception as e: return {"status": "error", "message": f"Error terminando proceso: {str(e)}"} def _load_executable_metadata(self) -> Dict[str, Any]: """Cargar metadatos de ejecutables desde archivo""" try: with open(self.script_metadata_path, 'r', encoding='utf-8') as f: return json.load(f) except (FileNotFoundError, json.JSONDecodeError): return {"version": "1.0", "executable_metadata": {}} def get_project_executables(self, project_id: str) -> List[Dict[str, Any]]: """Obtener ejecutables de un proyecto específico (solo visibles)""" project = self.get_csharp_project(project_id) if not project: return [] project_dir = project["directory"] if not os.path.isdir(project_dir): return [] executables = [] metadata = self._load_executable_metadata() # Buscar en bin/Release y bin/Debug search_patterns = [ os.path.join(project_dir, "**/bin/Release/**/*.exe"), os.path.join(project_dir, "**/bin/Debug/**/*.exe") ] found_exe_files = set() for pattern in search_patterns: for exe_path in glob.glob(pattern, recursive=True): if os.path.isfile(exe_path): found_exe_files.add(exe_path) for exe_path in found_exe_files: exe_name = os.path.basename(exe_path) exe_key = f"{project_id}_{exe_name}" # Obtener metadatos o crear por defecto exe_metadata = metadata.get("executable_metadata", {}).get(exe_key, {}) # Solo incluir si no está oculto if not exe_metadata.get("hidden", False): # Determinar si es Debug o Release build_type = "Release" if "\\Release\\" in exe_path or "/Release/" in exe_path else "Debug" executables.append({ "filename": exe_name, "full_path": exe_path, "display_name": exe_metadata.get("display_name", exe_name.replace('.exe', '')), "short_description": exe_metadata.get("short_description", f"Aplicación C# ({build_type})"), "long_description": exe_metadata.get("long_description", ""), "build_type": build_type, "relative_path": os.path.relpath(exe_path, project_dir) }) # Ordenar por nombre de display executables.sort(key=lambda x: x['display_name']) return executables def get_all_project_executables(self, project_id: str) -> List[Dict[str, Any]]: """Obtener TODOS los ejecutables de un proyecto (incluyendo ocultos) para gestión""" project = self.get_csharp_project(project_id) if not project: return [] project_dir = project["directory"] if not os.path.isdir(project_dir): return [] executables = [] metadata = self._load_executable_metadata() # Buscar en bin/Release y bin/Debug search_patterns = [ os.path.join(project_dir, "**/bin/Release/**/*.exe"), os.path.join(project_dir, "**/bin/Debug/**/*.exe") ] found_exe_files = set() for pattern in search_patterns: for exe_path in glob.glob(pattern, recursive=True): if os.path.isfile(exe_path): found_exe_files.add(exe_path) for exe_path in found_exe_files: exe_name = os.path.basename(exe_path) exe_key = f"{project_id}_{exe_name}" # Obtener metadatos o crear por defecto exe_metadata = metadata.get("executable_metadata", {}).get(exe_key, {}) # Determinar si es Debug o Release build_type = "Release" if "\\Release\\" in exe_path or "/Release/" in exe_path else "Debug" executables.append({ "filename": exe_name, "full_path": exe_path, "display_name": exe_metadata.get("display_name", exe_name.replace('.exe', '')), "short_description": exe_metadata.get("short_description", f"Aplicación C# ({build_type})"), "long_description": exe_metadata.get("long_description", ""), "build_type": build_type, "relative_path": os.path.relpath(exe_path, project_dir), "hidden": exe_metadata.get("hidden", False) }) # Ordenar por nombre de display executables.sort(key=lambda x: x['display_name']) return executables def get_executable_metadata(self, project_id: str, exe_name: str) -> Dict[str, Any]: """Obtener metadatos de un ejecutable específico""" metadata = self._load_executable_metadata() exe_key = f"{project_id}_{exe_name}" return metadata.get("executable_metadata", {}).get(exe_key, { "display_name": exe_name.replace('.exe', ''), "short_description": "Aplicación C#", "long_description": "", "hidden": False }) def update_executable_metadata(self, project_id: str, exe_name: str, metadata_update: Dict[str, Any]) -> Dict[str, str]: """Actualizar metadatos de un ejecutable específico""" try: metadata = self._load_executable_metadata() exe_key = f"{project_id}_{exe_name}" if "executable_metadata" not in metadata: metadata["executable_metadata"] = {} if exe_key not in metadata["executable_metadata"]: metadata["executable_metadata"][exe_key] = {} # Actualizar campos metadata["executable_metadata"][exe_key].update(metadata_update) self._save_executable_metadata(metadata) return {"status": "success", "message": "Metadatos actualizados exitosamente"} except Exception as e: return {"status": "error", "message": f"Error actualizando metadatos: {str(e)}"} def execute_csharp_executable(self, project_id: str, exe_name: str, exe_args: List[str], broadcast_func, working_dir: str = None) -> Dict[str, Any]: """Ejecutar un ejecutable C#""" try: project = self.get_csharp_project(project_id) if not project: return {"status": "error", "message": "Proyecto no encontrado"} # Buscar el ejecutable executables = self.get_all_project_executables(project_id) exe_info = None for exe in executables: if exe["filename"] == exe_name: exe_info = exe break if not exe_info: return {"status": "error", "message": f"Ejecutable '{exe_name}' no encontrado"} exe_path = exe_info["full_path"] # Determinar directorio de trabajo if working_dir and os.path.isdir(working_dir): work_dir = working_dir else: work_dir = os.path.dirname(exe_path) # Preparar comando cmd = [exe_path] + exe_args execution_id = str(uuid.uuid4())[:8] broadcast_func(f"🚀 Ejecutando: {exe_info['display_name']}") broadcast_func(f"📁 Directorio: {work_dir}") broadcast_func(f"⚡ Comando: {' '.join(cmd)}") broadcast_func("=" * 50) try: # Ejecutar el proceso process = subprocess.Popen( cmd, cwd=work_dir, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, universal_newlines=True, creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == "win32" else 0 ) # Almacenar información del proceso with self.process_lock: self.running_processes[process.pid] = { "project_id": project_id, "exe_name": exe_name, "display_name": exe_info['display_name'], "start_time": datetime.now(), "process": process } broadcast_func(f"✅ Proceso iniciado con PID: {process.pid}") # Monitorear salida en hilo separado def read_output(): try: for line in iter(process.stdout.readline, ''): if line.strip(): broadcast_func(line.rstrip()) except Exception as e: broadcast_func(f"Error leyendo salida: {e}") finally: if process.stdout: process.stdout.close() output_thread = threading.Thread(target=read_output, daemon=True) output_thread.start() # Monitorear finalización en hilo separado def monitor_completion(): try: start_time = time.time() return_code = process.wait() execution_time = time.time() - start_time # Limpiar de procesos en ejecución with self.process_lock: if process.pid in self.running_processes: del self.running_processes[process.pid] if return_code == 0: broadcast_func(f"✅ Proceso completado exitosamente (PID: {process.pid})") else: broadcast_func(f"❌ Proceso terminó con código: {return_code} (PID: {process.pid})") broadcast_func(f"⏱️ Tiempo de ejecución: {execution_time:.2f} segundos") broadcast_func("=" * 50) except Exception as e: broadcast_func(f"Error monitoreando proceso: {e}") monitor_thread = threading.Thread(target=monitor_completion, daemon=True) monitor_thread.start() return { "status": "success", "message": f"Ejecutable '{exe_info['display_name']}' iniciado", "pid": process.pid, "execution_id": execution_id } except subprocess.SubprocessError as e: return {"status": "error", "message": f"Error ejecutando el proceso: {str(e)}"} except Exception as e: return {"status": "error", "message": f"Error inesperado: {str(e)}"} except Exception as e: return {"status": "error", "message": f"Error ejecutando ejecutable: {str(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 C# favorites: {e}") return [] def toggle_favorite(self, project_id: str, exe_name: str) -> Dict[str, str]: """Agregar o quitar de favoritos""" try: with open(self.favorites_path, 'r', encoding='utf-8') as f: data = json.load(f) favorites = data.get("favorites", []) favorite_key = f"{project_id}_{exe_name}" # Buscar si ya existe existing_favorite = None for fav in favorites: if fav.get("project_id") == project_id and fav.get("exe_name") == exe_name: existing_favorite = fav break if existing_favorite: # Quitar de favoritos favorites.remove(existing_favorite) message = "Removido de favoritos" is_favorite = False else: # Agregar a favoritos favorites.append({ "id": favorite_key, "project_id": project_id, "exe_name": exe_name, "added_date": datetime.now().isoformat() + "Z" }) message = "Agregado a favoritos" is_favorite = True 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", "message": message, "is_favorite": is_favorite } except Exception as e: return {"status": "error", "message": f"Error gestionando favorito: {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 C# categories: {e}") return {} def get_running_processes(self) -> List[Dict[str, Any]]: """Obtener procesos C# en ejecución""" with self.process_lock: processes = [] for pid, info in self.running_processes.items(): try: # Verificar si el proceso sigue activo if info["process"].poll() is None: processes.append({ "pid": pid, "project_id": info["project_id"], "exe_name": info["exe_name"], "display_name": info["display_name"], "start_time": info["start_time"].isoformat() }) else: # Proceso terminado, remover de la lista del self.running_processes[pid] except: # Error verificando proceso, remover del self.running_processes[pid] return processes def terminate_process(self, pid: int) -> Dict[str, str]: """Cerrar un proceso C#""" try: with self.process_lock: if pid in self.running_processes: process_info = self.running_processes[pid] process = process_info["process"] try: process.terminate() process.wait(timeout=5) del self.running_processes[pid] return { "status": "success", "message": f"Proceso {process_info['display_name']} (PID: {pid}) terminado" } except subprocess.TimeoutExpired: process.kill() del self.running_processes[pid] return { "status": "success", "message": f"Proceso {process_info['display_name']} (PID: {pid}) forzado a cerrar" } else: return {"status": "error", "message": "Proceso no encontrado en ejecución"} except Exception as e: return {"status": "error", "message": f"Error terminando proceso: {str(e)}"} def _load_executable_metadata(self) -> Dict[str, Any]: """Cargar metadatos de ejecutables desde archivo""" try: with open(self.script_metadata_path, 'r', encoding='utf-8') as f: return json.load(f) except (FileNotFoundError, json.JSONDecodeError): return {"version": "1.0", "executable_metadata": {}} def _save_executable_metadata(self, metadata: Dict[str, Any]): """Guardar metadatos de ejecutables a archivo""" try: with open(self.script_metadata_path, 'w', encoding='utf-8') as f: json.dump(metadata, f, indent=2, ensure_ascii=False) except Exception as e: print(f"Error saving C# executable metadata: {e}") def _cleanup_metadata_for_project(self, project_id: str): """Limpiar metadatos de un proyecto eliminado""" try: metadata = self._load_executable_metadata() if "executable_metadata" in metadata: # Remover metadatos que empiecen con project_id_ keys_to_remove = [key for key in metadata["executable_metadata"].keys() if key.startswith(f"{project_id}_")] for key in keys_to_remove: del metadata["executable_metadata"][key] self._save_executable_metadata(metadata) except Exception as e: print(f"Error cleaning metadata for project {project_id}: {e}") def _cleanup_favorites_for_project(self, project_id: str): """Limpiar favoritos de un proyecto eliminado""" try: with open(self.favorites_path, 'r', encoding='utf-8') as f: data = json.load(f) # Filtrar favoritos que no sean del proyecto eliminado data["favorites"] = [fav for fav in data.get("favorites", []) if fav.get("project_id") != project_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 favorites for project {project_id}: {e}") def get_executable_arguments(self, project_id: str, exe_name: str) -> List[Dict[str, str]]: """Obtener argumentos predefinidos de un ejecutable""" try: metadata = self._load_executable_metadata() exe_key = f"{project_id}_{exe_name}" if "executable_metadata" in metadata and exe_key in metadata["executable_metadata"]: return metadata["executable_metadata"][exe_key].get("arguments", []) return [] except Exception as e: print(f"Error loading executable arguments: {e}") return [] def update_executable_arguments(self, project_id: str, exe_name: str, arguments: List[Dict[str, str]]) -> Dict[str, str]: """Actualizar argumentos predefinidos de un ejecutable""" try: metadata = self._load_executable_metadata() exe_key = f"{project_id}_{exe_name}" # Crear estructura si no existe if "executable_metadata" not in metadata: metadata["executable_metadata"] = {} if exe_key not in metadata["executable_metadata"]: metadata["executable_metadata"][exe_key] = {} # Validar formato de argumentos for arg in arguments: if not isinstance(arg, dict) or "description" not in arg or "arguments" not in arg: return {"status": "error", "message": "Formato de argumentos inválido. Debe ser [{'description': '...', 'arguments': '...'}]"} # Actualizar argumentos metadata["executable_metadata"][exe_key]["arguments"] = arguments metadata["executable_metadata"][exe_key]["updated_date"] = datetime.now().isoformat() + "Z" self._save_executable_metadata(metadata) return {"status": "success", "message": "Argumentos actualizados exitosamente"} except Exception as e: return {"status": "error", "message": f"Error actualizando argumentos: {str(e)}"} def get_all_project_executables(self, project_id: str) -> List[Dict[str, Any]]: """Obtener TODOS los ejecutables de un proyecto (incluyendo ocultos) para gestión""" project = self.get_csharp_project(project_id) if not project: return [] project_dir = project["directory"] if not os.path.isdir(project_dir): return [] executables = [] metadata = self._load_executable_metadata() # Buscar en bin/Release y bin/Debug search_patterns = [ os.path.join(project_dir, "**/bin/Release/**/*.exe"), os.path.join(project_dir, "**/bin/Debug/**/*.exe") ] found_exe_files = set() for pattern in search_patterns: for exe_path in glob.glob(pattern, recursive=True): if os.path.isfile(exe_path): found_exe_files.add(exe_path) for exe_path in found_exe_files: exe_name = os.path.basename(exe_path) exe_key = f"{project_id}_{exe_name}" # Obtener metadatos o crear por defecto exe_metadata = metadata.get("executable_metadata", {}).get(exe_key, {}) # Determinar si es Debug o Release build_type = "Release" if "\\Release\\" in exe_path or "/Release/" in exe_path else "Debug" executables.append({ "filename": exe_name, "full_path": exe_path, "display_name": exe_metadata.get("display_name", exe_name.replace('.exe', '')), "short_description": exe_metadata.get("short_description", f"Aplicación C# ({build_type})"), "long_description": exe_metadata.get("long_description", ""), "build_type": build_type, "relative_path": os.path.relpath(exe_path, project_dir), "hidden": exe_metadata.get("hidden", False) }) # Ordenar por nombre de display executables.sort(key=lambda x: x['display_name']) return executables def get_executable_metadata(self, project_id: str, exe_name: str) -> Dict[str, Any]: """Obtener metadatos de un ejecutable específico""" metadata = self._load_executable_metadata() exe_key = f"{project_id}_{exe_name}" return metadata.get("executable_metadata", {}).get(exe_key, { "display_name": exe_name.replace('.exe', ''), "short_description": "Aplicación C#", "long_description": "", "hidden": False }) def update_executable_metadata(self, project_id: str, exe_name: str, metadata_update: Dict[str, Any]) -> Dict[str, str]: """Actualizar metadatos de un ejecutable específico""" try: metadata = self._load_executable_metadata() exe_key = f"{project_id}_{exe_name}" if "executable_metadata" not in metadata: metadata["executable_metadata"] = {} if exe_key not in metadata["executable_metadata"]: metadata["executable_metadata"][exe_key] = {} # Actualizar campos metadata["executable_metadata"][exe_key].update(metadata_update) self._save_executable_metadata(metadata) return {"status": "success", "message": "Metadatos actualizados exitosamente"} except Exception as e: return {"status": "error", "message": f"Error actualizando metadatos: {str(e)}"} def _save_executable_metadata(self, metadata: Dict[str, Any]): """Guardar metadatos de ejecutables a archivo""" try: with open(self.script_metadata_path, 'w', encoding='utf-8') as f: json.dump(metadata, f, indent=2, ensure_ascii=False) except Exception as e: print(f"Error saving C# executable metadata: {e}") def _cleanup_metadata_for_project(self, project_id: str): """Limpiar metadatos de un proyecto eliminado""" try: metadata = self._load_executable_metadata() if "executable_metadata" in metadata: # Remover metadatos que empiecen con project_id_ keys_to_remove = [key for key in metadata["executable_metadata"].keys() if key.startswith(f"{project_id}_")] for key in keys_to_remove: del metadata["executable_metadata"][key] self._save_executable_metadata(metadata) except Exception as e: print(f"Error cleaning metadata for project {project_id}: {e}") def _cleanup_favorites_for_project(self, project_id: str): """Limpiar favoritos de un proyecto eliminado""" try: with open(self.favorites_path, 'r', encoding='utf-8') as f: data = json.load(f) # Filtrar favoritos que no sean del proyecto eliminado data["favorites"] = [fav for fav in data.get("favorites", []) if fav.get("project_id") != project_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 favorites for project {project_id}: {e}")