Documentation du code

This commit is contained in:
SeekDaSky 2017-12-08 06:06:46 +01:00
parent b944b060c1
commit 317c4164b3
12 changed files with 259 additions and 88 deletions

View File

@ -7,11 +7,18 @@ import seekdasky.kWebSocket.Listeners.AsynchronousListener
import seekdasky.kWebSocket.Message.*; import seekdasky.kWebSocket.Message.*;
import seekdasky.kWebSocket.Server import seekdasky.kWebSocket.Server
/**
* Classe s'occupant de la partie chat simple du site, ancienne version que l'on a pas eu le temps de refactorer, pour un meilleur exemple regardez les classe contenues dans le package Channels
*/
class Channel : AsynchronousListener { class Channel : AsynchronousListener {
//nom du channel, utilisé pour définir l'URL d'écoute
val channelName : String; val channelName : String;
//list des clients
var clients : MutableList<Client>; var clients : MutableList<Client>;
//liste des messages du chat
val messages : Stack<Pair<Client, String>>; val messages : Stack<Pair<Client, String>>;
//instance du serveur
val serv : Server; val serv : Server;
constructor(serv : Server , chanName : String){ constructor(serv : Server , chanName : String){
@ -21,43 +28,56 @@ class Channel : AsynchronousListener {
this.serv = serv; this.serv = serv;
} }
/**
* comme toute les autres méthodes filter
*/
override fun filter(c: Client): Boolean { override fun filter(c: Client): Boolean {
return this.channelName == c.URL; return this.channelName == c.URL;
} }
/**
* méthode executée quand le client ferme son socket
*/
override fun processClosed(e: Event) { override fun processClosed(e: Event) {
this.clients.remove(e.client); this.clients.remove(e.client);
ConnectChannel.notifyDisconnect(e.client); ConnectChannel.notifyDisconnect(e.client);
System.out.println("A client disconnected ("+this.clients.count()+" clients connected)"); System.out.println("A client disconnected ("+this.clients.count()+" clients connected)");
} }
/**
* les clients sont ajouté a la liste lors de leur première connection mais ne sont pas loggués
*/
override fun processConnection(c: Client) { override fun processConnection(c: Client) {
this.clients.add(c); this.clients.add(c);
c.data["IsLogged"] = false; c.data["IsLogged"] = false;
} }
/**
* méthode gérant la connexion et l'envoi de message
*/
override fun processEvent(e: Event) { override fun processEvent(e: Event) {
try{ try{
val json = JSONObject(e.message.getString()) val json = JSONObject(e.message.getString())
if(json.has("close")){
return
}
if(e.client.data["IsLogged"] != true){ if(e.client.data["IsLogged"] != true){
//json format : {username:xxx} //json format : {username:xxx}
if(json.has("name") && json.getString("name") != null){ if(json.has("name") && json.getString("name") != null){
e.client.data["Username"] = json.getString("name"); e.client.data["Username"] = json.getString("name");
//on demande si le client est bien connecté
if(ConnectChannel.isConnected(e.client)){ if(ConnectChannel.isConnected(e.client)){
System.out.println("A client connected ("+this.clients.count()+" clients connected)"); System.out.println("A client connected ("+this.clients.count()+" clients connected)");
//on loggue le client et on lui notifie le succès
e.client.data["IsLogged"] = true; e.client.data["IsLogged"] = true;
val jsonLogin = JSONObject(); val jsonLogin = JSONObject();
jsonLogin.put("error",false); jsonLogin.put("error",false);
e.client.send(buildTextMessage(jsonLogin.toString())); e.client.send(buildTextMessage(jsonLogin.toString()));
//on lui envoie la liste des messages
val array = JSONArray(); val array = JSONArray();
//gare a la concurrence
synchronized(this.messages,{ synchronized(this.messages,{
this.messages.toList().forEach { this.messages.toList().forEach {
array.put(JSONArray(listOf(it.first.data["Username"],it.second))) array.put(JSONArray(listOf(it.first.data["Username"],it.second)))
@ -87,6 +107,7 @@ class Channel : AsynchronousListener {
return; return;
}else if(json.has("message")){ }else if(json.has("message")){
//gare a la concurrence
synchronized(this.messages,{ synchronized(this.messages,{
this.messages.push(Pair(e.client,json.getString("message"))) this.messages.push(Pair(e.client,json.getString("message")))
}); });

View File

@ -1,43 +0,0 @@
import seekdasky.kWebSocket.Client
import seekdasky.kWebSocket.Event
import seekdasky.kWebSocket.Listeners.AsynchronousListener
import seekdasky.kWebSocket.Server
class ChannelDispatcher : AsynchronousListener{
val serv : Server;
private var channels : MutableList<Channel>;
constructor(serv : Server){
this.serv = serv;
this.channels = mutableListOf();
}
override fun filter(c: Client): Boolean {
for(chan in this.channels){
if(chan.channelName == c.URL){
return false;
}
}
return true;
}
override fun processClosed(e: Event) {
//no client should arrive here
}
override fun processConnection(c: Client) {
System.out.println("new channel created: "+c.URL);
//if a client is handled here it means we have no channel at this address, let's create it
var newChan = Channel(this.serv,c.URL);
this.serv.addListener(newChan);
//we transfer the client to the new channel
newChan.processConnection(c);
this.channels.add(newChan);
}
override fun processEvent(e: Event) {
//no client should arrive here
}
}

View File

@ -10,20 +10,43 @@ import seekdasky.kWebSocket.Listeners.AsynchronousListener
import seekdasky.kWebSocket.Message.buildTextMessage import seekdasky.kWebSocket.Message.buildTextMessage
import seekdasky.kWebSocket.Server import seekdasky.kWebSocket.Server
/**
* La classe abstraite AbstractChannel fournis toutes les méthodes nécéssaire a la gestion du channel (authentification, méthode de gestion des messages
* utilisés par les classes Interop, dispatch des messages a tout les clients, la seule partie abstraite est le nom du channel (propriété ChanName)
* qui sert a définir l'URL d'écoute.
*/
abstract class AbstractChannel : AsynchronousListener { abstract class AbstractChannel : AsynchronousListener {
//liste de tout les clients du channel
var clients : MutableList<Client>; var clients : MutableList<Client>;
//map ayant en clé le nom du channel et en valeur la stack de message, la stack est composé de paire nom d'utilisateur/message
val messages : MutableMap<String, Stack<Pair<String, Message>>>; val messages : MutableMap<String, Stack<Pair<String, Message>>>;
//instance du serveur pour dispatcher les évenements
val serv : Server; val serv : Server;
//propriété abstraite servant a définir l'addresse d'écoute
abstract val chanName : String; abstract val chanName : String;
/**
* Constructeur de la classe
*
* @param serv Server instance du serveur
*/
constructor(serv : Server){ constructor(serv : Server){
this.clients = mutableListOf(); this.clients = mutableListOf();
this.messages = mutableMapOf(); this.messages = mutableMapOf();
this.serv = serv; this.serv = serv;
} }
override fun filter(c: Client): Boolean { /**
* Methode permettant de filtrer les évenements passés a la méthode ProcessEvent()
*
* @param c Client client a filtrer
*/
override fun filter(c: Client): Boolean{
//on compare l'addresse liée au client a l'adresse d'écoute du channel
val regex = "\\/$chanName\\/(.*)".toRegex() val regex = "\\/$chanName\\/(.*)".toRegex()
val result = regex.findAll(c.URL); val result = regex.findAll(c.URL);
if(result.count() == 1){ if(result.count() == 1){
@ -32,40 +55,62 @@ abstract class AbstractChannel : AsynchronousListener {
return false; return false;
} }
/**
* Lorsqu'un client quitte le channel, on notifie la classe d'authentification et on retire le client de notre liste
*
* @param e Event évenement lancé par le serveur
*/
override fun processClosed(e: Event) { override fun processClosed(e: Event) {
this.clients.remove(e.client); this.clients.remove(e.client);
ConnectChannel.notifyDisconnect(e.client); ConnectChannel.notifyDisconnect(e.client);
System.out.println("A client disconnected ("+this.clients.count()+" clients connected)"); System.out.println("A client disconnected ("+this.clients.count()+" clients connected)");
} }
/**
* Méthode executée lors de la première connexion du client
*/
override fun processConnection(c: Client) { override fun processConnection(c: Client) {
//on rajoute le client a notre liste
this.clients.add(c); this.clients.add(c);
val regex = "\\/$chanName\\/(.*)".toRegex(); val regex = "\\/$chanName\\/(.*)".toRegex();
val result = regex.findAll(c.URL).elementAt(0).groups[1]?.value ?: ""; val result = regex.findAll(c.URL).elementAt(0).groups[1]?.value ?: "";
//create stack if not present //si le sous-channel auquel le client esaie d'accéder n'existe pas, on le créé
if(this.messages[result] == null){ if(this.messages[result] == null){
this.messages[result] = Stack( 50); this.messages[result] = Stack( 50);
} }
//bind client to channel //on rajoute au client une donnée permettant de savoir a quel sous-channel il est lié
if(c.data["emergencyChannel"] == null){ if(c.data["channel"] == null){
c.data["emergencyChannel"] = result; c.data["channel"] = result;
} }
//le client est conecté mais pas logué, il devra envoyer un message spécial pour l'être
c.data["IsLogged"] = false; c.data["IsLogged"] = false;
} }
fun manuallyDispatchMessage(channel : String, msg : Message, operation : String){ /**
* Méthode appelée par les classes Interop afin de dispatcher les mesages envoyé par l'API REST du site
*
* @param channel String nom du sous-channel auquel il faut dispatch, un nom vide signifie un broadcast a tout les sous channels
* @param msg Messag message a dispatcher
*/
fun manuallyDispatchMessage(channel : String, msg : Message){
//on récupère la stach du sous-channel
val channelStack = this.messages[channel]; val channelStack = this.messages[channel];
//si il existe, on dispatch
if(channelStack != null){ if(channelStack != null){
//gare aux modifications concurrentes
synchronized(channelStack, { synchronized(channelStack, {
channelStack.push(Pair(msg.user,msg)); channelStack.push(Pair(msg.user,msg));
}) })
this.dispatchMessage(channel,msg,operation); //dispatch
this.dispatchMessage(channel,msg,"add");
//sinon si la chaine est vide on broadcast
}else if (channel == ""){ }else if (channel == ""){
//broadcast //broadcast
for(c in this.messages.values){ for(c in this.messages.values){
@ -74,42 +119,43 @@ abstract class AbstractChannel : AsynchronousListener {
}) })
} }
this.dispatchMessage(channel,msg,operation); //dispatch
this.dispatchMessage(channel,msg,"add");
} }
} }
/**
* Méthode principale du listener, elle prend en charge tout les messages de tout les clients
*/
override fun processEvent(e: Event) { override fun processEvent(e: Event) {
try{ try{
val json = JSONObject(e.message.getString()) val json = JSONObject(e.message.getString())
//dead code, should remove it later
if(json.has("close")){
return
}
//si l'utilisateur n'est pas log, on le check //si l'utilisateur n'est pas log, on le check
if(e.client.data["IsLogged"] != true){ if(e.client.data["IsLogged"] != true){
//json format : {username:xxx} //json format : {username:xxx}
if(json.has("name") && json.getString("name") != null){ if(json.has("name") && json.getString("name") != null){
//on set le nom d'utilisateur
e.client.data["Username"] = json.getString("name"); e.client.data["Username"] = json.getString("name");
//if the client correctly identified with the Interop server //On vérifie qu'il est bien connecté a l'aide de la classe ConnectChannel
if(ConnectChannel.isConnected(e.client)){ if(ConnectChannel.isConnected(e.client)){
//log //log
System.out.println("Emergency connection ("+this.clients.count()+" clients connected)"); System.out.println("Emergency connection ("+this.clients.count()+" clients connected)");
//return OK to the client //on retourne une réponse de succès au client
e.client.data["IsLogged"] = true; e.client.data["IsLogged"] = true;
val jsonLogin = JSONObject(); val jsonLogin = JSONObject();
jsonLogin.put("error",false); jsonLogin.put("error",false);
e.client.send(buildTextMessage(jsonLogin.toString())); e.client.send(buildTextMessage(jsonLogin.toString()));
//give the client the list of the most recent messages //on envoie au client la liste des messages récent
val array = JSONObject(); val array = JSONObject();
val chanMessages = this.messages[e.client.data["emergencyChannel"]]; val chanMessages = this.messages[e.client.data["channel"]];
if(chanMessages != null){ if(chanMessages != null){
//gare a la concurrence
synchronized(chanMessages,{ synchronized(chanMessages,{
chanMessages.toList().forEach { chanMessages.toList().forEach {
array.put(it.second.id,it.second.toJSON()) array.put(it.second.id,it.second.toJSON())
@ -117,11 +163,11 @@ abstract class AbstractChannel : AsynchronousListener {
}) })
} }
//build JSON //on construit le JSON
val jsonMessage = JSONObject(); val jsonMessage = JSONObject();
jsonMessage.put("error",false); jsonMessage.put("error",false);
jsonMessage.put("add",array); jsonMessage.put("add",array);
//on envoie la sauce
e.client.send(buildTextMessage(jsonMessage.toString())); e.client.send(buildTextMessage(jsonMessage.toString()));
}else{ }else{
val jsonError = JSONObject(); val jsonError = JSONObject();
@ -131,27 +177,32 @@ abstract class AbstractChannel : AsynchronousListener {
} }
}else{ }else{
//tried to access chat without logging in //Tentative d'accès au chat sans loggin
val jsonError = JSONObject(); val jsonError = JSONObject();
jsonError.put("error","You must send your credential before sending anything else"); jsonError.put("error","You must send your credential before sending anything else");
e.client.send(buildTextMessage(jsonError.toString())); e.client.send(buildTextMessage(jsonError.toString()));
} }
return; return;
//si le client envoie un message sur le canal websocket (pas utilisé sur le site car tout passe par l'API REST et les Interops mais utilisable en serveur standalone
}else if(json.has("message")){ }else if(json.has("message")){
val chanMessages = this.messages[e.client.data["emergencyChannel"]]; //on récupère le channel
val chanMessages = this.messages[e.client.data["channel"]];
val message = Message(); val message = Message();
//toujours vrai mais il est important de faire la vérification pour l'analyseur de code kotlin
if(chanMessages != null){ if(chanMessages != null){
//on peuple l'objet message
message.user = e.client.data["Username"].toString(); message.user = e.client.data["Username"].toString();
message.message = json.getString("message"); message.message = json.getString("message");
message.id = "-1"; //ligne pourrie, on ne peut pas donner d'id vu que c'est le serveur de l'API qui le fait, par défaut on utilise le timestamp (qui plus est un id est toujours une string
message.id = System.currentTimeMillis().toString();
//on check les coordonnés GPS
var lat : Float; var lat : Float;
var lng : Float; var lng : Float;
//javascript may send float as integer so we double check that //étant doné que javascript fait un peu ce qu'il veut avec les nombres on peut recevoir des int ou des double, il faut tout cast en float
try { try {
lat = (json.getJSONArray("location")[0] as Double).toFloat(); lat = (json.getJSONArray("location")[0] as Double).toFloat();
}catch(e : Exception){ }catch(e : Exception){
@ -166,20 +217,24 @@ abstract class AbstractChannel : AsynchronousListener {
message.location = mutableListOf(lat,lng); message.location = mutableListOf(lat,lng);
//utilisé que par le channel Event si tout va bien
if(json.has("type")){ if(json.has("type")){
message.type = json.getInt("type"); message.type = json.getInt("type");
} }
//concurrence encore
synchronized(chanMessages,{ synchronized(chanMessages,{
chanMessages.push(Pair(e.client.data["Username"].toString(),message)) chanMessages.push(Pair(e.client.data["Username"].toString(),message))
}); });
} }
var chanName = ""; var chanName = "";
if(e.client.data["emergencyChannel"] != null){ //obligatoire pour l'analyseur de code kotlin
chanName = e.client.data["emergencyChannel"].toString(); if(e.client.data["channel"] != null){
chanName = e.client.data["channel"].toString();
} }
//dispatch.....SPATCH!!!!
for(c in this.clients){ for(c in this.clients){
this.dispatchMessage(chanName,message,"add"); this.dispatchMessage(chanName,message,"add");
} }
@ -188,25 +243,35 @@ abstract class AbstractChannel : AsynchronousListener {
} }
}catch (e : Exception){ }catch (e : Exception){
//System.out.println("Something went wrong (probably JSON parsing error"); //System.out.println("Something went wrong (probably JSON parsing error");
//utile pour debug
e.printStackTrace(); e.printStackTrace();
} }
} }
/**
* SPATCH!! methode envoyant aux bon clients les opérations demandées
* @param channel String sous-channel
* @param message Message message a dispatch
* @param operation String operation a effectuer (add, upd, del)
*/
fun dispatchMessage(channel: String, message: Message, operation : String){ fun dispatchMessage(channel: String, message: Message, operation : String){
//whitelist
val authorizedOperations = listOf<String>("add","upd","del"); val authorizedOperations = listOf<String>("add","upd","del");
//wow much security, very reliable, wow
if(!authorizedOperations.contains(operation)){ if(!authorizedOperations.contains(operation)){
throw Exception("Unauthorized operation"); throw Exception("Unauthorized operation");
} }
//on construit le JSON
val array = JSONObject(); val array = JSONObject();
array.put(message.id,message.toJSON()) array.put(message.id,message.toJSON())
val jsonMessage = JSONObject(); val jsonMessage = JSONObject();
jsonMessage.put("error",false); jsonMessage.put("error",false);
//all operations must be present but can be empty //toutes les opréations doivent être présente mais peuvent être vide
for (op in authorizedOperations){ for (op in authorizedOperations){
if(op == operation){ if(op == operation){
jsonMessage.put(op,array); jsonMessage.put(op,array);
@ -215,40 +280,59 @@ abstract class AbstractChannel : AsynchronousListener {
} }
} }
//SPATCH!!
for(c in this.clients){ for(c in this.clients){
//dispatch message to logged client in the right channel or in any channel if channel is an empty string (broadcast) //on envoie a tout les clients loggés du bon channel, ou en broadcast si aucun channel n'est précisé
if(c.data["IsLogged"] == true && ((channel != "" && c.data["emergencyChannel"] == channel) || channel == "")){ if(c.data["IsLogged"] == true && ((channel != "" && c.data["channel"] == channel) || channel == "")){
c.send(buildTextMessage(jsonMessage.toString())); c.send(buildTextMessage(jsonMessage.toString()));
} }
} }
} }
/**
* Methode utilisée par les classes Interop pour que l'API puisse agir sur les channels
* @param id String id du message a supprimer
*/
fun deleteMessage(id : String){ fun deleteMessage(id : String){
//comme tout les ids sont unique, on doit parcourir tout les channels a la recherche du message
this.messages.keys.forEach { this.messages.keys.forEach {
//comme j'utilise la version fonctionnelle des boucle, la variable it (qui contiens l'item) se reécrit dans la deuxieme boucle
val key = it; val key = it;
val array = this.messages[it]; val array = this.messages[it];
array?.toList()?.forEach{ array?.toList()?.forEach{
if(it.second.id == id){ if(it.second.id == id){
//on enleve de la stack
array.remove(it); array.remove(it);
//on créer un message vide, seul l'id est utile
val msg = Message() val msg = Message()
msg.id = id; msg.id = id;
//on envoie au client
this.dispatchMessage(key,msg,"del"); this.dispatchMessage(key,msg,"del");
//petite optimisation pour éviter de continuer a parcourir les channels
return;
} }
} }
} }
} }
/**
* Methode utilisée par les classes Interop pour que l'API puisse agir sur les channels
* @param msg Message message avec le contenu modifié
*/
fun updateMessage(msg : Message){ fun updateMessage(msg : Message){
//comme pour le delete, on doit tout parcourir
this.messages.keys.forEach { this.messages.keys.forEach {
val key = it; val key = it;
val array = this.messages[it]; val array = this.messages[it];
var index = 0; var index = 0;
array?.toList()?.forEach{ array?.toList()?.forEach{
if(it.second.id == msg.id){ if(it.second.id == msg.id){
//on peuple le nouveau message des anciennes valeurs
msg.user = it.second.user; msg.user = it.second.user;
msg.location = it.second.location; msg.location = it.second.location;
msg.timestamp = it.second.timestamp; msg.timestamp = it.second.timestamp;
array.set(index,Pair(it.first,msg)); array.set(index,Pair(it.first,msg));
//on envoie
this.dispatchMessage(key,msg,"upd"); this.dispatchMessage(key,msg,"upd");
} }
index++; index++;
@ -256,6 +340,10 @@ abstract class AbstractChannel : AsynchronousListener {
} }
} }
/**
* Methode implémentée mais pas utilisée suite a une décision durant la nuit
* aucune idée de si ça marche du coup
*/
fun getMessages(chan : String) : MutableList<Pair<String, Message>>{ fun getMessages(chan : String) : MutableList<Pair<String, Message>>{
val channel = this.messages[chan]?.toList(); val channel = this.messages[chan]?.toList();
val returned = mutableListOf<Pair<String, Message>>() val returned = mutableListOf<Pair<String, Message>>()

View File

@ -2,6 +2,9 @@ package Channels
import seekdasky.kWebSocket.Server import seekdasky.kWebSocket.Server
/**
* Classe gérant le channel emergency
*/
class Emergency : AbstractChannel { class Emergency : AbstractChannel {
override val chanName = "emergency"; override val chanName = "emergency";

View File

@ -2,6 +2,9 @@ package Channels
import seekdasky.kWebSocket.Server import seekdasky.kWebSocket.Server
/**
* classe gérant le channel Event
*/
class Event : AbstractChannel { class Event : AbstractChannel {
override val chanName = "event"; override val chanName = "event";

View File

@ -3,6 +3,9 @@ package Collections
import org.json.JSONObject import org.json.JSONObject
import java.lang.System; import java.lang.System;
/**
* Modèle de donnée standard pour tout les messages de channel
*/
class Message { class Message {
var user = ""; var user = "";
@ -14,10 +17,13 @@ class Message {
var type = -1; var type = -1;
//par défaut la date de message est celle actuelle
constructor(){ constructor(){
//on converti le timestamp de milliseconde en seconde (UNIX timestamp)
this.timestamp = System.currentTimeMillis() / 1000; this.timestamp = System.currentTimeMillis() / 1000;
} }
//permet de facilement serialiser le message
fun toJSON() : JSONObject{ fun toJSON() : JSONObject{
val json = JSONObject(); val json = JSONObject();
json.put("user",user); json.put("user",user);
@ -25,6 +31,7 @@ class Message {
json.put("timestamp",timestamp); json.put("timestamp",timestamp);
json.put("location",location); json.put("location",location);
//le type n'est pas présent sur tout les channels
if(this.type >= 0){ if(this.type >= 0){
json.put("type",type); json.put("type",type);
} }

View File

@ -1,25 +1,39 @@
package Collections package Collections
/**
* Stack FIFO codée pour m'adier a avoir un historique de conversation limité et facile a utiliser
* la classe est générique et réutilisable ailleur au besoin
*/
class Stack<T>(size : Int){ class Stack<T>(size : Int){
var itCounter = 0; //taille max de la stack
val size = size; val size = size;
//collection utilisée pour stocker les données
var items:MutableList<T> = mutableListOf() var items:MutableList<T> = mutableListOf()
//vérifie si la stack est vide
fun isEmpty():Boolean = this.items.isEmpty() fun isEmpty():Boolean = this.items.isEmpty()
//retourne le nombre d'élément de la stack
fun count():Int = this.items.count() fun count():Int = this.items.count()
override fun toString() = this.items.toString() override fun toString() = this.items.toString()
/**
* Rajoute un élément en haut de la stack
* @param element T item a ajouter
*/
fun push(element: T){ fun push(element: T){
//BEWARE this stack is programmed to empty itself automatically if you push an item when the stack is full //ATTENTION cette stack est faite pour évacuer les éléments les plus anciens toute seule en cas de saturation
if(this.items.count() == this.size ){ if(this.items.count() == this.size ){
this.pull(); this.pull();
} }
this.items.add(element) this.items.add(element)
} }
/**
* retire un élément en pas de la stack
*/
fun pull():T?{ fun pull():T?{
if (this.isEmpty()){ if (this.isEmpty()){
return null return null
@ -28,14 +42,22 @@ class Stack<T>(size : Int){
} }
} }
/**
* permet de mettre a jour des élements dans la stack
*/
fun set(index : Int, item : T){ fun set(index : Int, item : T){
this.items.set(index,item); this.items.set(index,item);
} }
//enlève un élément de la stack
fun remove(item : T){ fun remove(item : T){
this.items.remove(item); this.items.remove(item);
} }
/**
* Plutot que d'implémenter l'interface Iterable moi même (risque de bug en cas d'itération concurrente), lorsque l'on a besoin d'itérer sur la stack
* on peut apppeller cette méthode et itérer sur la collection Kotlin qui est thread safe
*/
fun toList() : MutableList<T>{ fun toList() : MutableList<T>{
return this.items.toMutableList(); return this.items.toMutableList();
} }

View File

@ -9,13 +9,22 @@ import seekdasky.kWebSocket.Listeners.InteropListener
import seekdasky.kWebSocket.Message.InteropMessage import seekdasky.kWebSocket.Message.InteropMessage
import seekdasky.kWebSocket.Message.buildTextMessage import seekdasky.kWebSocket.Message.buildTextMessage
/**
* Singleton permettant de gérer les conecion par Interop et par WebSocket, Singleton utilisé par les classes lsiteners pour vérifier l'identité des clients
*/
object ConnectChannel : InteropListener, AsynchronousListener() { object ConnectChannel : InteropListener, AsynchronousListener() {
//utilisé pour que chaque gues ai un id unique (dans la limite de 500)
private var lastGuestIndex = 0; private var lastGuestIndex = 0;
//clints s'étant loggué mais ne s'étant pas encore connecté a un channel (si un client se loggue sans jamais se connecter a un socket il sera éjecté de la satck
private val notBoundYet = Stack<Pair<String, String>>(500); private val notBoundYet = Stack<Pair<String, String>>(500);
//client connecté a un channel WebSocket
private val connected = mutableMapOf<Client, String>(); private val connected = mutableMapOf<Client, String>();
/**
* méthode de filtre pour le WebSOcket
*/
override fun filter(c: Client): Boolean { override fun filter(c: Client): Boolean {
return c.URL == "/connect"; return c.URL == "/connect";
} }
@ -28,13 +37,20 @@ object ConnectChannel : InteropListener, AsynchronousListener() {
return return
} }
/**
* méthode de login par WebSocket
*/
override fun processEvent(e: Event) { override fun processEvent(e: Event) {
try { try {
val json = JSONObject(e.message.toString()); val json = JSONObject(e.message.toString());
//si l'utilisateur est un guest
if(json.getString("type") == "guest"){ if(json.getString("type") == "guest"){
//on lui donne un id unique (dans la limtite de 500)
this.lastGuestIndex = ++this.lastGuestIndex % 500; this.lastGuestIndex = ++this.lastGuestIndex % 500;
val guestName = "Guest"+this.lastGuestIndex; val guestName = "Guest"+this.lastGuestIndex;
//on retourne au client son id de guest
val jsonReturn = JSONObject(); val jsonReturn = JSONObject();
jsonReturn.put("error", false); jsonReturn.put("error", false);
jsonReturn.put("name", guestName); jsonReturn.put("name", guestName);
@ -44,6 +60,7 @@ object ConnectChannel : InteropListener, AsynchronousListener() {
e.client.send(buildTextMessage(jsonReturn.toString())); e.client.send(buildTextMessage(jsonReturn.toString()));
}else{ }else{
//on n'autorise pas la connection en tant qu'admin ou utilisateur (car aucun lien possible avec la base de donnée pour vérifier les tokens
val jsonReturn = JSONObject(); val jsonReturn = JSONObject();
jsonReturn.put("error", "Admin or User login is disabled when using the websocket API, please use the REST API to access you account"); jsonReturn.put("error", "Admin or User login is disabled when using the websocket API, please use the REST API to access you account");
@ -56,6 +73,9 @@ object ConnectChannel : InteropListener, AsynchronousListener() {
} }
} }
/**
* méthode de filtre pour le socket Interop
*/
override fun filter(e: InteropEvent): Boolean { override fun filter(e: InteropEvent): Boolean {
try{ try{
val json = JSONObject(e.message.string); val json = JSONObject(e.message.string);
@ -68,13 +88,18 @@ object ConnectChannel : InteropListener, AsynchronousListener() {
} }
} }
/**
* méthode de loggin par Interop
*/
override fun processEvent(e: InteropEvent) { override fun processEvent(e: InteropEvent) {
try { try {
val json = JSONObject(e.message.string); val json = JSONObject(e.message.string);
if(json.getString("type") == "guest"){ if(json.getString("type") == "guest"){
//Login de guest
this.lastGuestIndex = ++this.lastGuestIndex % 500; this.lastGuestIndex = ++this.lastGuestIndex % 500;
val guestName = "Guest"+this.lastGuestIndex; val guestName = "Guest"+this.lastGuestIndex;
//on retourne l'id de l'utilisateur
val jsonReturn = JSONObject(); val jsonReturn = JSONObject();
jsonReturn.put("error", false); jsonReturn.put("error", false);
jsonReturn.put("name", guestName); jsonReturn.put("name", guestName);
@ -84,6 +109,7 @@ object ConnectChannel : InteropListener, AsynchronousListener() {
e.client.send(InteropMessage(encode(jsonReturn.toString()))); e.client.send(InteropMessage(encode(jsonReturn.toString())));
}else{ }else{
//la vérification de token étant faite par l'API REST, on peut logg les utilisateurs et admin
val jsonReturn = JSONObject(); val jsonReturn = JSONObject();
jsonReturn.put("error", false); jsonReturn.put("error", false);
jsonReturn.put("name", json.getString("name")); jsonReturn.put("name", json.getString("name"));
@ -98,15 +124,21 @@ object ConnectChannel : InteropListener, AsynchronousListener() {
} }
} }
/**
* méthode appelée par les Listeners Interop et WebSocket pour vérifier l'identitée d'un client
*/
fun isConnected(client : Client) : Boolean{ fun isConnected(client : Client) : Boolean{
//est ce que l'utilisateur est deja log sur un autre channel
for(c in this.connected.keys){ for(c in this.connected.keys){
if(c.data["Username"] == client.data["Username"]){ if(c.data["Username"] == client.data["Username"]){
return true; return true;
} }
} }
//est ce qu'il ne s'est pas encore log sur un channel
for(p in this.notBoundYet.toList()) { for(p in this.notBoundYet.toList()) {
if (p.second == client.data["Username"]) { if (p.second == client.data["Username"]) {
//on rajoute le client au clients utilisant un channel
this.connected.put(client, p.first); this.connected.put(client, p.first);
this.notBoundYet.remove(p); this.notBoundYet.remove(p);
return true; return true;
@ -116,6 +148,9 @@ object ConnectChannel : InteropListener, AsynchronousListener() {
return false; return false;
} }
/**
* lorsqu'un client se déconnecte, on remet son ID dans la catégorie des clients pas encore connecté afin de ne pas avoir a su re loguer a chaque changement de channel
*/
fun notifyDisconnect(client : Client){ fun notifyDisconnect(client : Client){
val clientType = this.connected[client]; val clientType = this.connected[client];
if(clientType != null){ if(clientType != null){

View File

@ -6,9 +6,14 @@ import org.json.JSONObject
import seekdasky.kWebSocket.InteropEvent import seekdasky.kWebSocket.InteropEvent
import seekdasky.kWebSocket.Listeners.InteropListener import seekdasky.kWebSocket.Listeners.InteropListener
/**
* Classe Interop permettant de gérer toutes les suppréssions de message
*/
class DelMessage : InteropListener { class DelMessage : InteropListener {
//channel emergency
val emergencyController : Emergency; val emergencyController : Emergency;
//channel event
val eventController : Event; val eventController : Event;
constructor(emergency : Emergency, event : Event){ constructor(emergency : Emergency, event : Event){
@ -16,6 +21,9 @@ class DelMessage : InteropListener {
this.eventController = event; this.eventController = event;
} }
/**
* Comme pour les Listeners WebSocket, cette méthode filtre les évenements entrant
*/
override fun filter(e: InteropEvent): Boolean { override fun filter(e: InteropEvent): Boolean {
val json = JSONObject(e.message.string); val json = JSONObject(e.message.string);
@ -24,12 +32,15 @@ class DelMessage : InteropListener {
json.getString("operation") == "DelMessage"; json.getString("operation") == "DelMessage";
} }
/**
* méthode gérant la suppression de message
*/
override fun processEvent(e: InteropEvent) { override fun processEvent(e: InteropEvent) {
try { try {
val json = JSONObject(e.message.string); val json = JSONObject(e.message.string);
//on dispatch au bon channel
if(json.getString("channelType") == "Emergency"){ if(json.getString("channelType") == "Emergency"){
this.emergencyController.deleteMessage(json.getString("id")); this.emergencyController.deleteMessage(json.getString("id"));
}else if(json.getString("channelType") == "Event"){ }else if(json.getString("channelType") == "Event"){

View File

@ -7,16 +7,26 @@ import org.json.JSONObject
import seekdasky.kWebSocket.InteropEvent import seekdasky.kWebSocket.InteropEvent
import seekdasky.kWebSocket.Listeners.InteropListener import seekdasky.kWebSocket.Listeners.InteropListener
/**
* Classe Interop permettant de gérer tous les ajouts de message
*/
class PostMessage : InteropListener { class PostMessage : InteropListener {
//channel emergency
val emergencyController : Emergency; val emergencyController : Emergency;
//channel event
val eventController : Event; val eventController : Event;
constructor(emergency : Emergency, event : Event){ constructor(emergency : Emergency, event : Event){
this.emergencyController = emergency; this.emergencyController = emergency;
this.eventController = event; this.eventController = event;
} }
/**
* cf: DelMessage
*/
override fun filter(e: InteropEvent): Boolean { override fun filter(e: InteropEvent): Boolean {
val json = JSONObject(e.message.string); val json = JSONObject(e.message.string);
@ -25,12 +35,15 @@ class PostMessage : InteropListener {
json.getString("operation") == "PostMessage"; json.getString("operation") == "PostMessage";
} }
/**
* méthode traitant l'ajout de message
*/
override fun processEvent(e: InteropEvent) { override fun processEvent(e: InteropEvent) {
try { try {
val json = JSONObject(e.message.string); val json = JSONObject(e.message.string);
//javascript may send float as integer so we double check that //javascript fait un peu ce qu'il veut au niveau des nombres et peut envoyer des doubles ou des int, on doit tout cast en float
var lat : Float; var lat : Float;
var lng : Float var lng : Float
try { try {
@ -47,26 +60,24 @@ class PostMessage : InteropListener {
val location = mutableListOf<Float>(lat, lng); val location = mutableListOf<Float>(lat, lng);
//on peuple le modèle
val message = Message(); val message = Message();
message.location = location; message.location = location;
message.message = json.getString("message"); message.message = json.getString("message");
message.user = json.getString("username"); message.user = json.getString("username");
message.id = json.getString("id"); message.id = json.getString("id");
message.timestamp = System.currentTimeMillis() / 1000;
if(json.getString("channelType") == "Emergency"){ if(json.getString("channelType") == "Emergency"){
this.emergencyController.manuallyDispatchMessage( this.emergencyController.manuallyDispatchMessage(
json.getString("channelName"), json.getString("channelName"),
message, message
"add"
); );
}else if(json.getString("channelType") == "Event"){ }else if(json.getString("channelType") == "Event"){
message.type = json.getInt("type"); message.type = json.getInt("type");
this.eventController.manuallyDispatchMessage( this.eventController.manuallyDispatchMessage(
json.getString("channelName"), json.getString("channelName"),
message, message
"add"
); );
} }

View File

@ -7,6 +7,9 @@ import org.json.JSONObject
import seekdasky.kWebSocket.InteropEvent import seekdasky.kWebSocket.InteropEvent
import seekdasky.kWebSocket.Listeners.InteropListener import seekdasky.kWebSocket.Listeners.InteropListener
/**
* Classe Interop permettant de gérer toutes les mise a jour de message
*/
class UpdMessage : InteropListener { class UpdMessage : InteropListener {
val emergencyController : Emergency; val emergencyController : Emergency;

View File

@ -7,22 +7,32 @@ import seekdasky.kWebSocket.Server
fun main(args: Array<String>){ fun main(args: Array<String>){
val server = Server("0.0.0.0",9999,null); //on démarre le serveur WebSOcket
val server = Server("0.0.0.0",9999,null)
//on démarre le serveur Interop
server.startInteropServer("localhost",9998); server.startInteropServer("localhost",9998);
//channels WebSocket pilotés par Interop
val emergencyChannel = Emergency(server); val emergencyChannel = Emergency(server);
val eventChannel = Event(server); val eventChannel = Event(server);
//channel WebSocket
server.addListener(Channel(server,"/chat")); server.addListener(Channel(server,"/chat"));
//on bind les listeners
server.addListener(emergencyChannel); server.addListener(emergencyChannel);
server.addListener(eventChannel) server.addListener(eventChannel);
//service de login par WebSocket
server.addListener(ConnectChannel); server.addListener(ConnectChannel);
//Listeners Interops
server.addInteropListener(PostMessage(emergencyChannel,eventChannel)); server.addInteropListener(PostMessage(emergencyChannel,eventChannel));
server.addInteropListener(DelMessage(emergencyChannel,eventChannel)); server.addInteropListener(DelMessage(emergencyChannel,eventChannel));
server.addInteropListener(UpdMessage(emergencyChannel,eventChannel)); server.addInteropListener(UpdMessage(emergencyChannel,eventChannel));
//login Interop
server.addInteropListener(ConnectChannel); server.addInteropListener(ConnectChannel);
//et c'est partiiiiiiii
server.startServer(); server.startServer();
//méthode bloquant le thread principal tant que le serveur est en vie
server.block(); server.block();
} }