update
This commit is contained in:
parent
386e8c1693
commit
fe7e592a28
|
|
@ -0,0 +1,135 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Script para instalación automática de dependencias y verificación de entorno virtual
|
||||||
|
|
||||||
|
# Función para instalar dependencias del sistema
|
||||||
|
install_system_dependencies() {
|
||||||
|
echo "🔍 Verificando dependencias del sistema..."
|
||||||
|
|
||||||
|
MISSING=()
|
||||||
|
|
||||||
|
# Verificar tkinter
|
||||||
|
if ! python3 -c "import tkinter" 2>/dev/null; then
|
||||||
|
MISSING+=("python3-tk")
|
||||||
|
else
|
||||||
|
echo "✅ tkinter disponible"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verificar VLC
|
||||||
|
if ! command -v vlc &>/dev/null; then
|
||||||
|
MISSING+=("vlc")
|
||||||
|
else
|
||||||
|
echo "✅ vlc disponible"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#MISSING[@]} -gt 0 ]; then
|
||||||
|
echo "📦 Instalando paquetes del sistema: ${MISSING[*]}"
|
||||||
|
if command -v apt &>/dev/null; then
|
||||||
|
sudo apt update && sudo apt install -y "${MISSING[@]}"
|
||||||
|
elif command -v dnf &>/dev/null; then
|
||||||
|
sudo dnf install -y "${MISSING[@]}"
|
||||||
|
elif command -v pacman &>/dev/null; then
|
||||||
|
sudo pacman -S --noconfirm "${MISSING[@]}"
|
||||||
|
else
|
||||||
|
echo "❌ Gestor de paquetes no soportado. Instala manualmente: ${MISSING[*]}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "✅ Dependencias del sistema instaladas"
|
||||||
|
else
|
||||||
|
echo "✅ Todas las dependencias del sistema están disponibles"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Función para comprobar y activar entorno virtual
|
||||||
|
check_virtual_env() {
|
||||||
|
echo "🔍 Verificando entorno virtual..."
|
||||||
|
|
||||||
|
if [[ "$VIRTUAL_ENV" != "" ]]; then
|
||||||
|
echo "✅ Entorno virtual activado: $VIRTUAL_ENV"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "❌ No se detectó un entorno virtual activado"
|
||||||
|
|
||||||
|
# Buscar directorio venv en el proyecto
|
||||||
|
if [ -d "venv" ]; then
|
||||||
|
echo "📁 Se encontró directorio 'venv'. Activando..."
|
||||||
|
source venv/bin/activate
|
||||||
|
if [[ "$VIRTUAL_ENV" != "" ]]; then
|
||||||
|
echo "✅ Entorno virtual activado correctamente"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "❌ Error al activar el entorno virtual"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "❌ No se encontró directorio 'venv'. Creando entorno virtual..."
|
||||||
|
python3 -m venv venv
|
||||||
|
source venv/bin/activate
|
||||||
|
echo "✅ Entorno virtual creado y activado"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Función para instalar dependencias
|
||||||
|
install_dependencies() {
|
||||||
|
echo "📦 Actualizando pip..."
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
|
||||||
|
# Verificar si existe requirements.txt
|
||||||
|
if [ -f "requirements.txt" ]; then
|
||||||
|
echo "📋 Instalando dependencias desde requirements.txt..."
|
||||||
|
pip install -r requirements.txt
|
||||||
|
echo "✅ Dependencias instaladas correctamente"
|
||||||
|
else
|
||||||
|
echo "⚠️ No se encontró requirements.txt"
|
||||||
|
echo "ℹ️ Crea un requirements.txt con: pip freeze > requirements.txt"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Función para verificar instalación
|
||||||
|
verify_installation() {
|
||||||
|
echo "🔍 Verificando instalación..."
|
||||||
|
python -c "import sys; print(f'✅ Python {sys.version.split()[0]} funcionando correctamente')"
|
||||||
|
|
||||||
|
echo "📋 Paquetes instalados:"
|
||||||
|
pip list
|
||||||
|
}
|
||||||
|
|
||||||
|
# Función principal
|
||||||
|
main() {
|
||||||
|
echo "🚀 Iniciando script de instalación..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Comprobar entorno virtual
|
||||||
|
if ! check_virtual_env; then
|
||||||
|
echo "❌ Error en la configuración del entorno virtual"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Instalar dependencias del sistema (tkinter, vlc)
|
||||||
|
if ! install_system_dependencies; then
|
||||||
|
echo "❌ Error instalando dependencias del sistema"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Instalar dependencias de Python
|
||||||
|
install_dependencies
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Verificar instalación
|
||||||
|
verify_installation
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🎉 Instalación completada con éxito!"
|
||||||
|
echo "💡 Para activar el entorno virtual manualmente: source venv/bin/activate"
|
||||||
|
echo "💡 Para ejecutar el proyecto: python __main__.py"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ejecutar función principal
|
||||||
|
main
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
# Módulo logica/auxiliar/add_audio.py
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
|
||||||
|
# 🔑 RUTA CONFIRMADA Desde la raíz del proyecto
|
||||||
|
NOMBRE_FICHERO = os.path.join("res", "radios.json")
|
||||||
|
|
||||||
|
|
||||||
|
class RadioManager {
|
||||||
|
"""
|
||||||
|
Gestiona la lectura, escritura y adición de emisoras en el archivo radios.json.
|
||||||
|
Utiliza un Lock para asegurar la seguridad al escribir el archivo.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, filename=NOMBRE_FICHERO) {
|
||||||
|
self.filename = filename
|
||||||
|
# Bloqueo para operaciones de archivo
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
self.emisoras_cargadas = self.cargar_emisoras()
|
||||||
|
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
# GESTIÓN DE ARCHIVO
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
}
|
||||||
|
|
||||||
|
def cargar_emisoras(self) {
|
||||||
|
"""Carga las emisoras existentes desde el archivo."""
|
||||||
|
with self._lock {
|
||||||
|
if os.path.exists(self.filename) {
|
||||||
|
try {
|
||||||
|
with open(self.filename, 'r', encoding='utf-8') as f {
|
||||||
|
return json.load(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
except json.JSONDecodeError {
|
||||||
|
print("⚠️ Error al leer el archivo {self.filename}. Retornando lista vacía.")
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
def guardar_emisoras(self) {
|
||||||
|
"""Guarda la lista de emisoras actual en memoria en formato JSON."""
|
||||||
|
with self._lock {
|
||||||
|
try {
|
||||||
|
with open(self.filename, 'w', encoding='utf-8') as f {
|
||||||
|
json.dump(self.emisoras_cargadas, f, indent=4, ensure_ascii=False)
|
||||||
|
}
|
||||||
|
print(f"\n✅ [RadioManager] Archivo '{self.filename}' guardado.")
|
||||||
|
return True
|
||||||
|
}
|
||||||
|
except Exception as e {
|
||||||
|
print(f"\n❌ [RadioManager] Error al intentar guardar el archivo {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
# GESTIÓN DE DATOS EN MEMORIA
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def get_emisoras(self) {
|
||||||
|
"""Retorna la lista de emisoras cargadas en memoria."""
|
||||||
|
return self.emisoras_cargadas
|
||||||
|
}
|
||||||
|
|
||||||
|
def add_radio(self, nombre, url, pais=None, genero=None) {
|
||||||
|
"""
|
||||||
|
Añade una nueva emisora a la lista en memoria y guarda el archivo.
|
||||||
|
"""
|
||||||
|
if not nombre or not url {
|
||||||
|
print("❌ El nombre y la URL son obligatorios.")
|
||||||
|
return False
|
||||||
|
}
|
||||||
|
|
||||||
|
nueva_emisora = {
|
||||||
|
"nombre" nombre.strip(),
|
||||||
|
"url_stream" url.strip(),
|
||||||
|
"pais" pais.strip() if pais else None,
|
||||||
|
"genero" genero.strip() if genero else None
|
||||||
|
}
|
||||||
|
|
||||||
|
self.emisoras_cargadas.append(nueva_emisora)
|
||||||
|
print(f"✅ [RadioManager] Emisora '{nombre}' añadida a la lista en memoria.")
|
||||||
|
|
||||||
|
# Guarda inmediatamente
|
||||||
|
self.guardar_emisoras()
|
||||||
|
|
||||||
|
return True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
import socket
|
||||||
|
|
||||||
|
from logica.red.servidor import APP_FIRMA, PUERTO_BROADCAST
|
||||||
|
|
||||||
|
|
||||||
|
def descubrir_servidores(timeout=5):
|
||||||
|
"""Escucha broadcasts UDP para encontrar servidores disponibles.
|
||||||
|
|
||||||
|
Retorna una lista de tuplas (ip, puerto) de servidores encontrados.
|
||||||
|
"""
|
||||||
|
print(f"[DEBUG CLI] Iniciando descubrimiento UDP en puerto {PUERTO_BROADCAST} (timeout={timeout}s)")
|
||||||
|
servidores = []
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
if hasattr(socket, "SO_REUSEPORT"):
|
||||||
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
||||||
|
sock.settimeout(timeout)
|
||||||
|
try:
|
||||||
|
sock.bind(("", PUERTO_BROADCAST))
|
||||||
|
print(f"[DEBUG CLI] Socket UDP enlazado a '':{PUERTO_BROADCAST}")
|
||||||
|
except OSError as e:
|
||||||
|
print(f"[DEBUG CLI] Error al enlazar UDP: {e}")
|
||||||
|
sock.close()
|
||||||
|
return servidores
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
datos, direccion = sock.recvfrom(1024)
|
||||||
|
mensaje = datos.decode("utf-8")
|
||||||
|
print(f"[DEBUG CLI] Paquete UDP recibido de {direccion}: {mensaje!r}")
|
||||||
|
if verificar_firma(mensaje):
|
||||||
|
partes = mensaje.split("|")
|
||||||
|
if len(partes) == 2:
|
||||||
|
puerto_servidor = int(partes[1])
|
||||||
|
entrada = (direccion[0], puerto_servidor)
|
||||||
|
if entrada not in servidores:
|
||||||
|
servidores.append(entrada)
|
||||||
|
print(f"[DEBUG CLI] Servidor descubierto: {entrada[0]}:{entrada[1]}")
|
||||||
|
else:
|
||||||
|
print(f"[DEBUG CLI] Firma no valida, ignorado")
|
||||||
|
except socket.timeout:
|
||||||
|
print(f"[DEBUG CLI] Timeout alcanzado")
|
||||||
|
finally:
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
print(f"[DEBUG CLI] Descubrimiento finalizado: {len(servidores)} servidor(es)")
|
||||||
|
return servidores
|
||||||
|
|
||||||
|
|
||||||
|
def conectar_servidor(ip, puerto, clave_acceso):
|
||||||
|
"""Conecta al servidor y envia la clave de autenticacion.
|
||||||
|
|
||||||
|
La clave debe tener el formato: puerto#contraseña_alfabetica
|
||||||
|
"""
|
||||||
|
print(f"[DEBUG CLI] Conectando TCP a {ip}:{puerto}")
|
||||||
|
print(f"[DEBUG CLI] Clave a enviar: {clave_acceso!r}")
|
||||||
|
cliente = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
cliente.settimeout(10)
|
||||||
|
|
||||||
|
try:
|
||||||
|
cliente.connect((ip, puerto))
|
||||||
|
print(f"[DEBUG CLI] Conexion TCP establecida")
|
||||||
|
cliente.sendall(clave_acceso.encode("utf-8"))
|
||||||
|
print(f"[DEBUG CLI] Clave enviada, esperando respuesta...")
|
||||||
|
respuesta = cliente.recv(1024).decode("utf-8")
|
||||||
|
print(f"[DEBUG CLI] Respuesta recibida: {respuesta!r}")
|
||||||
|
except (OSError, socket.timeout) as e:
|
||||||
|
print(f"[DEBUG CLI] Error en conexion/autenticacion: {e}")
|
||||||
|
cliente.close()
|
||||||
|
return None, ""
|
||||||
|
|
||||||
|
if respuesta.startswith("OK"):
|
||||||
|
cliente.settimeout(None)
|
||||||
|
extra = respuesta[2:]
|
||||||
|
print(f"[DEBUG CLI] Autenticacion exitosa, datos extra: {extra!r}")
|
||||||
|
return cliente, extra
|
||||||
|
else:
|
||||||
|
print(f"[DEBUG CLI] Autenticacion fallida: {respuesta!r}")
|
||||||
|
cliente.close()
|
||||||
|
return None, ""
|
||||||
|
|
||||||
|
|
||||||
|
def verificar_firma(mensaje):
|
||||||
|
"""Verifica que el mensaje recibido contenga la firma de la aplicacion."""
|
||||||
|
return APP_FIRMA in mensaje
|
||||||
|
|
@ -0,0 +1,188 @@
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from logica.red.servidor import iniciar_servidor, autenticar_cliente, PUERTO_BROADCAST
|
||||||
|
from logica.red.cliente import conectar_servidor
|
||||||
|
|
||||||
|
|
||||||
|
def obtener_ip_local():
|
||||||
|
"""Obtiene la IP local de la maquina en la red."""
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
||||||
|
try:
|
||||||
|
s.connect(("8.8.8.8", 80))
|
||||||
|
return s.getsockname()[0]
|
||||||
|
except OSError:
|
||||||
|
return "127.0.0.1"
|
||||||
|
|
||||||
|
|
||||||
|
def modo_servidor():
|
||||||
|
servidor, puerto, contrasena_alfa, _broadcast_stop = iniciar_servidor()
|
||||||
|
clave_acceso = f"{puerto}#{contrasena_alfa}"
|
||||||
|
ip = obtener_ip_local()
|
||||||
|
print(f"[SERVIDOR] IP: {ip}")
|
||||||
|
print(f"[SERVIDOR] Escuchando en puerto {puerto}")
|
||||||
|
print(f"[SERVIDOR] Clave de acceso: {clave_acceso}")
|
||||||
|
print("[SERVIDOR] Esperando clientes...\n")
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
conn, addr = servidor.accept()
|
||||||
|
datos = conn.recv(1024).decode("utf-8")
|
||||||
|
if autenticar_cliente(datos, clave_acceso):
|
||||||
|
conn.sendall("OK".encode("utf-8"))
|
||||||
|
print(f"[SERVIDOR] Cliente {addr[0]}:{addr[1]} autenticado")
|
||||||
|
hilo = threading.Thread(target=manejar_cliente, args=(conn, addr), daemon=True)
|
||||||
|
hilo.start()
|
||||||
|
else:
|
||||||
|
conn.sendall("DENIED".encode("utf-8"))
|
||||||
|
conn.close()
|
||||||
|
print(f"[SERVIDOR] Cliente {addr[0]}:{addr[1]} rechazado (clave incorrecta)")
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n[SERVIDOR] Cerrando...")
|
||||||
|
servidor.close()
|
||||||
|
|
||||||
|
|
||||||
|
def manejar_cliente(conn, addr):
|
||||||
|
etiqueta = f"{addr[0]}:{addr[1]}"
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
datos = conn.recv(4096)
|
||||||
|
if not datos:
|
||||||
|
break
|
||||||
|
mensaje = datos.decode("utf-8")
|
||||||
|
print(f"[{etiqueta}] {mensaje}")
|
||||||
|
conn.sendall(f"Echo: {mensaje}".encode("utf-8"))
|
||||||
|
except (ConnectionResetError, OSError):
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
print(f"[SERVIDOR] Cliente {etiqueta} desconectado")
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def buscar_servidores():
|
||||||
|
"""Busca servidores con reintentos. Retorna lista de servidores o None."""
|
||||||
|
MAX_INTENTOS = 3
|
||||||
|
TIMEOUT = 5
|
||||||
|
|
||||||
|
for intento in range(1, MAX_INTENTOS + 1):
|
||||||
|
print(f"\n[CLIENTE] Intento {intento}/{MAX_INTENTOS} - Escaneando red durante {TIMEOUT}s...")
|
||||||
|
print(f"[CLIENTE] Puerto de descubrimiento (UDP): {PUERTO_BROADCAST}")
|
||||||
|
print(f"[CLIENTE] Esperando broadcast de servidores", end="", flush=True)
|
||||||
|
|
||||||
|
# Animacion simple mientras espera
|
||||||
|
servidores = []
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
if hasattr(socket, "SO_REUSEPORT"):
|
||||||
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
||||||
|
sock.settimeout(1)
|
||||||
|
sock.bind(("", PUERTO_BROADCAST))
|
||||||
|
|
||||||
|
for segundo in range(TIMEOUT):
|
||||||
|
try:
|
||||||
|
datos, direccion = sock.recvfrom(1024)
|
||||||
|
mensaje = datos.decode("utf-8")
|
||||||
|
from logica.red.cliente import verificar_firma
|
||||||
|
if verificar_firma(mensaje):
|
||||||
|
partes = mensaje.split("|")
|
||||||
|
if len(partes) == 2:
|
||||||
|
puerto_servidor = int(partes[1])
|
||||||
|
entrada = (direccion[0], puerto_servidor)
|
||||||
|
if entrada not in servidores:
|
||||||
|
servidores.append(entrada)
|
||||||
|
print(f"\n[CLIENTE] Servidor encontrado: {entrada[0]}:{entrada[1]}")
|
||||||
|
except socket.timeout:
|
||||||
|
print(".", end="", flush=True)
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
if servidores:
|
||||||
|
print(f"\n[CLIENTE] {len(servidores)} servidor(es) encontrado(s)")
|
||||||
|
return servidores
|
||||||
|
|
||||||
|
print(f"\n[CLIENTE] No se encontraron servidores en el intento {intento}")
|
||||||
|
|
||||||
|
# Tras 3 intentos fallidos, preguntar
|
||||||
|
while True:
|
||||||
|
opcion = input("\n[CLIENTE] No se encontraron servidores. (r)eintentar / (s)alir: ").strip().lower()
|
||||||
|
if opcion == "r":
|
||||||
|
return buscar_servidores()
|
||||||
|
elif opcion == "s":
|
||||||
|
return None
|
||||||
|
print("[CLIENTE] Opcion no valida. Usa 'r' o 's'.")
|
||||||
|
|
||||||
|
|
||||||
|
def modo_cliente():
|
||||||
|
ip_local = obtener_ip_local()
|
||||||
|
print(f"[CLIENTE] IP local: {ip_local}")
|
||||||
|
servidores = buscar_servidores()
|
||||||
|
if not servidores:
|
||||||
|
print("[CLIENTE] Saliendo.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(servidores) == 1:
|
||||||
|
seleccion = 0
|
||||||
|
else:
|
||||||
|
print("\nServidores disponibles:")
|
||||||
|
for i, (ip, puerto) in enumerate(servidores, 1):
|
||||||
|
print(f" {i}. {ip}:{puerto}")
|
||||||
|
try:
|
||||||
|
num = int(input("\nElige servidor (numero): "))
|
||||||
|
seleccion = num - 1
|
||||||
|
if seleccion < 0 or seleccion >= len(servidores):
|
||||||
|
print("[CLIENTE] Seleccion invalida.")
|
||||||
|
return
|
||||||
|
except ValueError:
|
||||||
|
print("[CLIENTE] Entrada invalida.")
|
||||||
|
return
|
||||||
|
|
||||||
|
ip, puerto = servidores[seleccion]
|
||||||
|
print(f"\n[CLIENTE] Conectando a {ip}:{puerto}...")
|
||||||
|
contrasena = input("[CLIENTE] Introduce la contrasena: ")
|
||||||
|
|
||||||
|
cliente, _ = conectar_servidor(ip, puerto, contrasena)
|
||||||
|
if not cliente:
|
||||||
|
print("[CLIENTE] Autenticacion fallida o conexion rechazada.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"[CLIENTE] Conectado correctamente a {ip}:{puerto}")
|
||||||
|
print("[CLIENTE] Escribe mensajes (Ctrl+C para salir):\n")
|
||||||
|
|
||||||
|
def recibir():
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
datos = cliente.recv(4096)
|
||||||
|
if not datos:
|
||||||
|
print("\n[CLIENTE] El servidor cerro la conexion.")
|
||||||
|
break
|
||||||
|
print(f"[SERVIDOR] {datos.decode('utf-8')}")
|
||||||
|
except (ConnectionResetError, OSError):
|
||||||
|
print("\n[CLIENTE] Conexion perdida con el servidor.")
|
||||||
|
|
||||||
|
hilo = threading.Thread(target=recibir, daemon=True)
|
||||||
|
hilo.start()
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
mensaje = input()
|
||||||
|
if mensaje:
|
||||||
|
cliente.sendall(mensaje.encode("utf-8"))
|
||||||
|
except (KeyboardInterrupt, EOFError):
|
||||||
|
print("\n[CLIENTE] Desconectando...")
|
||||||
|
cliente.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("=== Selector de Red ===")
|
||||||
|
print("1. Servidor")
|
||||||
|
print("2. Cliente")
|
||||||
|
|
||||||
|
opcion = input("\nElige rol (1/2): ").strip()
|
||||||
|
|
||||||
|
if opcion == "1":
|
||||||
|
modo_servidor()
|
||||||
|
elif opcion == "2":
|
||||||
|
modo_cliente()
|
||||||
|
else:
|
||||||
|
print("Opcion invalida.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
|
|
||||||
|
# Firma de la aplicacion para identificacion
|
||||||
|
APP_FIRMA = "PSYP_ProyectoGlobal_v1"
|
||||||
|
|
||||||
|
# Rango de puertos validos (evita puertos conocidos 0-1023 y registrados 1024-49151)
|
||||||
|
RANGO_PUERTOS = (49152, 65535)
|
||||||
|
|
||||||
|
|
||||||
|
def _obtener_puertos_en_uso():
|
||||||
|
"""Lee /proc/net/tcp y /proc/net/udp para obtener los puertos en uso."""
|
||||||
|
puertos = set()
|
||||||
|
for archivo in ("/proc/net/tcp", "/proc/net/udp", "/proc/net/tcp6", "/proc/net/udp6"):
|
||||||
|
try:
|
||||||
|
with open(archivo, "r") as f:
|
||||||
|
for linea in f.readlines()[1:]:
|
||||||
|
partes = linea.split()
|
||||||
|
puerto = int(partes[1].split(":")[1], 16)
|
||||||
|
puertos.add(puerto)
|
||||||
|
except (FileNotFoundError, PermissionError):
|
||||||
|
pass
|
||||||
|
return puertos
|
||||||
|
|
||||||
|
|
||||||
|
def _puerto_disponible(puerto, puertos_en_uso=None):
|
||||||
|
"""Comprueba si un puerto esta libre en TCP y UDP."""
|
||||||
|
if puertos_en_uso and puerto in puertos_en_uso:
|
||||||
|
return False
|
||||||
|
for tipo in (socket.SOCK_STREAM, socket.SOCK_DGRAM):
|
||||||
|
with socket.socket(socket.AF_INET, tipo) as s:
|
||||||
|
try:
|
||||||
|
s.bind(("0.0.0.0", puerto))
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def generar_puerto():
|
||||||
|
"""Genera un puerto aleatorio disponible en el rango dinamico/privado."""
|
||||||
|
puertos_en_uso = _obtener_puertos_en_uso()
|
||||||
|
while True:
|
||||||
|
puerto = random.randint(*RANGO_PUERTOS)
|
||||||
|
if _puerto_disponible(puerto, puertos_en_uso):
|
||||||
|
return puerto
|
||||||
|
|
||||||
|
|
||||||
|
def generar_contrasena_alfabetica(longitud=6):
|
||||||
|
"""Genera una contraseña alfabetica aleatoria."""
|
||||||
|
return "".join(random.choices(string.ascii_lowercase, k=longitud))
|
||||||
|
|
||||||
|
|
||||||
|
def iniciar_servidor():
|
||||||
|
"""Inicia el servidor en un puerto TCP aleatorio disponible.
|
||||||
|
|
||||||
|
Retorna (servidor_socket, puerto, contrasena, broadcast_stop_event).
|
||||||
|
"""
|
||||||
|
puerto = generar_puerto()
|
||||||
|
contrasena = generar_contrasena_alfabetica()
|
||||||
|
print(f"[DEBUG SRV] Puerto generado: {puerto}")
|
||||||
|
print(f"[DEBUG SRV] Contrasena alfabetica: {contrasena}")
|
||||||
|
|
||||||
|
servidor = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
servidor.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
servidor.bind(("0.0.0.0", puerto))
|
||||||
|
servidor.listen(5)
|
||||||
|
print(f"[DEBUG SRV] Socket TCP escuchando en 0.0.0.0:{puerto}")
|
||||||
|
|
||||||
|
broadcast_stop = threading.Event()
|
||||||
|
hilo_broadcast = threading.Thread(target=_broadcast_presencia, args=(puerto, broadcast_stop), daemon=True)
|
||||||
|
hilo_broadcast.start()
|
||||||
|
print(f"[DEBUG SRV] Hilo broadcast iniciado en UDP:{PUERTO_BROADCAST}")
|
||||||
|
|
||||||
|
return servidor, puerto, contrasena, broadcast_stop
|
||||||
|
|
||||||
|
|
||||||
|
# Puerto fijo para descubrimiento UDP
|
||||||
|
PUERTO_BROADCAST = 64738
|
||||||
|
|
||||||
|
|
||||||
|
def _broadcast_presencia(puerto, stop_event):
|
||||||
|
"""Envia broadcasts UDP para que los clientes descubran el servidor."""
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||||
|
mensaje = f"{APP_FIRMA}|{puerto}".encode("utf-8")
|
||||||
|
|
||||||
|
while not stop_event.is_set():
|
||||||
|
try:
|
||||||
|
sock.sendto(mensaje, ("<broadcast>", PUERTO_BROADCAST))
|
||||||
|
print(f"[DEBUG SRV] Broadcast enviado a <broadcast>:{PUERTO_BROADCAST}")
|
||||||
|
except OSError as e:
|
||||||
|
print(f"[DEBUG SRV] Error broadcast <broadcast>: {e}")
|
||||||
|
try:
|
||||||
|
sock.sendto(mensaje, ("127.255.255.255", PUERTO_BROADCAST))
|
||||||
|
print(f"[DEBUG SRV] Broadcast enviado a 127.255.255.255:{PUERTO_BROADCAST}")
|
||||||
|
except OSError as e:
|
||||||
|
print(f"[DEBUG SRV] Error broadcast 127.255.255.255: {e}")
|
||||||
|
stop_event.wait(2)
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
|
||||||
|
def autenticar_cliente(datos_recibidos, contrasena):
|
||||||
|
"""Verifica que el cliente envie la contrasena correcta."""
|
||||||
|
resultado = datos_recibidos.strip() == contrasena
|
||||||
|
print(f"[DEBUG SRV] Autenticacion: recibido={datos_recibidos.strip()!r} esperado={contrasena!r} resultado={resultado}")
|
||||||
|
return resultado
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
beautifulsoup4==4.14.3
|
||||||
|
bs4==0.0.2
|
||||||
|
certifi==2026.1.4
|
||||||
|
charset-normalizer==3.4.4
|
||||||
|
contourpy==1.3.3
|
||||||
|
cycler==0.12.1
|
||||||
|
fonttools==4.61.1
|
||||||
|
idna==3.11
|
||||||
|
kiwisolver==1.4.9
|
||||||
|
matplotlib==3.10.8
|
||||||
|
numpy==2.4.1
|
||||||
|
packaging==25.0
|
||||||
|
pillow==12.1.0
|
||||||
|
psutil==7.2.1
|
||||||
|
pyparsing==3.3.1
|
||||||
|
python-dateutil==2.9.0.post0
|
||||||
|
python-vlc==3.0.21203
|
||||||
|
requests==2.32.5
|
||||||
|
six==1.17.0
|
||||||
|
soupsieve==2.8.2
|
||||||
|
typing_extensions==4.15.0
|
||||||
|
urllib3==2.6.3
|
||||||
|
|
@ -128,3 +128,44 @@ Historia
|
||||||
Los primeros pobladores que llegaron a la región fueron los vástagos, los caribes y los arahuacos.[11]
|
Los primeros pobladores que llegaron a la región fueron los vástagos, los caribes y los arahuacos.[11]
|
||||||
La conquista europea del territorio fue iniciada por el alemán Ambrosio Alfinger en 1530, quien llegó el valle de Upar haciendo frente a una considerable resistencia indígena.[12] En esa época, el territor
|
La conquista europea del territorio fue iniciada por el alemán Ambrosio Alfinger en 1530, quien llegó el valle de Upar haciendo frente a una considerable resistencia indígena.[12] En esa época, el territor
|
||||||
[... Contenido truncado ...]
|
[... Contenido truncado ...]
|
||||||
|
|
||||||
|
|
||||||
|
==========================================================
|
||||||
|
== 🌐 DATOS EXTRAÍDOS (Búsqueda: 'google') ==
|
||||||
|
==========================================================
|
||||||
|
URL Artículo: https://es.wikipedia.org/w/index.php?search=google&title=Especial:Buscar&go=Ir
|
||||||
|
TÍTULO: Google
|
||||||
|
FECHA/HORA: 2025-12-08 15:23:14
|
||||||
|
----------------------------------------------------------
|
||||||
|
CONTENIDO (Primeros 1529 chars):
|
||||||
|
----------------------------------------------------------
|
||||||
|
Google LLC (pronunciado /ˈɡuːɡəl/ (escucharⓘ), más conocida como Google) es una empresa de tecnología multinacional con sede en California, Estados Unidos, que se centra en inteligencia artificial, publicidad en línea, tecnología de motores de búsqueda, computación en la nube, software, computación cuántica, comercio electrónico y electrónica de consumo.[1] Es una de las marcas más valiosas del mundo debido a su dominio del mercado, recopilación de datos y ventajas tecnológicas en el campo de la inteligencia artificial. Es considerada una de las cinco grandes compañías tecnológicas junto con Apple, Amazon, Microsoft y Meta Platforms.[2][3][4]
|
||||||
|
Su producto principal es el motor de búsqueda de contenido en internet del mismo nombre, aunque ofrece también otros productos y servicios, como su servicio de almacenamiento en la nube Google Drive, el correo electrónico Gmail, sus servicios de mapas Google Maps, Google Street View y Google Earth, el sitio web de vídeos YouTube, entre otros.
|
||||||
|
Por otra parte, lidera el desarrollo del sistema operativo y servicios de operación de aplicaciones basado en Linux: Android, orientado a teléfonos inteligentes, tabletas, televisores, automóviles y gafas de realidad aumentada, las Google Glass. Su eslogan es «Do the Right Thing» («Haz lo correcto»).[5]
|
||||||
|
Con miles de servidores y centros de datos presentes en todo el mundo, Google es capaz de procesar más de 1000 millones de peticiones de búsqueda diarias y su motor de búsqueda es el sitio web m
|
||||||
|
[... Contenido truncado ...]
|
||||||
|
|
||||||
|
|
||||||
|
==========================================================
|
||||||
|
== 🌐 DATOS EXTRAÍDOS (Búsqueda: 'hola') ==
|
||||||
|
==========================================================
|
||||||
|
URL Artículo: https://es.wikipedia.org/w/index.php?search=hola&title=Especial:Buscar&go=Ir
|
||||||
|
TÍTULO: Hola
|
||||||
|
FECHA/HORA: 2026-01-19 19:05:02
|
||||||
|
----------------------------------------------------------
|
||||||
|
CONTENIDO (Primeros 600 chars):
|
||||||
|
----------------------------------------------------------
|
||||||
|
El término «hola» se puede referir a:
|
||||||
|
Hola, interjección en español usada como saludo con el objetivo de comenzar una conversación;
|
||||||
|
Hola, también llamado Galole, un pueblo de Kenia;
|
||||||
|
¡Hola!, una revista española;
|
||||||
|
Hola Airlines, una aerolínea española con base en Palma de Mallorca;
|
||||||
|
Hola mundo, un programa informático básico para iniciar el estudio de un lenguaje de programación;
|
||||||
|
«Hola», canción de Joey Montana.
|
||||||
|
Enlaces externos
|
||||||
|
Wikcionario tiene definiciones y otra información sobre hola.
|
||||||
|
Proyectos Wikimedia
|
||||||
|
Datos: Q1177609
|
||||||
|
Multimedia: Hola / Q1177609
|
||||||
|
Datos: Q1177609
|
||||||
|
Multimedia: Hola / Q1177609
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
from vista.chat.chat_servidor import ChatServidorPanel
|
||||||
|
from vista.chat.chat_cliente import ChatClientePanel
|
||||||
|
from vista.chat.chat_selector import ChatSelectorPanel
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
from datetime import datetime
|
||||||
|
from vista.config import *
|
||||||
|
|
||||||
|
|
||||||
|
class ChatBase(ttk.Frame):
|
||||||
|
"""Base comun para las vistas de chat servidor y cliente."""
|
||||||
|
|
||||||
|
def __init__(self, parent, root, *args, **kwargs):
|
||||||
|
super().__init__(parent, *args, **kwargs)
|
||||||
|
self.root = root
|
||||||
|
self.chat_history = None
|
||||||
|
self.chat_input_entry = None
|
||||||
|
self.socket = None
|
||||||
|
|
||||||
|
def crear_interfaz_chat(self, parent_frame, titulo="Chat", boton_accion_texto=None, boton_accion_callback=None):
|
||||||
|
"""Crea la interfaz grafica del chat: titulo, historial, entrada y boton."""
|
||||||
|
self._chat_frame = ttk.Frame(parent_frame, padding=15, style='TFrame')
|
||||||
|
self._chat_frame.pack(expand=True, fill="both")
|
||||||
|
|
||||||
|
self._chat_frame.grid_rowconfigure(1, weight=1)
|
||||||
|
self._chat_frame.grid_columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
# Titulo y boton de accion
|
||||||
|
frame_titulo = ttk.Frame(self._chat_frame, style='TFrame')
|
||||||
|
frame_titulo.grid(row=0, column=0, columnspan=2, sticky="ew", pady=(0, 10))
|
||||||
|
frame_titulo.grid_columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
self.info_label = ttk.Label(frame_titulo, text=titulo, font=FUENTE_TITULO)
|
||||||
|
self.info_label.grid(row=0, column=0, sticky="w")
|
||||||
|
|
||||||
|
if boton_accion_texto and boton_accion_callback:
|
||||||
|
ttk.Button(
|
||||||
|
frame_titulo, text=boton_accion_texto,
|
||||||
|
command=boton_accion_callback
|
||||||
|
).grid(row=0, column=1, sticky="e", padx=(10, 0))
|
||||||
|
|
||||||
|
# Historial de mensajes
|
||||||
|
self.chat_history = tk.Text(
|
||||||
|
self._chat_frame, wrap="word",
|
||||||
|
bg=COLOR_BLANCO, fg=COLOR_TEXTO,
|
||||||
|
relief="flat", borderwidth=1,
|
||||||
|
font=('Arial', 10)
|
||||||
|
)
|
||||||
|
self.chat_history.grid(row=1, column=0, columnspan=2, sticky="nsew", pady=(0, 10))
|
||||||
|
self.chat_history.config(state=tk.DISABLED)
|
||||||
|
|
||||||
|
# Entrada y boton
|
||||||
|
frame_input = ttk.Frame(self._chat_frame, style='TFrame')
|
||||||
|
frame_input.grid(row=2, column=0, columnspan=2, sticky="ew")
|
||||||
|
frame_input.grid_columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
self.chat_input_entry = ttk.Entry(frame_input, font=('Arial', 11))
|
||||||
|
self.chat_input_entry.grid(row=0, column=0, sticky="ew", padx=(0, 5))
|
||||||
|
self.chat_input_entry.bind('<Return>', self.enviar_mensaje)
|
||||||
|
|
||||||
|
ttk.Button(
|
||||||
|
frame_input, text="Enviar",
|
||||||
|
command=self.enviar_mensaje, style='Action.TButton'
|
||||||
|
).grid(row=0, column=1, sticky="e")
|
||||||
|
|
||||||
|
def agregar_mensaje(self, remitente, texto):
|
||||||
|
"""Agrega un mensaje al historial."""
|
||||||
|
self.chat_history.config(state=tk.NORMAL)
|
||||||
|
timestamp = datetime.now().strftime('%H:%M:%S')
|
||||||
|
self.chat_history.insert(tk.END, f"[{timestamp}] {remitente.upper()}: {texto.strip()}\n")
|
||||||
|
self.chat_history.config(state=tk.DISABLED)
|
||||||
|
self.chat_history.see(tk.END)
|
||||||
|
|
||||||
|
def agregar_mensaje_sistema(self, texto):
|
||||||
|
"""Agrega un mensaje del sistema."""
|
||||||
|
self.agregar_mensaje("SISTEMA", f"--- {texto} ---")
|
||||||
|
|
||||||
|
def enviar_mensaje(self, event=None):
|
||||||
|
"""Debe ser implementado por las subclases."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def cerrar_conexion(self):
|
||||||
|
"""Cierra el socket si esta activo."""
|
||||||
|
if self.socket:
|
||||||
|
try:
|
||||||
|
self.socket.close()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
self.socket = None
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
import threading
|
||||||
|
from vista.chat.chat_base import ChatBase
|
||||||
|
from logica.red.cliente import conectar_servidor
|
||||||
|
|
||||||
|
PREFIJO_NOMBRE = "__NOMBRE__:"
|
||||||
|
|
||||||
|
|
||||||
|
class ChatClientePanel(ChatBase):
|
||||||
|
"""Vista de chat para el rol de cliente."""
|
||||||
|
|
||||||
|
def __init__(self, parent, root, ip, puerto, clave, *args, **kwargs):
|
||||||
|
super().__init__(parent, root, *args, **kwargs)
|
||||||
|
self.ip = ip
|
||||||
|
self.puerto = puerto
|
||||||
|
self.clave = clave
|
||||||
|
self.nombre = None
|
||||||
|
self.crear_interfaz_chat(
|
||||||
|
self, titulo="Chat - Cliente",
|
||||||
|
boton_accion_texto="Desconectar",
|
||||||
|
boton_accion_callback=self.desconectar_y_volver
|
||||||
|
)
|
||||||
|
self.conectar()
|
||||||
|
|
||||||
|
def conectar(self):
|
||||||
|
print(f"[DEBUG CLI-GUI] Iniciando conexion a {self.ip}:{self.puerto} con clave={self.clave!r}")
|
||||||
|
self.agregar_mensaje_sistema(f"Conectando a {self.ip}:{self.puerto}...")
|
||||||
|
|
||||||
|
def hilo_conexion():
|
||||||
|
cliente, extra = conectar_servidor(self.ip, self.puerto, self.clave)
|
||||||
|
if cliente:
|
||||||
|
self.socket = cliente
|
||||||
|
print(f"[DEBUG CLI-GUI] Conexion exitosa, extra={extra!r}")
|
||||||
|
self.root.after(0, self.agregar_mensaje_sistema, f"Conectado a {self.ip}:{self.puerto}")
|
||||||
|
self.recibir_mensajes(extra)
|
||||||
|
else:
|
||||||
|
print(f"[DEBUG CLI-GUI] Conexion fallida")
|
||||||
|
self.root.after(0, self.agregar_mensaje_sistema, "Error: no se pudo conectar o clave incorrecta")
|
||||||
|
|
||||||
|
hilo = threading.Thread(target=hilo_conexion, daemon=True)
|
||||||
|
hilo.start()
|
||||||
|
|
||||||
|
def _procesar_mensaje(self, mensaje):
|
||||||
|
"""Procesa un mensaje recibido (nombre, chat o remoto)."""
|
||||||
|
print(f"[DEBUG CLI-GUI] Procesando mensaje: {mensaje!r}")
|
||||||
|
if mensaje.startswith(PREFIJO_NOMBRE):
|
||||||
|
self.nombre = mensaje[len(PREFIJO_NOMBRE):]
|
||||||
|
print(f"[DEBUG CLI-GUI] Nombre asignado: {self.nombre!r}")
|
||||||
|
self.root.after(0, self.agregar_mensaje_sistema, f"Tu nombre: {self.nombre}")
|
||||||
|
self.root.after(0, self._actualizar_titulo)
|
||||||
|
elif ": " in mensaje:
|
||||||
|
remitente, texto = mensaje.split(": ", 1)
|
||||||
|
print(f"[DEBUG CLI-GUI] Mensaje de chat: {remitente!r} -> {texto!r}")
|
||||||
|
self.root.after(0, self.agregar_mensaje, remitente, texto)
|
||||||
|
else:
|
||||||
|
print(f"[DEBUG CLI-GUI] Mensaje remoto sin formato: {mensaje!r}")
|
||||||
|
self.root.after(0, self.agregar_mensaje, "REMOTO", mensaje)
|
||||||
|
|
||||||
|
def recibir_mensajes(self, extra=""):
|
||||||
|
# Procesar datos que llegaron junto con el "OK" de autenticacion
|
||||||
|
if extra:
|
||||||
|
print(f"[DEBUG CLI-GUI] Procesando datos extra del handshake: {extra!r}")
|
||||||
|
self._procesar_mensaje(extra)
|
||||||
|
|
||||||
|
print("[DEBUG CLI-GUI] Bucle de recepcion iniciado")
|
||||||
|
try:
|
||||||
|
while self.socket:
|
||||||
|
datos = self.socket.recv(4096)
|
||||||
|
if not datos:
|
||||||
|
print("[DEBUG CLI-GUI] Datos vacios recibidos (servidor cerro)")
|
||||||
|
self.root.after(0, self.agregar_mensaje_sistema, "El servidor cerro la conexion")
|
||||||
|
break
|
||||||
|
mensaje = datos.decode("utf-8")
|
||||||
|
print(f"[DEBUG CLI-GUI] Datos crudos recibidos: {datos!r}")
|
||||||
|
self._procesar_mensaje(mensaje)
|
||||||
|
except (ConnectionResetError, OSError) as e:
|
||||||
|
print(f"[DEBUG CLI-GUI] Error en recepcion: {e}")
|
||||||
|
self.root.after(0, self.agregar_mensaje_sistema, "Conexion perdida con el servidor")
|
||||||
|
|
||||||
|
def _actualizar_titulo(self):
|
||||||
|
if self.nombre:
|
||||||
|
self.info_label.config(text=f"Chat - {self.nombre}")
|
||||||
|
|
||||||
|
def enviar_mensaje(self, event=None):
|
||||||
|
mensaje = self.chat_input_entry.get().strip()
|
||||||
|
if not mensaje or not self.socket:
|
||||||
|
return "break" if event else None
|
||||||
|
|
||||||
|
self.chat_input_entry.delete(0, "end")
|
||||||
|
self.agregar_mensaje("TU", mensaje)
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(f"[DEBUG CLI-GUI] Enviando mensaje: {mensaje!r}")
|
||||||
|
self.socket.sendall(mensaje.encode("utf-8"))
|
||||||
|
except OSError as e:
|
||||||
|
print(f"[DEBUG CLI-GUI] Error al enviar: {e}")
|
||||||
|
self.agregar_mensaje_sistema("Error al enviar mensaje")
|
||||||
|
|
||||||
|
return "break" if event else None
|
||||||
|
|
||||||
|
def desconectar_y_volver(self):
|
||||||
|
"""Desconecta del servidor y vuelve al selector."""
|
||||||
|
self.cerrar_conexion()
|
||||||
|
parent = self.master
|
||||||
|
self.destroy()
|
||||||
|
if hasattr(parent, 'volver_al_selector'):
|
||||||
|
parent.volver_al_selector()
|
||||||
|
|
@ -0,0 +1,193 @@
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
import threading
|
||||||
|
from vista.config import *
|
||||||
|
from logica.red.cliente import descubrir_servidores
|
||||||
|
from logica.red.selector import obtener_ip_local
|
||||||
|
from vista.chat.chat_servidor import ChatServidorPanel
|
||||||
|
from vista.chat.chat_cliente import ChatClientePanel
|
||||||
|
|
||||||
|
|
||||||
|
class ChatSelectorPanel(ttk.Frame):
|
||||||
|
"""Panel inicial que permite elegir entre servidor o cliente."""
|
||||||
|
|
||||||
|
def __init__(self, parent_notebook, root, *args, **kwargs):
|
||||||
|
super().__init__(parent_notebook, *args, **kwargs)
|
||||||
|
self.root = root
|
||||||
|
self.parent_notebook = parent_notebook
|
||||||
|
self.panel_activo = None
|
||||||
|
self._busqueda_cancelada = False
|
||||||
|
self.crear_selector()
|
||||||
|
|
||||||
|
def crear_selector(self):
|
||||||
|
"""Crea la pantalla de seleccion de rol."""
|
||||||
|
self.selector_frame = ttk.Frame(self, padding=30, style='TFrame')
|
||||||
|
self.selector_frame.pack(expand=True, fill="both")
|
||||||
|
|
||||||
|
# Titulo
|
||||||
|
ttk.Label(
|
||||||
|
self.selector_frame, text="Chat en Red",
|
||||||
|
font=FUENTE_TITULO
|
||||||
|
).pack(pady=(20, 5))
|
||||||
|
|
||||||
|
ip = obtener_ip_local()
|
||||||
|
ttk.Label(
|
||||||
|
self.selector_frame, text=f"IP local: {ip}",
|
||||||
|
font=FUENTE_NOTA
|
||||||
|
).pack(pady=(0, 20))
|
||||||
|
|
||||||
|
# Botones de rol
|
||||||
|
frame_botones = ttk.Frame(self.selector_frame, style='TFrame')
|
||||||
|
frame_botones.pack(pady=10)
|
||||||
|
|
||||||
|
ttk.Button(
|
||||||
|
frame_botones, text="Crear Servidor",
|
||||||
|
command=self.iniciar_servidor, style='Action.TButton'
|
||||||
|
).pack(side="left", padx=10)
|
||||||
|
|
||||||
|
ttk.Button(
|
||||||
|
frame_botones, text="Conectar como Cliente",
|
||||||
|
command=self.iniciar_busqueda_cliente, style='Action.TButton'
|
||||||
|
).pack(side="left", padx=10)
|
||||||
|
|
||||||
|
# Frame para resultado de busqueda (cliente)
|
||||||
|
self.frame_cliente = ttk.Frame(self.selector_frame, style='TFrame')
|
||||||
|
self.estado_label = None
|
||||||
|
self.servidores_encontrados = []
|
||||||
|
|
||||||
|
def iniciar_servidor(self):
|
||||||
|
"""Reemplaza el selector por el panel de chat servidor."""
|
||||||
|
self.selector_frame.destroy()
|
||||||
|
self.panel_activo = ChatServidorPanel(self, self.root)
|
||||||
|
self.panel_activo.pack(expand=True, fill="both")
|
||||||
|
|
||||||
|
def iniciar_busqueda_cliente(self):
|
||||||
|
"""Muestra progreso y busca servidores."""
|
||||||
|
self._busqueda_cancelada = False
|
||||||
|
|
||||||
|
# Limpiar frame cliente previo
|
||||||
|
for widget in self.frame_cliente.winfo_children():
|
||||||
|
widget.destroy()
|
||||||
|
self.frame_cliente.pack(pady=20, fill="x")
|
||||||
|
|
||||||
|
self.estado_label = ttk.Label(
|
||||||
|
self.frame_cliente, text="Buscando servidores...",
|
||||||
|
font=FUENTE_NEGOCIOS
|
||||||
|
)
|
||||||
|
self.estado_label.pack()
|
||||||
|
|
||||||
|
self.progress = ttk.Progressbar(self.frame_cliente, mode='indeterminate')
|
||||||
|
self.progress.pack(fill="x", padx=40, pady=5)
|
||||||
|
self.progress.start(15)
|
||||||
|
|
||||||
|
ttk.Button(
|
||||||
|
self.frame_cliente, text="Cancelar busqueda",
|
||||||
|
command=self.cancelar_busqueda
|
||||||
|
).pack(pady=5)
|
||||||
|
|
||||||
|
hilo = threading.Thread(target=self.buscar_servidores, daemon=True)
|
||||||
|
hilo.start()
|
||||||
|
|
||||||
|
def cancelar_busqueda(self):
|
||||||
|
"""Cancela la busqueda de servidores."""
|
||||||
|
self._busqueda_cancelada = True
|
||||||
|
self.progress.stop()
|
||||||
|
for widget in self.frame_cliente.winfo_children():
|
||||||
|
widget.destroy()
|
||||||
|
self.frame_cliente.pack_forget()
|
||||||
|
|
||||||
|
def buscar_servidores(self):
|
||||||
|
print("[DEBUG SEL] Iniciando busqueda de servidores...")
|
||||||
|
servidores = descubrir_servidores(timeout=5)
|
||||||
|
print(f"[DEBUG SEL] Busqueda completada: {servidores}")
|
||||||
|
if not self._busqueda_cancelada:
|
||||||
|
self.root.after(0, self.mostrar_resultado_busqueda, servidores)
|
||||||
|
|
||||||
|
def mostrar_resultado_busqueda(self, servidores):
|
||||||
|
self.progress.stop()
|
||||||
|
for widget in self.frame_cliente.winfo_children():
|
||||||
|
widget.destroy()
|
||||||
|
|
||||||
|
if not servidores:
|
||||||
|
ttk.Label(
|
||||||
|
self.frame_cliente, text="No se encontraron servidores.",
|
||||||
|
font=FUENTE_NEGOCIOS, foreground="red"
|
||||||
|
).pack()
|
||||||
|
|
||||||
|
frame_opciones = ttk.Frame(self.frame_cliente, style='TFrame')
|
||||||
|
frame_opciones.pack(pady=10)
|
||||||
|
ttk.Button(
|
||||||
|
frame_opciones, text="Reintentar",
|
||||||
|
command=self.iniciar_busqueda_cliente, style='Action.TButton'
|
||||||
|
).pack(side="left", padx=5)
|
||||||
|
ttk.Button(
|
||||||
|
frame_opciones, text="Volver",
|
||||||
|
command=self.volver_selector
|
||||||
|
).pack(side="left", padx=5)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.servidores_encontrados = servidores
|
||||||
|
|
||||||
|
ttk.Label(
|
||||||
|
self.frame_cliente,
|
||||||
|
text=f"{len(servidores)} servidor(es) encontrado(s):",
|
||||||
|
font=FUENTE_NEGOCIOS
|
||||||
|
).pack()
|
||||||
|
|
||||||
|
for i, (ip, puerto) in enumerate(servidores):
|
||||||
|
frame_srv = ttk.Frame(self.frame_cliente, style='TFrame')
|
||||||
|
frame_srv.pack(pady=2)
|
||||||
|
ttk.Label(frame_srv, text=f"{ip}", font=FUENTE_NORMAL).pack(side="left", padx=5)
|
||||||
|
ttk.Button(
|
||||||
|
frame_srv, text="Conectar",
|
||||||
|
command=lambda idx=i: self.pedir_clave(idx), style='Action.TButton'
|
||||||
|
).pack(side="left", padx=5)
|
||||||
|
|
||||||
|
def pedir_clave(self, indice):
|
||||||
|
"""Muestra un dialogo para introducir la contrasena."""
|
||||||
|
ip, puerto = self.servidores_encontrados[indice]
|
||||||
|
|
||||||
|
dialogo = tk.Toplevel(self.root)
|
||||||
|
dialogo.title("Clave de acceso")
|
||||||
|
dialogo.geometry("350x120")
|
||||||
|
dialogo.resizable(False, False)
|
||||||
|
dialogo.transient(self.root)
|
||||||
|
dialogo.grab_set()
|
||||||
|
|
||||||
|
frame = ttk.Frame(dialogo, padding=15)
|
||||||
|
frame.pack(expand=True, fill="both")
|
||||||
|
|
||||||
|
ttk.Label(frame, text=f"Conectar a {ip}", font=FUENTE_NEGOCIOS).pack()
|
||||||
|
ttk.Label(frame, text="Clave (puerto#contraseña):", font=FUENTE_NORMAL).pack(pady=(5, 0))
|
||||||
|
|
||||||
|
entrada = ttk.Entry(frame, font=FUENTE_NORMAL)
|
||||||
|
entrada.pack(fill="x", pady=5)
|
||||||
|
entrada.focus_set()
|
||||||
|
|
||||||
|
def conectar(event=None):
|
||||||
|
contrasena = entrada.get().strip()
|
||||||
|
if contrasena:
|
||||||
|
dialogo.destroy()
|
||||||
|
self.conectar_a_servidor(ip, puerto, contrasena)
|
||||||
|
|
||||||
|
entrada.bind('<Return>', conectar)
|
||||||
|
ttk.Button(frame, text="Conectar", command=conectar, style='Action.TButton').pack()
|
||||||
|
|
||||||
|
def conectar_a_servidor(self, ip, puerto, contrasena):
|
||||||
|
"""Reemplaza el selector por el panel de chat cliente."""
|
||||||
|
print(f"[DEBUG SEL] Conectando a servidor: ip={ip} puerto={puerto} clave={contrasena!r}")
|
||||||
|
self.selector_frame.destroy()
|
||||||
|
self.panel_activo = ChatClientePanel(self, self.root, ip, puerto, contrasena)
|
||||||
|
self.panel_activo.pack(expand=True, fill="both")
|
||||||
|
|
||||||
|
def volver_al_selector(self):
|
||||||
|
"""Destruye el panel activo y recrea el selector."""
|
||||||
|
if self.panel_activo:
|
||||||
|
self.panel_activo = None
|
||||||
|
self.crear_selector()
|
||||||
|
|
||||||
|
def volver_selector(self):
|
||||||
|
"""Limpia el frame de cliente para volver al estado inicial."""
|
||||||
|
for widget in self.frame_cliente.winfo_children():
|
||||||
|
widget.destroy()
|
||||||
|
self.frame_cliente.pack_forget()
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
import threading
|
||||||
|
from vista.chat.chat_base import ChatBase
|
||||||
|
from logica.red.servidor import iniciar_servidor, autenticar_cliente
|
||||||
|
from logica.red.selector import obtener_ip_local
|
||||||
|
|
||||||
|
PREFIJO_NOMBRE = "__NOMBRE__:"
|
||||||
|
|
||||||
|
|
||||||
|
class ChatServidorPanel(ChatBase):
|
||||||
|
"""Vista de chat para el rol de servidor."""
|
||||||
|
|
||||||
|
def __init__(self, parent, root, *args, **kwargs):
|
||||||
|
super().__init__(parent, root, *args, **kwargs)
|
||||||
|
self.servidor = None
|
||||||
|
self.clientes = []
|
||||||
|
self.clave_acceso = None
|
||||||
|
self.broadcast_stop = None
|
||||||
|
self.contador_clientes = 0
|
||||||
|
self.crear_interfaz_chat(
|
||||||
|
self, titulo="Chat - Servidor",
|
||||||
|
boton_accion_texto="Cerrar Servidor",
|
||||||
|
boton_accion_callback=self.cerrar_y_volver
|
||||||
|
)
|
||||||
|
self.iniciar()
|
||||||
|
|
||||||
|
def iniciar(self):
|
||||||
|
ip = obtener_ip_local()
|
||||||
|
self.servidor, puerto, contrasena_alfa, self.broadcast_stop = iniciar_servidor()
|
||||||
|
self.clave_acceso = f"{puerto}#{contrasena_alfa}"
|
||||||
|
print(f"[DEBUG SRV-GUI] Servidor iniciado: ip={ip} puerto={puerto} clave={self.clave_acceso}")
|
||||||
|
self.agregar_mensaje_sistema(f"Servidor iniciado en {ip}")
|
||||||
|
self.agregar_mensaje_sistema(f"Clave de acceso: {self.clave_acceso}")
|
||||||
|
self.agregar_mensaje_sistema("Esperando clientes...")
|
||||||
|
|
||||||
|
hilo = threading.Thread(target=self.aceptar_clientes, daemon=True)
|
||||||
|
hilo.start()
|
||||||
|
|
||||||
|
def aceptar_clientes(self):
|
||||||
|
print("[DEBUG SRV-GUI] Esperando conexiones de clientes...")
|
||||||
|
while self.servidor:
|
||||||
|
try:
|
||||||
|
conn, addr = self.servidor.accept()
|
||||||
|
print(f"[DEBUG SRV-GUI] Conexion entrante de {addr[0]}:{addr[1]}")
|
||||||
|
datos = conn.recv(1024).decode("utf-8")
|
||||||
|
print(f"[DEBUG SRV-GUI] Datos de autenticacion recibidos: {datos!r}")
|
||||||
|
if autenticar_cliente(datos, self.clave_acceso):
|
||||||
|
conn.sendall("OK".encode("utf-8"))
|
||||||
|
self.contador_clientes += 1
|
||||||
|
nombre = f"Cliente {self.contador_clientes}"
|
||||||
|
conn.sendall(f"{PREFIJO_NOMBRE}{nombre}".encode("utf-8"))
|
||||||
|
print(f"[DEBUG SRV-GUI] Cliente autenticado como {nombre!r}, enviado OK + nombre")
|
||||||
|
self.clientes.append(conn)
|
||||||
|
self.root.after(0, self.agregar_mensaje_sistema, f"{nombre} conectado")
|
||||||
|
hilo = threading.Thread(target=self.recibir_de_cliente, args=(conn, nombre), daemon=True)
|
||||||
|
hilo.start()
|
||||||
|
else:
|
||||||
|
conn.sendall("DENIED".encode("utf-8"))
|
||||||
|
conn.close()
|
||||||
|
etiqueta = f"{addr[0]}:{addr[1]}"
|
||||||
|
print(f"[DEBUG SRV-GUI] Cliente {etiqueta} rechazado (clave incorrecta)")
|
||||||
|
self.root.after(0, self.agregar_mensaje_sistema, f"Cliente {etiqueta} rechazado")
|
||||||
|
except OSError as e:
|
||||||
|
print(f"[DEBUG SRV-GUI] Error en accept: {e}")
|
||||||
|
break
|
||||||
|
|
||||||
|
def recibir_de_cliente(self, conn, nombre):
|
||||||
|
print(f"[DEBUG SRV-GUI] Hilo de recepcion iniciado para {nombre}")
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
datos = conn.recv(4096)
|
||||||
|
if not datos:
|
||||||
|
print(f"[DEBUG SRV-GUI] {nombre} envio datos vacios (desconexion)")
|
||||||
|
break
|
||||||
|
mensaje = datos.decode("utf-8")
|
||||||
|
print(f"[DEBUG SRV-GUI] Mensaje de {nombre}: {mensaje!r}")
|
||||||
|
self.root.after(0, self.agregar_mensaje, nombre, mensaje)
|
||||||
|
self.difundir(mensaje, nombre, conn)
|
||||||
|
except (ConnectionResetError, OSError) as e:
|
||||||
|
print(f"[DEBUG SRV-GUI] Error recibiendo de {nombre}: {e}")
|
||||||
|
finally:
|
||||||
|
if conn in self.clientes:
|
||||||
|
self.clientes.remove(conn)
|
||||||
|
conn.close()
|
||||||
|
self.root.after(0, self.agregar_mensaje_sistema, f"{nombre} desconectado")
|
||||||
|
|
||||||
|
def difundir(self, mensaje, remitente, origen):
|
||||||
|
"""Envia un mensaje a todos los clientes excepto al que lo envio."""
|
||||||
|
datos = f"{remitente}: {mensaje}".encode("utf-8")
|
||||||
|
for cliente in self.clientes[:]:
|
||||||
|
if cliente is not origen:
|
||||||
|
try:
|
||||||
|
cliente.sendall(datos)
|
||||||
|
except OSError:
|
||||||
|
self.clientes.remove(cliente)
|
||||||
|
|
||||||
|
def enviar_mensaje(self, event=None):
|
||||||
|
mensaje = self.chat_input_entry.get().strip()
|
||||||
|
if not mensaje:
|
||||||
|
return "break" if event else None
|
||||||
|
|
||||||
|
self.chat_input_entry.delete(0, "end")
|
||||||
|
self.agregar_mensaje("SERVIDOR", mensaje)
|
||||||
|
|
||||||
|
datos = f"SERVIDOR: {mensaje}".encode("utf-8")
|
||||||
|
for cliente in self.clientes[:]:
|
||||||
|
try:
|
||||||
|
cliente.sendall(datos)
|
||||||
|
except OSError:
|
||||||
|
self.clientes.remove(cliente)
|
||||||
|
|
||||||
|
return "break" if event else None
|
||||||
|
|
||||||
|
def cerrar_y_volver(self):
|
||||||
|
"""Cierra el servidor y vuelve al selector."""
|
||||||
|
self.cerrar_conexion()
|
||||||
|
parent = self.master
|
||||||
|
self.destroy()
|
||||||
|
if hasattr(parent, 'volver_al_selector'):
|
||||||
|
parent.volver_al_selector()
|
||||||
|
|
||||||
|
def cerrar_conexion(self):
|
||||||
|
if self.broadcast_stop:
|
||||||
|
self.broadcast_stop.set()
|
||||||
|
if self.servidor:
|
||||||
|
self.servidor.close()
|
||||||
|
self.servidor = None
|
||||||
|
for cliente in self.clientes:
|
||||||
|
try:
|
||||||
|
cliente.close()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
self.clientes.clear()
|
||||||
Loading…
Reference in New Issue