1 /**
2 * DServer
3 *
4 * Represents a server instance.
5 *
6 * Holds a list of DConnections,
7 * configuration parameters and
8 * more.
9 */
10 
11 module dnetd.dserver;
12 
13 import core.thread : Thread;
14 import std.socket : Address, Socket, AddressFamily, SocketType, ProtocolType;
15 import dnetd.dconnection;
16 import dnetd.dchannel;
17 import std.string : cmp;
18 import core.sync.mutex : Mutex;
19 import std.stdio;
20 import std.conv : to;
21 import dnetd.dconfig;
22 import dnetd.dlink;
23 import dnetd.dlistener;
24 import gogga;
25 
26 public class DServer : Thread
27 {
28 	/* Server configuration */
29 	private DConfig config;
30 
31 	/**
32 	* Connection queue
33 	*/
34 	private DConnection[] connectionQueue;
35 	private Mutex connectionLock;
36 
37 	/**
38 	* Channels
39 	*/
40 	private DChannel[] channels;
41 	private Mutex channelLock;
42 
43 	/**
44 	* Meyer linking subsystem
45 	*/
46 	private DMeyer meyerSS;
47 	
48 	/**
49 	* The listeners attached to this server
50 	*/
51 	private DListener[] listeners;
52 
53 	/* TODO: Implement new constructor */
54 	this(DConfig config)
55 	{
56 		/* Set the function to be called on thread start */
57 		super(&startListeners);
58 
59 		/* Set the server's config */
60 		this.config = config;
61 	
62 		/* Construct the listeners */
63 		initListeners(config.getGeneral().getAddresses());
64 
65 		/* Initialize the server */
66 		init();
67 
68 		/* Start the server */
69 		startServer();
70 	}
71 
72 	/**
73 	* Given an array of Address(es) this will construct all
74 	* the corresponding listsners (DListener) and append them
75 	* to the array
76 	*/
77 	private void initListeners(Address[] listenAddresses)
78 	{
79 		gprintln("Constructing "~to!(string)(listenAddresses.length)~" listsners...");
80 
81 		foreach(Address listenAddress; listenAddresses)
82 		{
83 			gprintln("Constructing listener for address '"~to!(string)(listenAddress)~"'");
84 
85 			import std.socket : AddressInfo;
86 			AddressInfo addrInfo;
87 
88 			/* Set the address (and port) to the current one along with address family */
89 			addrInfo.address = listenAddress;
90 			addrInfo.family = listenAddress.addressFamily;
91 		
92 			/* Set standard (it will always be TCP and in stream access mode) */
93 			addrInfo.protocol = ProtocolType.TCP;
94 			addrInfo.type = SocketType.STREAM;
95 
96 			/* Construct the listener */
97 			listeners ~= new DListener(this, addrInfo);
98 			gprintln("Listener for '"~to!(string)(listenAddress)~"' constructed");
99 		}
100 
101 		gprintln("Listener construction complete.");
102 	}
103 
104 	/**
105 	* Starts all the listeners
106 	*/
107 	private void startListeners()
108 	{
109 		foreach(DListener listener; listeners)
110 		{
111 			/* Start the listener */
112 			gprintln("Starting listener "~to!(string)(listener)~"...");
113 			listener.start();
114 		}
115 	}
116 
117 	public DConfig getConfig()
118 	{
119 		return config;
120 	}
121 
122 	private void init()
123 	{
124 		/* Setup queues */
125 		initQueues();
126 
127 		/* Setup locks */
128 		initLocks();
129 	}
130 
131 	/**
132 	* Creates all needed queues
133 	* and their mutexes
134 	*/
135 	private void initQueues()
136 	{
137 		/* TODO: Implement me */
138 	}
139 
140 	private void initLocks()
141 	{
142 		/* Initialize the connection lock */
143 		connectionLock = new Mutex();
144 
145 		/* Initialize the channel lock */
146 		channelLock = new Mutex();
147 	}
148 	
149 	public DMeyer getMeyer()
150 	{
151 		return meyerSS;
152 	}
153 
154 	private void startServer()
155 	{
156 		/* Initialize the Meyer linking sub-system */
157 		meyerSS = new DMeyer(this);
158 
159 		/* Start the listener starter */
160 		start();
161 	}
162 
163 	public void addChannel(DConnection causer, DChannel channel)
164 	{
165 		/* Lock the channels list */
166 		// channelLock.lock();
167 
168 		channels ~= channel;
169 
170 		/* TODO: Use causer */
171 
172 		/* Unlock the channels list */
173 		// channelLock.unlock();
174 	}
175 
176 	public void addConnection(DConnection connection)
177 	{
178 		/* Lock the connections list */
179 		connectionLock.lock();
180 
181 		/* Add to the connection queue */
182 		connectionQueue ~= connection;
183 		gprintln("Added connection to queue "~to!(string)(connection));
184 
185 		/* Unlock the connections list */
186 		connectionLock.unlock();
187 	}
188 
189 	/* TODO Remove connection */
190 	public void removeConnection(DConnection connection)
191 	{
192 		/* Lock the connections list */
193 		connectionLock.lock();
194 
195 		/* The new connection queue */
196 		DConnection[] connectionQueueNew;
197 
198 		foreach(DConnection currentConnection; connectionQueue)
199 		{
200 			if(!(currentConnection is connection))
201 			{
202 				connectionQueueNew ~= currentConnection;
203 			}
204 		}
205 
206 		/* Set this as the new queue */
207 		connectionQueue = connectionQueueNew;
208 
209 		gprintln("Removed connection from queue "~to!(string)(connection));
210 
211 		/* Unlock the connections list */
212 		connectionLock.unlock();
213 	}
214 
215 	/* TODO: neew method */
216 	public DChannel getChannel(DConnection causer, string channelName)
217 	{
218 		DChannel channel = null;
219 		
220 		channelLock.lock();
221 
222 		
223 		foreach(DChannel currentChannel; channels)
224 		{
225 			if(cmp(currentChannel.getName(), channelName) == 0)
226 			{
227 				channel = currentChannel;
228 				break;
229 			}
230 		}
231 
232 		if(channel)
233 		{
234 			
235 		}
236 		else
237 		{
238 			channel = new DChannel(channelName);
239 								
240 								this.addChannel(causer, channel);
241 		}
242 
243 		channelLock.unlock();
244 
245 
246 		return channel;
247 	}
248 
249 
250 	public DChannel getChannelByName(string channelName)
251 	{
252 		/* The channel */
253 		DChannel channel = null;
254 		
255 		/* Lock the channels list */
256 		channelLock.lock();
257 
258 		foreach(DChannel currentChannel; channels)
259 		{
260 			if(cmp(currentChannel.getName(), channelName) == 0)
261 			{
262 				channel = currentChannel;
263 				break;
264 			}
265 		}
266 
267 		/* Unlock the channels list */
268 		channelLock.unlock();
269 
270 		return channel;
271 	}
272 
273 	/**
274 	* Returns the DConnection with the matching
275 	* username, null if not found
276 	*/
277 	public DConnection findUser(string username)
278 	{
279 		/* The found Connection */
280 		DConnection foundConnection;
281 
282 		/* Lock the connections list */
283 		connectionLock.lock();
284 
285 		/* Find the user with the matching user name */
286 		foreach(DConnection connection; connectionQueue)
287 		{
288 			/* The connection must be a user (not unspec or server) */
289 			if(connection.getConnectionType() == DConnection.ConnectionType.CLIENT)
290 			{
291 				/* Match the username */
292 				if(cmp(connection.getUsername(), username) == 0)
293 				{
294 					foundConnection = connection;
295 				}
296 			}
297 		}
298 
299 		/* Unlock the connections list */
300 		connectionLock.unlock();
301 
302 		return foundConnection;
303 	}
304 
305 	public bool channelExists(string channelName)
306 	{
307 		/* Whether or not it exists */
308 		bool exists;
309 
310 		/* Get all channels */
311 		DChannel[] currentChannels = getChannels();
312 
313 		foreach(DChannel currentChannel; currentChannels)
314 		{
315 			if(cmp(currentChannel.getName(), channelName) == 0)
316 			{
317 				exists = true;
318 				break;
319 			}
320 		}
321 
322 		return exists;
323 	}
324 
325 	public DChannel[] getChannels()
326 	{
327 		/* The current channels list */
328 		DChannel[] currentChannels;
329 		
330 		/* Lock the channels list */
331 		channelLock.lock();
332 
333 		currentChannels = channels;
334 
335 		/* Unlock the channels list */
336 		channelLock.unlock();
337 		
338 		return currentChannels;
339 	}
340 
341 	public string getStatusMessage(string username)
342 	{
343 		/* Lock the connections list */
344 		connectionLock.lock();
345 
346 		/* The matching connection */
347 		DConnection matchedConnection;
348 
349 		/* Find the connection */
350 		foreach(DConnection connection; connectionQueue)
351 		{
352 			if(cmp(connection.getUsername(), username) == 0)
353 			{
354 				matchedConnection = connection;
355 				break;
356 			}
357 		}
358 
359 
360 		/* Unlock the connections list */
361 		connectionLock.unlock();
362 
363 		return matchedConnection.getStatusMessage(username);
364 	}
365 
366 	/**
367 	* Checks whether the given user has the given
368 	* property
369 	*/
370 	public bool isProperty(string username, string propertyName)
371 	{
372 		/* Whether or not the user has the given property */
373 		bool status;
374 
375 		/* Lock the connections list */
376 		connectionLock.lock();
377 
378 		/* The matching connection */
379 		DConnection matchedConnection;
380 
381 		/* Find the connection */
382 		foreach(DConnection connection; connectionQueue)
383 		{
384 			if(cmp(connection.getUsername(), username) == 0)
385 			{
386 				matchedConnection = connection;
387 				break;
388 			}
389 		}
390 
391 		/* Unlock the connections list */
392 		connectionLock.unlock();
393 
394 		/* Check for the user's property */
395 		status = matchedConnection.isProperty(propertyName);
396 
397 		return status;
398 	}
399 
400 	/* TODO: All these functions can really be re-duced, why am I not using getConnection() */
401 
402 	/**
403 	* Checks whether the given user has the given
404 	* property
405 	*/
406 	public string getProperty(string username, string propertyName)
407 	{
408 		/* The retrieved property value */
409 		string propertyValue;
410 
411 		/* Lock the connections list */
412 		connectionLock.lock();
413 
414 		/* The matching connection */
415 		DConnection matchedConnection;
416 
417 		/* Find the connection */
418 		foreach(DConnection connection; connectionQueue)
419 		{
420 			if(cmp(connection.getUsername(), username) == 0)
421 			{
422 				matchedConnection = connection;
423 				break;
424 			}
425 		}
426 
427 		/* Unlock the connections list */
428 		connectionLock.unlock();
429 
430 		/* Check for the user's property */
431 		propertyValue = matchedConnection.getProperty(propertyName);
432 
433 		return propertyValue;
434 	}
435 
436 	/**
437 	* Set the property of the given user to the given value
438 	*/
439 	public void setProperty(string username, string propertyName, string propertyValue)
440 	{
441 		/* Lock the connections list */
442 		connectionLock.lock();
443 
444 		/* The matching connection */
445 		DConnection matchedConnection;
446 
447 		/* Find the connection */
448 		foreach(DConnection connection; connectionQueue)
449 		{
450 			if(cmp(connection.getUsername(), username) == 0)
451 			{
452 				matchedConnection = connection;
453 				break;
454 			}
455 		}
456 
457 		/* Unlock the connections list */
458 		connectionLock.unlock();
459 
460 		/* Set the property's value of the user */
461 		matchedConnection.setProperty(propertyName, propertyValue);
462 	}
463 	
464 
465 	public string getServerInfo()
466 	{
467 		/* The server information */
468 		string serverInfo;
469 
470 		/* TODO: Fetch serverName */
471 		/* TODO: Fetch networkName */
472 		/* TODO: Fetch userCount */
473 		/* TODO: Fetch channelCount */
474 
475 
476 		return serverInfo;
477 	}
478 }