Compare commits
5 Commits
emergency_
...
master
Author | SHA1 | Date |
---|---|---|
SeekDaSky | 317c4164b3 | |
SeekDaSky | b944b060c1 | |
SeekDaSky | ecaed684fc | |
SeekDaSky | c0d3d1d1a5 | |
SeekDaSky | e739fb25d6 |
|
@ -7,11 +7,18 @@ import seekdasky.kWebSocket.Listeners.AsynchronousListener
|
|||
import seekdasky.kWebSocket.Message.*;
|
||||
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 {
|
||||
|
||||
//nom du channel, utilisé pour définir l'URL d'écoute
|
||||
val channelName : String;
|
||||
//list des clients
|
||||
var clients : MutableList<Client>;
|
||||
//liste des messages du chat
|
||||
val messages : Stack<Pair<Client, String>>;
|
||||
//instance du serveur
|
||||
val serv : Server;
|
||||
|
||||
constructor(serv : Server , chanName : String){
|
||||
|
@ -21,43 +28,56 @@ class Channel : AsynchronousListener {
|
|||
this.serv = serv;
|
||||
}
|
||||
|
||||
/**
|
||||
* comme toute les autres méthodes filter
|
||||
*/
|
||||
override fun filter(c: Client): Boolean {
|
||||
return this.channelName == c.URL;
|
||||
}
|
||||
|
||||
/**
|
||||
* méthode executée quand le client ferme son socket
|
||||
*/
|
||||
override fun processClosed(e: Event) {
|
||||
this.clients.remove(e.client);
|
||||
ConnectChannel.notifyDisconnect(e.client);
|
||||
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) {
|
||||
this.clients.add(c);
|
||||
c.data["IsLogged"] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* méthode gérant la connexion et l'envoi de message
|
||||
*/
|
||||
override fun processEvent(e: Event) {
|
||||
try{
|
||||
val json = JSONObject(e.message.getString())
|
||||
|
||||
if(json.has("close")){
|
||||
return
|
||||
}
|
||||
|
||||
if(e.client.data["IsLogged"] != true){
|
||||
//json format : {username:xxx}
|
||||
if(json.has("name") && json.getString("name") != null){
|
||||
e.client.data["Username"] = json.getString("name");
|
||||
|
||||
//on demande si le client est bien connecté
|
||||
if(ConnectChannel.isConnected(e.client)){
|
||||
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;
|
||||
val jsonLogin = JSONObject();
|
||||
jsonLogin.put("error",false);
|
||||
e.client.send(buildTextMessage(jsonLogin.toString()));
|
||||
|
||||
|
||||
//on lui envoie la liste des messages
|
||||
val array = JSONArray();
|
||||
//gare a la concurrence
|
||||
synchronized(this.messages,{
|
||||
this.messages.toList().forEach {
|
||||
array.put(JSONArray(listOf(it.first.data["Username"],it.second)))
|
||||
|
@ -87,6 +107,7 @@ class Channel : AsynchronousListener {
|
|||
|
||||
return;
|
||||
}else if(json.has("message")){
|
||||
//gare a la concurrence
|
||||
synchronized(this.messages,{
|
||||
this.messages.push(Pair(e.client,json.getString("message")))
|
||||
});
|
||||
|
@ -98,7 +119,7 @@ class Channel : AsynchronousListener {
|
|||
jsonMessage.put("error",false);
|
||||
jsonMessage.put("msg",array);
|
||||
for(c in this.clients){
|
||||
if(c != e.client && c.data["IsLogged"] == true){
|
||||
if(c.data["IsLogged"] == true){
|
||||
c.send(buildTextMessage(jsonMessage.toString()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,359 @@
|
|||
package Channels
|
||||
|
||||
import Collections.Message
|
||||
import Collections.Stack
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import seekdasky.kWebSocket.Client
|
||||
import seekdasky.kWebSocket.Event
|
||||
import seekdasky.kWebSocket.Listeners.AsynchronousListener
|
||||
import seekdasky.kWebSocket.Message.buildTextMessage
|
||||
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 {
|
||||
|
||||
//liste de tout les clients du channel
|
||||
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>>>;
|
||||
|
||||
//instance du serveur pour dispatcher les évenements
|
||||
val serv : Server;
|
||||
|
||||
//propriété abstraite servant a définir l'addresse d'écoute
|
||||
abstract val chanName : String;
|
||||
|
||||
/**
|
||||
* Constructeur de la classe
|
||||
*
|
||||
* @param serv Server instance du serveur
|
||||
*/
|
||||
constructor(serv : Server){
|
||||
this.clients = mutableListOf();
|
||||
this.messages = mutableMapOf();
|
||||
this.serv = serv;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 result = regex.findAll(c.URL);
|
||||
if(result.count() == 1){
|
||||
return true;
|
||||
}
|
||||
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) {
|
||||
this.clients.remove(e.client);
|
||||
ConnectChannel.notifyDisconnect(e.client);
|
||||
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) {
|
||||
//on rajoute le client a notre liste
|
||||
this.clients.add(c);
|
||||
val regex = "\\/$chanName\\/(.*)".toRegex();
|
||||
val result = regex.findAll(c.URL).elementAt(0).groups[1]?.value ?: "";
|
||||
|
||||
//si le sous-channel auquel le client esaie d'accéder n'existe pas, on le créé
|
||||
if(this.messages[result] == null){
|
||||
this.messages[result] = Stack( 50);
|
||||
}
|
||||
|
||||
//on rajoute au client une donnée permettant de savoir a quel sous-channel il est lié
|
||||
if(c.data["channel"] == null){
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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];
|
||||
|
||||
//si il existe, on dispatch
|
||||
if(channelStack != null){
|
||||
|
||||
//gare aux modifications concurrentes
|
||||
synchronized(channelStack, {
|
||||
channelStack.push(Pair(msg.user,msg));
|
||||
})
|
||||
|
||||
//dispatch
|
||||
this.dispatchMessage(channel,msg,"add");
|
||||
//sinon si la chaine est vide on broadcast
|
||||
}else if (channel == ""){
|
||||
//broadcast
|
||||
for(c in this.messages.values){
|
||||
synchronized(c,{
|
||||
c.push(Pair(msg.user,msg));
|
||||
})
|
||||
}
|
||||
|
||||
//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) {
|
||||
try{
|
||||
val json = JSONObject(e.message.getString())
|
||||
|
||||
//si l'utilisateur n'est pas log, on le check
|
||||
if(e.client.data["IsLogged"] != true){
|
||||
//json format : {username:xxx}
|
||||
if(json.has("name") && json.getString("name") != null){
|
||||
//on set le nom d'utilisateur
|
||||
e.client.data["Username"] = json.getString("name");
|
||||
|
||||
//On vérifie qu'il est bien connecté a l'aide de la classe ConnectChannel
|
||||
if(ConnectChannel.isConnected(e.client)){
|
||||
//log
|
||||
System.out.println("Emergency connection ("+this.clients.count()+" clients connected)");
|
||||
|
||||
//on retourne une réponse de succès au client
|
||||
e.client.data["IsLogged"] = true;
|
||||
val jsonLogin = JSONObject();
|
||||
jsonLogin.put("error",false);
|
||||
e.client.send(buildTextMessage(jsonLogin.toString()));
|
||||
|
||||
//on envoie au client la liste des messages récent
|
||||
val array = JSONObject();
|
||||
|
||||
val chanMessages = this.messages[e.client.data["channel"]];
|
||||
if(chanMessages != null){
|
||||
//gare a la concurrence
|
||||
synchronized(chanMessages,{
|
||||
chanMessages.toList().forEach {
|
||||
array.put(it.second.id,it.second.toJSON())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//on construit le JSON
|
||||
val jsonMessage = JSONObject();
|
||||
jsonMessage.put("error",false);
|
||||
jsonMessage.put("add",array);
|
||||
//on envoie la sauce
|
||||
e.client.send(buildTextMessage(jsonMessage.toString()));
|
||||
}else{
|
||||
val jsonError = JSONObject();
|
||||
jsonError.put("error","Invalid credentials");
|
||||
|
||||
e.client.send(buildTextMessage(jsonError.toString()));
|
||||
}
|
||||
|
||||
}else{
|
||||
//Tentative d'accès au chat sans loggin
|
||||
val jsonError = JSONObject();
|
||||
jsonError.put("error","You must send your credential before sending anything else");
|
||||
|
||||
e.client.send(buildTextMessage(jsonError.toString()));
|
||||
|
||||
}
|
||||
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")){
|
||||
//on récupère le channel
|
||||
val chanMessages = this.messages[e.client.data["channel"]];
|
||||
val message = Message();
|
||||
//toujours vrai mais il est important de faire la vérification pour l'analyseur de code kotlin
|
||||
if(chanMessages != null){
|
||||
//on peuple l'objet message
|
||||
message.user = e.client.data["Username"].toString();
|
||||
message.message = json.getString("message");
|
||||
//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 lng : Float;
|
||||
|
||||
//é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 {
|
||||
lat = (json.getJSONArray("location")[0] as Double).toFloat();
|
||||
}catch(e : Exception){
|
||||
lat = (json.getJSONArray("location")[0] as Integer).toFloat();
|
||||
}
|
||||
|
||||
try {
|
||||
lng = (json.getJSONArray("location")[1] as Double).toFloat();
|
||||
}catch(e : Exception){
|
||||
lng = (json.getJSONArray("location")[1] as Integer).toFloat();
|
||||
}
|
||||
|
||||
message.location = mutableListOf(lat,lng);
|
||||
|
||||
//utilisé que par le channel Event si tout va bien
|
||||
if(json.has("type")){
|
||||
message.type = json.getInt("type");
|
||||
}
|
||||
|
||||
//concurrence encore
|
||||
synchronized(chanMessages,{
|
||||
chanMessages.push(Pair(e.client.data["Username"].toString(),message))
|
||||
});
|
||||
}
|
||||
|
||||
var chanName = "";
|
||||
//obligatoire pour l'analyseur de code kotlin
|
||||
if(e.client.data["channel"] != null){
|
||||
chanName = e.client.data["channel"].toString();
|
||||
}
|
||||
|
||||
//dispatch.....SPATCH!!!!
|
||||
for(c in this.clients){
|
||||
this.dispatchMessage(chanName,message,"add");
|
||||
}
|
||||
}else{
|
||||
System.out.println("unknown JSON: "+json.toString());
|
||||
}
|
||||
}catch (e : Exception){
|
||||
//System.out.println("Something went wrong (probably JSON parsing error");
|
||||
//utile pour debug
|
||||
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){
|
||||
|
||||
//whitelist
|
||||
val authorizedOperations = listOf<String>("add","upd","del");
|
||||
|
||||
//wow much security, very reliable, wow
|
||||
if(!authorizedOperations.contains(operation)){
|
||||
throw Exception("Unauthorized operation");
|
||||
}
|
||||
|
||||
//on construit le JSON
|
||||
val array = JSONObject();
|
||||
array.put(message.id,message.toJSON())
|
||||
|
||||
val jsonMessage = JSONObject();
|
||||
jsonMessage.put("error",false);
|
||||
|
||||
//toutes les opréations doivent être présente mais peuvent être vide
|
||||
for (op in authorizedOperations){
|
||||
if(op == operation){
|
||||
jsonMessage.put(op,array);
|
||||
}else{
|
||||
jsonMessage.put(op, JSONArray());
|
||||
}
|
||||
}
|
||||
|
||||
//SPATCH!!
|
||||
for(c in this.clients){
|
||||
//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["channel"] == channel) || channel == "")){
|
||||
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){
|
||||
//comme tout les ids sont unique, on doit parcourir tout les channels a la recherche du message
|
||||
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 array = this.messages[it];
|
||||
array?.toList()?.forEach{
|
||||
if(it.second.id == id){
|
||||
//on enleve de la stack
|
||||
array.remove(it);
|
||||
//on créer un message vide, seul l'id est utile
|
||||
val msg = Message()
|
||||
msg.id = id;
|
||||
//on envoie au client
|
||||
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){
|
||||
//comme pour le delete, on doit tout parcourir
|
||||
this.messages.keys.forEach {
|
||||
val key = it;
|
||||
val array = this.messages[it];
|
||||
var index = 0;
|
||||
array?.toList()?.forEach{
|
||||
if(it.second.id == msg.id){
|
||||
//on peuple le nouveau message des anciennes valeurs
|
||||
msg.user = it.second.user;
|
||||
msg.location = it.second.location;
|
||||
msg.timestamp = it.second.timestamp;
|
||||
array.set(index,Pair(it.first,msg));
|
||||
//on envoie
|
||||
this.dispatchMessage(key,msg,"upd");
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>>{
|
||||
val channel = this.messages[chan]?.toList();
|
||||
val returned = mutableListOf<Pair<String, Message>>()
|
||||
if(channel != null){
|
||||
for (m in channel){
|
||||
returned.add(Pair(m.second.id,m.second));
|
||||
}
|
||||
}
|
||||
|
||||
return returned;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,219 +1,13 @@
|
|||
package Channels
|
||||
|
||||
import Collections.Message
|
||||
import Collections.Stack
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import seekdasky.kWebSocket.Client
|
||||
import seekdasky.kWebSocket.Event
|
||||
import seekdasky.kWebSocket.Listeners.AsynchronousListener
|
||||
import seekdasky.kWebSocket.Message.buildTextMessage
|
||||
import seekdasky.kWebSocket.Server
|
||||
|
||||
class Emergency : AsynchronousListener {
|
||||
/**
|
||||
* Classe gérant le channel emergency
|
||||
*/
|
||||
class Emergency : AbstractChannel {
|
||||
|
||||
var clients : MutableList<Client>;
|
||||
val messages : MutableMap<String,Stack<Pair<String,Message>>>;
|
||||
val serv : Server;
|
||||
override val chanName = "emergency";
|
||||
|
||||
constructor(serv : Server){
|
||||
this.clients = mutableListOf();
|
||||
this.messages = mutableMapOf();
|
||||
this.serv = serv;
|
||||
}
|
||||
|
||||
override fun filter(c: Client): Boolean {
|
||||
val regex = "\\/emergency\\/(.*)".toRegex()
|
||||
val result = regex.findAll(c.URL);
|
||||
if(result.count() == 1){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
override fun processClosed(e: Event) {
|
||||
this.clients.remove(e.client);
|
||||
ConnectChannel.notifyDisconnect(e.client);
|
||||
System.out.println("A client disconnected ("+this.clients.count()+" clients connected)");
|
||||
}
|
||||
|
||||
override fun processConnection(c: Client) {
|
||||
this.clients.add(c);
|
||||
val regex = "\\/emergency\\/(.*)".toRegex();
|
||||
val result = regex.findAll(c.URL).elementAt(0).groups[1]?.value ?: "";
|
||||
|
||||
//create stack if not present
|
||||
if(this.messages[result] == null){
|
||||
this.messages[result] = Stack( 50);
|
||||
}
|
||||
|
||||
//bind client to channel
|
||||
if(c.data["emergencyChannel"] == null){
|
||||
c.data["emergencyChannel"] = result;
|
||||
}
|
||||
c.data["IsLogged"] = false;
|
||||
}
|
||||
|
||||
fun manuallyDispatchMessage(channel : String, msg : String, username : String, location : List<Float>){
|
||||
val message = Message();
|
||||
message.location = location.toMutableList();
|
||||
message.message = msg;
|
||||
message.user = username;
|
||||
message.timestamp = System.currentTimeMillis() / 1000;
|
||||
|
||||
val channelStack = this.messages[channel];
|
||||
|
||||
if(channelStack != null){
|
||||
|
||||
synchronized(channelStack, {
|
||||
channelStack.push(Pair(username,message));
|
||||
})
|
||||
|
||||
val array = JSONArray();
|
||||
array.put(message.toJSON())
|
||||
|
||||
val jsonMessage = JSONObject();
|
||||
jsonMessage.put("error",false);
|
||||
jsonMessage.put("data",array);
|
||||
|
||||
for(c in this.clients){
|
||||
if(c.data["IsLogged"] == true && c.data["emergencyChannel"] == channel){
|
||||
c.send(buildTextMessage(jsonMessage.toString()));
|
||||
}
|
||||
}
|
||||
}else if (channel == ""){
|
||||
//broadcast
|
||||
for(c in this.messages.values){
|
||||
synchronized(c,{
|
||||
c.push(Pair(username,message));
|
||||
})
|
||||
}
|
||||
|
||||
val array = JSONArray();
|
||||
array.put(message.toJSON())
|
||||
|
||||
val jsonMessage = JSONObject();
|
||||
jsonMessage.put("error",false);
|
||||
jsonMessage.put("data",array);
|
||||
|
||||
for(c in this.clients){
|
||||
if(c.data["IsLogged"] == true){
|
||||
c.send(buildTextMessage(jsonMessage.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun processEvent(e: Event) {
|
||||
System.out.println(e.message.toString());
|
||||
try{
|
||||
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
|
||||
if(e.client.data["IsLogged"] != true){
|
||||
//json format : {username:xxx}
|
||||
if(json.has("name") && json.getString("name") != null){
|
||||
e.client.data["Username"] = json.getString("name");
|
||||
|
||||
//if the client correctly identified with the Interop server
|
||||
if(ConnectChannel.isConnected(e.client)){
|
||||
//log
|
||||
System.out.println("Emergency connection ("+this.clients.count()+" clients connected)");
|
||||
|
||||
//return OK to the client
|
||||
e.client.data["IsLogged"] = true;
|
||||
val jsonLogin = JSONObject();
|
||||
jsonLogin.put("error",false);
|
||||
e.client.send(buildTextMessage(jsonLogin.toString()));
|
||||
|
||||
//give the client the list of the most recent messages
|
||||
val array = JSONArray();
|
||||
|
||||
val chanMessages = this.messages[e.client.data["emergencyChannel"]];
|
||||
if(chanMessages != null){
|
||||
synchronized(chanMessages,{
|
||||
chanMessages.toList().forEach {
|
||||
array.put(it.second.toJSON())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//build JSON
|
||||
val jsonMessage = JSONObject();
|
||||
jsonMessage.put("error",false);
|
||||
jsonMessage.put("data",array);
|
||||
|
||||
e.client.send(buildTextMessage(jsonMessage.toString()));
|
||||
}else{
|
||||
val jsonError = JSONObject();
|
||||
jsonError.put("error","Invalid credentials");
|
||||
|
||||
e.client.send(buildTextMessage(jsonError.toString()));
|
||||
}
|
||||
|
||||
}else{
|
||||
//tried to access chat without logging in
|
||||
val jsonError = JSONObject();
|
||||
jsonError.put("error","You must send your credential before sending anything else");
|
||||
|
||||
e.client.send(buildTextMessage(jsonError.toString()));
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
}else if(json.has("message")){
|
||||
val chanMessages = this.messages[e.client.data["emergencyChannel"]];
|
||||
val message = Message();
|
||||
if(chanMessages != null){
|
||||
message.user = e.client.data["Username"].toString();
|
||||
message.message = json.getString("message");
|
||||
|
||||
var lat = 0F;
|
||||
var lng = 0F;
|
||||
|
||||
//javascript may send float as integer so we double check that
|
||||
try {
|
||||
lat = (json.getJSONArray("location")[0] as Double).toFloat();
|
||||
}catch(e : Exception){
|
||||
lat = (json.getJSONArray("location")[0] as Integer).toFloat();
|
||||
}
|
||||
|
||||
try {
|
||||
lng = (json.getJSONArray("location")[1] as Double).toFloat();
|
||||
}catch(e : Exception){
|
||||
lng = (json.getJSONArray("location")[1] as Integer).toFloat();
|
||||
}
|
||||
|
||||
message.location = mutableListOf(lat,lng);
|
||||
|
||||
synchronized(chanMessages,{
|
||||
chanMessages.push(Pair(e.client.data["Username"].toString(),message))
|
||||
});
|
||||
}
|
||||
|
||||
val array = JSONArray();
|
||||
array.put(message.toJSON())
|
||||
|
||||
val jsonMessage = JSONObject();
|
||||
jsonMessage.put("error",false);
|
||||
jsonMessage.put("data",array);
|
||||
for(c in this.clients){
|
||||
if(c.data["IsLogged"] == true && c.data["emergencyChannel"] == e.client.data["emergencyChannel"]){
|
||||
c.send(buildTextMessage(jsonMessage.toString()));
|
||||
}
|
||||
}
|
||||
}else{
|
||||
System.out.println("unknown JSON: "+json.toString());
|
||||
}
|
||||
}catch (e : Exception){
|
||||
//System.out.println("Something went wrong (probably JSON parsing error");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
constructor(server: Server) : super(server);
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package Channels
|
||||
|
||||
import seekdasky.kWebSocket.Server
|
||||
|
||||
/**
|
||||
* classe gérant le channel Event
|
||||
*/
|
||||
class Event : AbstractChannel {
|
||||
|
||||
override val chanName = "event";
|
||||
|
||||
constructor(server: Server) : super(server);
|
||||
|
||||
}
|
|
@ -3,18 +3,27 @@ package Collections
|
|||
import org.json.JSONObject
|
||||
import java.lang.System;
|
||||
|
||||
/**
|
||||
* Modèle de donnée standard pour tout les messages de channel
|
||||
*/
|
||||
class Message {
|
||||
|
||||
var user = "";
|
||||
var message = "";
|
||||
var timestamp = 0L;
|
||||
var location = mutableListOf<Float>();
|
||||
var id = "";
|
||||
|
||||
var type = -1;
|
||||
|
||||
|
||||
//par défaut la date de message est celle actuelle
|
||||
constructor(){
|
||||
//on converti le timestamp de milliseconde en seconde (UNIX timestamp)
|
||||
this.timestamp = System.currentTimeMillis() / 1000;
|
||||
}
|
||||
|
||||
//permet de facilement serialiser le message
|
||||
fun toJSON() : JSONObject{
|
||||
val json = JSONObject();
|
||||
json.put("user",user);
|
||||
|
@ -22,6 +31,11 @@ class Message {
|
|||
json.put("timestamp",timestamp);
|
||||
json.put("location",location);
|
||||
|
||||
//le type n'est pas présent sur tout les channels
|
||||
if(this.type >= 0){
|
||||
json.put("type",type);
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
}
|
|
@ -1,25 +1,39 @@
|
|||
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){
|
||||
|
||||
var itCounter = 0;
|
||||
//taille max de la stack
|
||||
val size = size;
|
||||
//collection utilisée pour stocker les données
|
||||
var items:MutableList<T> = mutableListOf()
|
||||
|
||||
//vérifie si la stack est vide
|
||||
fun isEmpty():Boolean = this.items.isEmpty()
|
||||
|
||||
//retourne le nombre d'élément de la stack
|
||||
fun count():Int = this.items.count()
|
||||
|
||||
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){
|
||||
//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 ){
|
||||
this.pull();
|
||||
}
|
||||
this.items.add(element)
|
||||
}
|
||||
|
||||
/**
|
||||
* retire un élément en pas de la stack
|
||||
*/
|
||||
fun pull():T?{
|
||||
if (this.isEmpty()){
|
||||
return null
|
||||
|
@ -28,12 +42,24 @@ class Stack<T>(size : Int){
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* permet de mettre a jour des élements dans la stack
|
||||
*/
|
||||
fun set(index : Int, item : T){
|
||||
this.items.set(index,item);
|
||||
}
|
||||
|
||||
//enlève un élément de la stack
|
||||
fun remove(item : T){
|
||||
this.items.remove(item);
|
||||
}
|
||||
|
||||
fun toList() : List<T>{
|
||||
return this.items.toList();
|
||||
/**
|
||||
* 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>{
|
||||
return this.items.toMutableList();
|
||||
}
|
||||
|
||||
}
|
|
@ -2,18 +2,80 @@ import Collections.Stack
|
|||
import org.json.JSONObject
|
||||
import seekdasky.UTF8Encoding.encode
|
||||
import seekdasky.kWebSocket.Client
|
||||
import seekdasky.kWebSocket.Event
|
||||
import seekdasky.kWebSocket.InteropEvent
|
||||
import seekdasky.kWebSocket.Listeners.AsynchronousListener
|
||||
import seekdasky.kWebSocket.Listeners.InteropListener
|
||||
import seekdasky.kWebSocket.Message.InteropMessage
|
||||
import seekdasky.kWebSocket.Message.buildTextMessage
|
||||
|
||||
object ConnectChannel : InteropListener {
|
||||
/**
|
||||
* 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() {
|
||||
|
||||
//utilisé pour que chaque gues ai un id unique (dans la limite de 500)
|
||||
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);
|
||||
//client connecté a un channel WebSocket
|
||||
private val connected = mutableMapOf<Client, String>();
|
||||
|
||||
/**
|
||||
* méthode de filtre pour le WebSOcket
|
||||
*/
|
||||
override fun filter(c: Client): Boolean {
|
||||
return c.URL == "/connect";
|
||||
}
|
||||
|
||||
override fun processClosed(e: Event) {
|
||||
return;
|
||||
}
|
||||
|
||||
override fun processConnection(c: Client) {
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* méthode de login par WebSocket
|
||||
*/
|
||||
override fun processEvent(e: Event) {
|
||||
try {
|
||||
val json = JSONObject(e.message.toString());
|
||||
|
||||
//si l'utilisateur est un guest
|
||||
if(json.getString("type") == "guest"){
|
||||
//on lui donne un id unique (dans la limtite de 500)
|
||||
this.lastGuestIndex = ++this.lastGuestIndex % 500;
|
||||
val guestName = "Guest"+this.lastGuestIndex;
|
||||
|
||||
//on retourne au client son id de guest
|
||||
val jsonReturn = JSONObject();
|
||||
jsonReturn.put("error", false);
|
||||
jsonReturn.put("name", guestName);
|
||||
|
||||
this.notBoundYet.push(Pair("guest",guestName));
|
||||
|
||||
|
||||
e.client.send(buildTextMessage(jsonReturn.toString()));
|
||||
}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();
|
||||
jsonReturn.put("error", "Admin or User login is disabled when using the websocket API, please use the REST API to access you account");
|
||||
|
||||
e.client.send(buildTextMessage(jsonReturn.toString()));
|
||||
}
|
||||
}catch (ex : Exception){
|
||||
val jsonReturn = JSONObject();
|
||||
jsonReturn.put("error",true);
|
||||
e.client.send(buildTextMessage(jsonReturn.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* méthode de filtre pour le socket Interop
|
||||
*/
|
||||
override fun filter(e: InteropEvent): Boolean {
|
||||
try{
|
||||
val json = JSONObject(e.message.string);
|
||||
|
@ -26,13 +88,18 @@ object ConnectChannel : InteropListener {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* méthode de loggin par Interop
|
||||
*/
|
||||
override fun processEvent(e: InteropEvent) {
|
||||
try {
|
||||
val json = JSONObject(e.message.string);
|
||||
|
||||
if(json.getString("type") == "guest"){
|
||||
//Login de guest
|
||||
this.lastGuestIndex = ++this.lastGuestIndex % 500;
|
||||
val guestName = "Guest"+this.lastGuestIndex;
|
||||
//on retourne l'id de l'utilisateur
|
||||
val jsonReturn = JSONObject();
|
||||
jsonReturn.put("error", false);
|
||||
jsonReturn.put("name", guestName);
|
||||
|
@ -42,6 +109,7 @@ object ConnectChannel : InteropListener {
|
|||
|
||||
e.client.send(InteropMessage(encode(jsonReturn.toString())));
|
||||
}else{
|
||||
//la vérification de token étant faite par l'API REST, on peut logg les utilisateurs et admin
|
||||
val jsonReturn = JSONObject();
|
||||
jsonReturn.put("error", false);
|
||||
jsonReturn.put("name", json.getString("name"));
|
||||
|
@ -56,15 +124,21 @@ object ConnectChannel : InteropListener {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* méthode appelée par les Listeners Interop et WebSocket pour vérifier l'identitée d'un client
|
||||
*/
|
||||
fun isConnected(client : Client) : Boolean{
|
||||
//est ce que l'utilisateur est deja log sur un autre channel
|
||||
for(c in this.connected.keys){
|
||||
if(c.data["Username"] == client.data["Username"]){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//est ce qu'il ne s'est pas encore log sur un channel
|
||||
for(p in this.notBoundYet.toList()) {
|
||||
if (p.second == client.data["Username"]) {
|
||||
//on rajoute le client au clients utilisant un channel
|
||||
this.connected.put(client, p.first);
|
||||
this.notBoundYet.remove(p);
|
||||
return true;
|
||||
|
@ -74,6 +148,9 @@ object ConnectChannel : InteropListener {
|
|||
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){
|
||||
val clientType = this.connected[client];
|
||||
if(clientType != null){
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
package Interop
|
||||
|
||||
import Channels.Emergency
|
||||
import Channels.Event
|
||||
import org.json.JSONObject
|
||||
import seekdasky.kWebSocket.InteropEvent
|
||||
import seekdasky.kWebSocket.Listeners.InteropListener
|
||||
|
||||
/**
|
||||
* Classe Interop permettant de gérer toutes les suppréssions de message
|
||||
*/
|
||||
class DelMessage : InteropListener {
|
||||
|
||||
//channel emergency
|
||||
val emergencyController : Emergency;
|
||||
//channel event
|
||||
val eventController : Event;
|
||||
|
||||
constructor(emergency : Emergency, event : Event){
|
||||
this.emergencyController = emergency;
|
||||
this.eventController = event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comme pour les Listeners WebSocket, cette méthode filtre les évenements entrant
|
||||
*/
|
||||
override fun filter(e: InteropEvent): Boolean {
|
||||
|
||||
val json = JSONObject(e.message.string);
|
||||
|
||||
return json.has("operation") &&
|
||||
json.getString("operation") == "DelMessage";
|
||||
}
|
||||
|
||||
/**
|
||||
* méthode gérant la suppression de message
|
||||
*/
|
||||
override fun processEvent(e: InteropEvent) {
|
||||
|
||||
try {
|
||||
val json = JSONObject(e.message.string);
|
||||
|
||||
//on dispatch au bon channel
|
||||
if(json.getString("channelType") == "Emergency"){
|
||||
this.emergencyController.deleteMessage(json.getString("id"));
|
||||
}else if(json.getString("channelType") == "Event"){
|
||||
this.eventController.deleteMessage(json.getString("id"));
|
||||
}
|
||||
|
||||
|
||||
}catch(e : Exception){
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +1,32 @@
|
|||
package Interop
|
||||
|
||||
import Channels.Emergency
|
||||
import Channels.Event
|
||||
import Collections.Message
|
||||
import org.json.JSONObject
|
||||
import seekdasky.kWebSocket.InteropEvent
|
||||
import seekdasky.kWebSocket.Listeners.InteropListener
|
||||
|
||||
|
||||
/**
|
||||
* Classe Interop permettant de gérer tous les ajouts de message
|
||||
*/
|
||||
class PostMessage : InteropListener {
|
||||
|
||||
//channel emergency
|
||||
val emergencyController : Emergency;
|
||||
//channel event
|
||||
val eventController : Event;
|
||||
|
||||
constructor(emergency : Emergency){
|
||||
|
||||
constructor(emergency : Emergency, event : Event){
|
||||
this.emergencyController = emergency;
|
||||
this.eventController = event;
|
||||
}
|
||||
|
||||
/**
|
||||
* cf: DelMessage
|
||||
*/
|
||||
override fun filter(e: InteropEvent): Boolean {
|
||||
|
||||
val json = JSONObject(e.message.string);
|
||||
|
@ -21,19 +35,51 @@ class PostMessage : InteropListener {
|
|||
json.getString("operation") == "PostMessage";
|
||||
}
|
||||
|
||||
/**
|
||||
* méthode traitant l'ajout de message
|
||||
*/
|
||||
override fun processEvent(e: InteropEvent) {
|
||||
|
||||
try {
|
||||
val json = JSONObject(e.message.string);
|
||||
|
||||
val location = listOf<Float>((json.getJSONArray("location")[0] as Double).toFloat(), (json.getJSONArray("location")[1] as Double).toFloat());
|
||||
//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 lng : Float
|
||||
try {
|
||||
lat = (json.getJSONArray("location")[0] as Double).toFloat();
|
||||
}catch(e : Exception){
|
||||
lat = (json.getJSONArray("location")[0] as Integer).toFloat();
|
||||
}
|
||||
|
||||
this.emergencyController.manuallyDispatchMessage(
|
||||
json.getString("channelName"),
|
||||
json.getString("message"),
|
||||
json.getString("username"),
|
||||
location
|
||||
)
|
||||
try {
|
||||
lng = (json.getJSONArray("location")[1] as Double).toFloat();
|
||||
}catch(e : Exception){
|
||||
lng = (json.getJSONArray("location")[1] as Integer).toFloat();
|
||||
}
|
||||
|
||||
val location = mutableListOf<Float>(lat, lng);
|
||||
|
||||
//on peuple le modèle
|
||||
val message = Message();
|
||||
message.location = location;
|
||||
message.message = json.getString("message");
|
||||
message.user = json.getString("username");
|
||||
message.id = json.getString("id");
|
||||
|
||||
|
||||
if(json.getString("channelType") == "Emergency"){
|
||||
this.emergencyController.manuallyDispatchMessage(
|
||||
json.getString("channelName"),
|
||||
message
|
||||
);
|
||||
}else if(json.getString("channelType") == "Event"){
|
||||
message.type = json.getInt("type");
|
||||
this.eventController.manuallyDispatchMessage(
|
||||
json.getString("channelName"),
|
||||
message
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}catch(e : Exception){
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package Interop
|
||||
|
||||
import Channels.Emergency
|
||||
import Channels.Event
|
||||
import Collections.Message
|
||||
import org.json.JSONObject
|
||||
import seekdasky.kWebSocket.InteropEvent
|
||||
import seekdasky.kWebSocket.Listeners.InteropListener
|
||||
|
||||
/**
|
||||
* Classe Interop permettant de gérer toutes les mise a jour de message
|
||||
*/
|
||||
class UpdMessage : InteropListener {
|
||||
|
||||
val emergencyController : Emergency;
|
||||
val eventController : Event;
|
||||
|
||||
constructor(emergency : Emergency, event : Event){
|
||||
this.emergencyController = emergency;
|
||||
this.eventController = event;
|
||||
}
|
||||
|
||||
override fun filter(e: InteropEvent): Boolean {
|
||||
|
||||
val json = JSONObject(e.message.string);
|
||||
|
||||
return json.has("operation") &&
|
||||
json.getString("operation") == "UpdMessage";
|
||||
}
|
||||
|
||||
override fun processEvent(e: InteropEvent) {
|
||||
|
||||
try {
|
||||
val json = JSONObject(e.message.string);
|
||||
|
||||
|
||||
if(json.getString("channelType") == "Emergency"){
|
||||
|
||||
val message = Message();
|
||||
message.message = json.getString("message");
|
||||
message.id = json.getString("id");
|
||||
|
||||
this.emergencyController.updateMessage(message);
|
||||
}else if(json.getString("channelType") == "Event"){
|
||||
val message = Message();
|
||||
message.message = json.getString("message");
|
||||
message.id = json.getString("id");
|
||||
|
||||
this.eventController.updateMessage(message);
|
||||
}
|
||||
|
||||
}catch(e : Exception){
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
25
src/Main.kt
25
src/Main.kt
|
@ -1,19 +1,38 @@
|
|||
import Channels.Emergency
|
||||
import Channels.Event
|
||||
import Interop.DelMessage
|
||||
import Interop.PostMessage
|
||||
import Interop.UpdMessage
|
||||
import seekdasky.kWebSocket.Server
|
||||
|
||||
|
||||
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);
|
||||
|
||||
//channels WebSocket pilotés par Interop
|
||||
val emergencyChannel = Emergency(server);
|
||||
|
||||
val eventChannel = Event(server);
|
||||
//channel WebSocket
|
||||
server.addListener(Channel(server,"/chat"));
|
||||
//on bind les listeners
|
||||
server.addListener(emergencyChannel);
|
||||
server.addInteropListener(PostMessage(emergencyChannel));
|
||||
server.addListener(eventChannel);
|
||||
//service de login par WebSocket
|
||||
server.addListener(ConnectChannel);
|
||||
|
||||
//Listeners Interops
|
||||
server.addInteropListener(PostMessage(emergencyChannel,eventChannel));
|
||||
server.addInteropListener(DelMessage(emergencyChannel,eventChannel));
|
||||
server.addInteropListener(UpdMessage(emergencyChannel,eventChannel));
|
||||
//login Interop
|
||||
server.addInteropListener(ConnectChannel);
|
||||
|
||||
//et c'est partiiiiiiii
|
||||
server.startServer();
|
||||
|
||||
//méthode bloquant le thread principal tant que le serveur est en vie
|
||||
server.block();
|
||||
}
|
Loading…
Reference in New Issue