ParamManagerScripts/lib/launcher_manager.py

416 lines
17 KiB
Python

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