ParamManagerScripts/lib/csharp_launcher_manager.py

980 lines
42 KiB
Python

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