diff --git a/src/Channel.kt b/src/Channel.kt index 1582d7d..271d730 100644 --- a/src/Channel.kt +++ b/src/Channel.kt @@ -1,4 +1,4 @@ -import kotlinx.coroutines.experimental.async +import Collections.Stack import org.json.JSONArray import org.json.JSONObject import seekdasky.kWebSocket.Client @@ -6,13 +6,12 @@ import seekdasky.kWebSocket.Event import seekdasky.kWebSocket.Listeners.AsynchronousListener import seekdasky.kWebSocket.Message.*; import seekdasky.kWebSocket.Server -import kotlin.test.currentStackTrace class Channel : AsynchronousListener { val channelName : String; var clients : MutableList; - val messages : Stack>; + val messages : Stack>; val serv : Server; constructor(serv : Server , chanName : String){ diff --git a/src/Channels/Emergency.kt b/src/Channels/Emergency.kt new file mode 100644 index 0000000..edc4baa --- /dev/null +++ b/src/Channels/Emergency.kt @@ -0,0 +1,219 @@ +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 { + + var clients : MutableList; + val messages : MutableMap>>; + val serv : Server; + + 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){ + 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(); + } + } +} \ No newline at end of file diff --git a/src/Collections/Message.kt b/src/Collections/Message.kt new file mode 100644 index 0000000..4f4ecef --- /dev/null +++ b/src/Collections/Message.kt @@ -0,0 +1,27 @@ +package Collections + +import org.json.JSONObject +import java.lang.System; + +class Message { + + var user = ""; + var message = ""; + var timestamp = 0L; + var location = mutableListOf(); + + + constructor(){ + this.timestamp = System.currentTimeMillis() / 1000; + } + + fun toJSON() : JSONObject{ + val json = JSONObject(); + json.put("user",user); + json.put("message",message); + json.put("timestamp",timestamp); + json.put("location",location); + + return json; + } +} \ No newline at end of file diff --git a/src/Stack.kt b/src/Collections/Stack.kt similarity index 97% rename from src/Stack.kt rename to src/Collections/Stack.kt index 502cb15..9035eb0 100644 --- a/src/Stack.kt +++ b/src/Collections/Stack.kt @@ -1,3 +1,5 @@ +package Collections + class Stack(size : Int){ var itCounter = 0; diff --git a/src/ConnectChannel.kt b/src/ConnectChannel.kt index dc8181d..345123f 100644 --- a/src/ConnectChannel.kt +++ b/src/ConnectChannel.kt @@ -1,3 +1,4 @@ +import Collections.Stack import org.json.JSONObject import seekdasky.UTF8Encoding.encode import seekdasky.kWebSocket.Client @@ -9,14 +10,17 @@ object ConnectChannel : InteropListener { private var lastGuestIndex = 0; - private val notBoundYet = Stack>(500); + private val notBoundYet = Stack>(500); private val connected = mutableMapOf(); override fun filter(e: InteropEvent): Boolean { try{ val json = JSONObject(e.message.string); - return json.has("type") && json.has("name"); + return json.has("operation") && + json.getString("operation") == "Connect" && + json.has("type") && + json.has("name"); }catch (e : Exception){ return false } diff --git a/src/Interop/PostMessage.kt b/src/Interop/PostMessage.kt new file mode 100644 index 0000000..4e8fd15 --- /dev/null +++ b/src/Interop/PostMessage.kt @@ -0,0 +1,43 @@ +package Interop + +import Channels.Emergency +import org.json.JSONObject +import seekdasky.kWebSocket.InteropEvent +import seekdasky.kWebSocket.Listeners.InteropListener + +class PostMessage : InteropListener { + + val emergencyController : Emergency; + + constructor(emergency : Emergency){ + this.emergencyController = emergency; + } + + override fun filter(e: InteropEvent): Boolean { + + val json = JSONObject(e.message.string); + + return json.has("operation") && + json.getString("operation") == "PostMessage"; + } + + override fun processEvent(e: InteropEvent) { + + try { + val json = JSONObject(e.message.string); + + val location = listOf((json.getJSONArray("location")[0] as Double).toFloat(), (json.getJSONArray("location")[1] as Double).toFloat()); + + this.emergencyController.manuallyDispatchMessage( + json.getString("channelName"), + json.getString("message"), + json.getString("username"), + location + ) + + + }catch(e : Exception){ + e.printStackTrace() + } + } +} \ No newline at end of file diff --git a/src/Main.kt b/src/Main.kt index 2c4ad3e..4ffe45f 100644 --- a/src/Main.kt +++ b/src/Main.kt @@ -1,3 +1,5 @@ +import Channels.Emergency +import Interop.PostMessage import seekdasky.kWebSocket.Server @@ -5,7 +7,11 @@ fun main(args: Array){ val server = Server("0.0.0.0",9999,null); server.startInteropServer("localhost",9998); + val emergencyChannel = Emergency(server); + server.addListener(Channel(server,"/chat")); + server.addListener(emergencyChannel); + server.addInteropListener(PostMessage(emergencyChannel)); server.addInteropListener(ConnectChannel); server.startServer();