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 }