337 lines
12 KiB
Python
337 lines
12 KiB
Python
import json
|
|
import time
|
|
import threading
|
|
import asyncio
|
|
import websockets
|
|
from oedb.utils.logging import logger
|
|
|
|
class WebSocketManager:
|
|
"""
|
|
Gestionnaire de WebSockets pour les fonctionnalités sociales d'OEDB.
|
|
Gère les connexions WebSocket des utilisateurs et distribue les messages.
|
|
Peut être utilisé soit de façon autonome, soit intégré avec uWSGI.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.clients = {}
|
|
self.positions = {}
|
|
self.lock = threading.Lock()
|
|
self.server = None
|
|
|
|
async def handle_connection(self, websocket, path):
|
|
"""
|
|
Gère une connexion WebSocket entrante.
|
|
|
|
Args:
|
|
websocket: La connexion WebSocket.
|
|
path: Le chemin de la demande.
|
|
"""
|
|
client_id = id(websocket)
|
|
logger.debug(f"Tentative de connexion WebSocket reçue: {client_id} - {path}")
|
|
|
|
try:
|
|
logger.info(f"Nouvelle connexion WebSocket: {client_id} - {path}")
|
|
|
|
# Ajouter le client à la liste
|
|
with self.lock:
|
|
self.clients[client_id] = {
|
|
'websocket': websocket,
|
|
'username': None,
|
|
'position': None,
|
|
'last_seen': time.time(),
|
|
'show_only_to_friends': False,
|
|
}
|
|
|
|
# Envoyer la liste des utilisateurs connectés
|
|
await self.send_users_list(websocket)
|
|
|
|
async for message in websocket:
|
|
await self.handle_message(client_id, message)
|
|
|
|
except websockets.exceptions.ConnectionClosed:
|
|
logger.info(f"Connexion WebSocket fermée: {client_id}")
|
|
except Exception as e:
|
|
logger.error(f"Erreur WebSocket: {e}")
|
|
finally:
|
|
# Supprimer le client de la liste
|
|
with self.lock:
|
|
if client_id in self.clients:
|
|
username = self.clients[client_id].get('username')
|
|
if username and username in self.positions:
|
|
del self.positions[username]
|
|
del self.clients[client_id]
|
|
|
|
# Informer les autres clients de la déconnexion
|
|
await self.broadcast_users_list()
|
|
|
|
async def handle_message(self, client_id, message):
|
|
"""
|
|
Traite un message WebSocket reçu.
|
|
|
|
Args:
|
|
client_id: L'ID du client qui a envoyé le message.
|
|
message: Le message JSON reçu.
|
|
"""
|
|
try:
|
|
data = json.loads(message)
|
|
message_type = data.get('type')
|
|
|
|
if message_type == 'position':
|
|
await self.handle_position_update(client_id, data)
|
|
elif message_type == 'pouet':
|
|
await self.handle_pouet(data)
|
|
elif message_type == 'friendRequest':
|
|
await self.handle_friend_request(data)
|
|
|
|
except json.JSONDecodeError:
|
|
logger.error(f"Message JSON invalide: {message}")
|
|
except Exception as e:
|
|
logger.error(f"Erreur de traitement du message: {e}")
|
|
|
|
async def handle_position_update(self, client_id, data):
|
|
"""
|
|
Traite une mise à jour de position d'un utilisateur.
|
|
|
|
Args:
|
|
client_id: L'ID du client qui a envoyé la mise à jour.
|
|
data: Les données de position.
|
|
"""
|
|
username = data.get('username')
|
|
position = data.get('position')
|
|
show_only_to_friends = data.get('showOnlyToFriends', False)
|
|
|
|
if not username or not position:
|
|
return
|
|
|
|
# Mettre à jour les informations du client
|
|
with self.lock:
|
|
if client_id in self.clients:
|
|
self.clients[client_id]['username'] = username
|
|
self.clients[client_id]['position'] = position
|
|
self.clients[client_id]['last_seen'] = time.time()
|
|
self.clients[client_id]['show_only_to_friends'] = show_only_to_friends
|
|
|
|
# Mettre à jour la position dans le dictionnaire des positions
|
|
self.positions[username] = {
|
|
'position': position,
|
|
'timestamp': data.get('timestamp'),
|
|
'show_only_to_friends': show_only_to_friends
|
|
}
|
|
|
|
# Diffuser la position à tous les autres clients
|
|
await self.broadcast_position(username, position, data.get('timestamp'), show_only_to_friends)
|
|
|
|
# Envoyer la liste mise à jour des utilisateurs
|
|
await self.broadcast_users_list()
|
|
|
|
async def handle_pouet(self, data):
|
|
"""
|
|
Traite un 'pouet pouet' envoyé d'un utilisateur à un autre.
|
|
|
|
Args:
|
|
data: Les données du pouet pouet.
|
|
"""
|
|
from_user = data.get('from')
|
|
to_user = data.get('to')
|
|
|
|
if not from_user or not to_user:
|
|
return
|
|
|
|
# Trouver le client destinataire
|
|
recipient_client_id = None
|
|
with self.lock:
|
|
for client_id, client_info in self.clients.items():
|
|
if client_info.get('username') == to_user:
|
|
recipient_client_id = client_id
|
|
break
|
|
|
|
if recipient_client_id and recipient_client_id in self.clients:
|
|
# Envoyer le pouet au destinataire
|
|
try:
|
|
await self.clients[recipient_client_id]['websocket'].send(json.dumps({
|
|
'type': 'pouet',
|
|
'from': from_user,
|
|
'timestamp': data.get('timestamp')
|
|
}))
|
|
logger.info(f"Pouet pouet envoyé de {from_user} à {to_user}")
|
|
except Exception as e:
|
|
logger.error(f"Erreur d'envoi de pouet pouet: {e}")
|
|
|
|
async def handle_friend_request(self, data):
|
|
"""
|
|
Traite une demande d'ami d'un utilisateur à un autre.
|
|
|
|
Args:
|
|
data: Les données de la demande d'ami.
|
|
"""
|
|
from_user = data.get('from')
|
|
to_user = data.get('to')
|
|
|
|
if not from_user or not to_user:
|
|
return
|
|
|
|
# Trouver le client destinataire
|
|
recipient_client_id = None
|
|
with self.lock:
|
|
for client_id, client_info in self.clients.items():
|
|
if client_info.get('username') == to_user:
|
|
recipient_client_id = client_id
|
|
break
|
|
|
|
if recipient_client_id and recipient_client_id in self.clients:
|
|
# Envoyer la demande d'ami au destinataire
|
|
try:
|
|
await self.clients[recipient_client_id]['websocket'].send(json.dumps({
|
|
'type': 'friendRequest',
|
|
'from': from_user,
|
|
'timestamp': data.get('timestamp')
|
|
}))
|
|
logger.info(f"Demande d'ami envoyée de {from_user} à {to_user}")
|
|
except Exception as e:
|
|
logger.error(f"Erreur d'envoi de demande d'ami: {e}")
|
|
|
|
async def broadcast_position(self, username, position, timestamp, show_only_to_friends):
|
|
"""
|
|
Diffuse la position d'un utilisateur à tous les autres utilisateurs.
|
|
|
|
Args:
|
|
username: Le nom d'utilisateur.
|
|
position: La position de l'utilisateur.
|
|
timestamp: L'horodatage de la mise à jour.
|
|
show_only_to_friends: Indique si la position est visible uniquement par les amis.
|
|
"""
|
|
message = json.dumps({
|
|
'type': 'position',
|
|
'username': username,
|
|
'position': position,
|
|
'timestamp': timestamp,
|
|
'showOnlyToFriends': show_only_to_friends
|
|
})
|
|
|
|
with self.lock:
|
|
for client_id, client_info in self.clients.items():
|
|
# Ne pas envoyer à l'utilisateur lui-même
|
|
if client_info.get('username') == username:
|
|
continue
|
|
|
|
try:
|
|
await client_info['websocket'].send(message)
|
|
except Exception as e:
|
|
logger.error(f"Erreur d'envoi de broadcast de position: {e}")
|
|
|
|
async def send_users_list(self, websocket):
|
|
"""
|
|
Envoie la liste des utilisateurs connectés à un client spécifique.
|
|
|
|
Args:
|
|
websocket: La connexion WebSocket du client.
|
|
"""
|
|
users = []
|
|
with self.lock:
|
|
for client_info in self.clients.values():
|
|
if client_info.get('username'):
|
|
users.append({
|
|
'username': client_info['username'],
|
|
'timestamp': time.time()
|
|
})
|
|
|
|
try:
|
|
await websocket.send(json.dumps({
|
|
'type': 'users',
|
|
'users': users
|
|
}))
|
|
except Exception as e:
|
|
logger.error(f"Erreur d'envoi de liste d'utilisateurs: {e}")
|
|
|
|
async def broadcast_users_list(self):
|
|
"""
|
|
Diffuse la liste des utilisateurs connectés à tous les clients.
|
|
"""
|
|
users = []
|
|
with self.lock:
|
|
for client_info in self.clients.values():
|
|
if client_info.get('username'):
|
|
users.append({
|
|
'username': client_info['username'],
|
|
'timestamp': time.time()
|
|
})
|
|
|
|
message = json.dumps({
|
|
'type': 'users',
|
|
'users': users
|
|
})
|
|
|
|
with self.lock:
|
|
for client_info in self.clients.values():
|
|
try:
|
|
await client_info['websocket'].send(message)
|
|
except Exception as e:
|
|
logger.error(f"Erreur de broadcast de liste d'utilisateurs: {e}")
|
|
|
|
async def cleanup_inactive_clients(self):
|
|
"""
|
|
Nettoie les clients inactifs (pas de mise à jour depuis plus de 5 minutes).
|
|
"""
|
|
inactive_clients = []
|
|
|
|
with self.lock:
|
|
current_time = time.time()
|
|
for client_id, client_info in self.clients.items():
|
|
if current_time - client_info['last_seen'] > 300: # 5 minutes
|
|
inactive_clients.append(client_id)
|
|
|
|
for client_id in inactive_clients:
|
|
username = self.clients[client_id].get('username')
|
|
if username and username in self.positions:
|
|
del self.positions[username]
|
|
del self.clients[client_id]
|
|
|
|
if inactive_clients:
|
|
logger.info(f"Nettoyage de {len(inactive_clients)} clients inactifs")
|
|
await self.broadcast_users_list()
|
|
|
|
async def cleanup_task(self):
|
|
"""
|
|
Tâche périodique pour nettoyer les clients inactifs.
|
|
"""
|
|
while True:
|
|
await asyncio.sleep(60) # Exécuter toutes les minutes
|
|
await self.cleanup_inactive_clients()
|
|
|
|
async def start_server(self, host='0.0.0.0', port=8765):
|
|
"""
|
|
Démarre le serveur WebSocket.
|
|
|
|
Args:
|
|
host: L'hôte à écouter.
|
|
port: Le port à écouter.
|
|
"""
|
|
self.server = await websockets.serve(self.handle_connection, host, port)
|
|
logger.info(f"Serveur WebSocket démarré sur {host}:{port}")
|
|
|
|
# Démarrer la tâche de nettoyage
|
|
asyncio.create_task(self.cleanup_task())
|
|
|
|
# Garder le serveur en cours d'exécution
|
|
await asyncio.Future()
|
|
|
|
def start(self, host='0.0.0.0', port=8765):
|
|
"""
|
|
Démarre le serveur WebSocket dans un thread séparé.
|
|
|
|
Args:
|
|
host: L'hôte à écouter.
|
|
port: Le port à écouter.
|
|
"""
|
|
def run_server():
|
|
asyncio.run(self.start_server(host, port))
|
|
|
|
server_thread = threading.Thread(target=run_server, daemon=True)
|
|
server_thread.start()
|
|
logger.info(f"Serveur WebSocket démarré dans un thread séparé sur {host}:{port}")
|
|
|
|
# Créer une instance du gestionnaire WebSocket
|
|
ws_manager = WebSocketManager()
|
|
|
|
# Démarrer automatiquement le serveur WebSocket
|
|
ws_manager.start(host='127.0.0.1', port=8765)
|