1 module server.informer.client; 2 3 import core.thread : Thread; 4 import server.server : BesterServer; 5 import std.socket; 6 import utils.message : receiveMessage, sendMessage; 7 import std.json; 8 import utils.debugging; 9 import std.string; 10 import std.conv : to; 11 import connection.connection : BesterConnection; 12 13 /** 14 * Represents a handler's connection to the 15 * Bester informer socket, runs as a seperate 16 * thread with a read, dispatch, write loop 17 * to handle commands and responses to the handler 18 * from the server. 19 */ 20 public final class BesterInformerClient : Thread 21 { 22 /* The associated `BesterServer` */ 23 private BesterServer server; 24 25 /* The socket to the handler */ 26 private Socket handlerSocket; 27 28 /* If the connection is still active or not */ 29 private bool active = true; 30 31 /** 32 * Constructs a new `BesterInformerClient` with the 33 * associated BesterServer, `server`, and handler 34 * socket, `handlerSocket`. 35 */ 36 this(BesterServer server, Socket handlerSocket) 37 { 38 super(&worker); 39 this.server = server; 40 this.handlerSocket = handlerSocket; 41 } 42 43 /** 44 * Run's the command specified in `commandBlock` and sets the 45 * response in the variable pointed to by `result`. 46 */ 47 private bool runCommand(JSONValue commandBlock, ref JSONValue result) 48 { 49 try 50 { 51 /* Get the command type */ 52 string commandType = commandBlock["type"].str(); 53 debugPrint("CommandType: " ~ commandType); 54 55 /* Check if the command if `listClients` */ 56 if(cmp(commandType, "listClients") == 0) 57 { 58 /* Create a JSON list of strings */ 59 result = listClients(server); 60 } 61 /* Check if the command is `isClient` */ 62 else if(cmp(commandType, "isClient") == 0) 63 { 64 /* The username to match */ 65 string username = commandBlock["data"].str(); 66 result = isClient(server, username); 67 } 68 /* Check if the command is `serverInfo` */ 69 else if(cmp(commandType, "serverInfo") == 0) 70 { 71 result = getServerInfo(server); 72 } 73 /* Check if the command is `quit` */ 74 else if (cmp(commandType, "quit") == 0) 75 { 76 /* Set the connection to inactive */ 77 active = false; 78 result = null; /* TODO: JSOn default value */ 79 } 80 /* If the command is invalid */ 81 else 82 { 83 result = null; 84 return false; 85 } 86 87 debugPrint(result.toPrettyString()); 88 return true; 89 } 90 catch(JSONException e) 91 { 92 return false; 93 } 94 } 95 96 private void worker() 97 { 98 /* TODO: Implement me */ 99 while(active) 100 { 101 /* Receive a message */ 102 JSONValue handlerCommand; 103 receiveMessage(handlerSocket, handlerCommand); 104 105 /* The response to send */ 106 JSONValue handlerResponse; 107 108 /* Respose from `runCommand` */ 109 JSONValue runCommandData; 110 111 /* Attempt to get the JSON */ 112 try 113 { 114 /* Get the command type */ 115 string commandType = handlerCommand["command"]["type"].str(); 116 debugPrint("Command: " ~ commandType); 117 118 /* Dispatch to the correct command and return a status */ 119 bool commandStatus = runCommand(handlerCommand["command"], runCommandData); 120 121 /* Set the data */ 122 handlerResponse["data"] = runCommandData; 123 124 /* Set the `status` field to `"0"` */ 125 handlerResponse["status"] = commandStatus ? "0" : "1"; 126 } 127 catch(JSONException e) 128 { 129 /* Set the `status` field to `"1"` */ 130 handlerResponse["status"] = "1"; 131 } 132 133 /* Send the response to the handler */ 134 sendMessage(handlerSocket, handlerResponse); 135 } 136 137 /* Close the socket */ 138 handlerSocket.close(); 139 } 140 141 /** 142 * Shutdown the informer client. 143 */ 144 public void shutdown() 145 { 146 active = false; 147 } 148 149 /** 150 * This functions returns `string[]` where each element 151 * contains the username of the locally connected client. 152 */ 153 public static string[] listClients(BesterServer server) 154 { 155 string[] clientList; 156 157 for(ulong i = 0; i < server.clients.length; i++) 158 { 159 /* Make sure only to add client connections */ 160 BesterConnection connection = server.clients[i]; 161 if(connection.getType() == BesterConnection.Scope.CLIENT) 162 { 163 clientList ~= [connection.getCredentials()[0]]; 164 } 165 } 166 167 return clientList; 168 } 169 170 /** 171 * This function returns `true` if the provided username 172 * matches a locally connected client, `false` otherwise. 173 */ 174 public static bool isClient(BesterServer server, string username) 175 { 176 for(ulong i = 0; i < server.clients.length; i++) 177 { 178 /* Make sure only to match client connections */ 179 BesterConnection connection = server.clients[i]; 180 if(connection.getType() == BesterConnection.Scope.CLIENT && cmp(connection.getCredentials[0], username)) 181 { 182 return true; 183 } 184 } 185 186 return false; 187 } 188 189 /** 190 * This function returns server information. 191 */ 192 public static JSONValue getServerInfo(BesterServer server) 193 { 194 /* Server information */ 195 JSONValue serverInfo; 196 197 /* Create the `listeners` block */ 198 JSONValue listenersBlock; 199 200 for(ulong i = 0; i < server.listeners.length; i++) 201 { 202 JSONValue listener; 203 listener["address"] = server.listeners[i].toString(); 204 listenersBlock["listener"~to!(string)(i)] = listener; 205 } 206 207 208 /* TODO: Load additional information from `server.conf`'s `admin[info]` block */ 209 210 /* TODO: Use as is number, no string */ 211 serverInfo["clientCount"] = to!(string)(server.clients.length); 212 serverInfo["adminInfo"] = server.getAdminInfo(); 213 serverInfo["listeners"] = listenersBlock; 214 215 return serverInfo; 216 } 217 }