1 module libdnet.dclient;
2 
3 import tristanable.manager : Manager;
4 import tristanable.queue : Queue;
5 import tristanable.encoding : DataMessage;
6 import tristanable.queueitem : QueueItem;
7 import std.socket;
8 import std.stdio;
9 import std.conv : to;
10 import std.string : split;
11 import bmessage : bSendMessage = sendMessage;
12 
13 public final class DClient
14 {
15 	/**
16 	* tristanabale tag manager
17 	*/
18 	private Manager manager;
19 	private Socket socket;
20 
21 	/* Create a queue for normal traffic (request-reply on tag: 0) */
22 	private	Queue reqRepQueue;
23 
24 	/* Create a queue for notifications (replies-only on tag: 1) */
25 	private Queue notificationQueue;
26 
27 	/**
28 	* Constructs a new DClient and connects
29 	* it to the given endpoint Address
30 	*
31 	* @param address the endpoint (server) to
32 	* connect to
33 	*/
34 	this(Address address)
35 	{
36 		/* Initialize the socket */
37 		/* TODO: Error handling */
38 		socket = new Socket(address.addressFamily, SocketType.STREAM, ProtocolType.TCP);
39 		socket.connect(address);
40 		
41 		/* Initialize tristanable */
42 		initTristanable(socket);
43 	}
44 
45 	/* TODO: Create a new queue actually for each command (this is for performance) - as we will have server spawn workers */
46 
47 	private void initTristanable(Socket socket)
48 	{
49 		/* Initialize the manager */
50 		manager = new Manager(socket);
51 
52 		/* Create a queue for normal traffic (request-reply on tag: 1) */
53 		reqRepQueue = new Queue(1);
54 
55 		/* Create a queue for notifications (replies-only on tag: 0) */
56 		notificationQueue = new Queue(0);
57 
58 		/* Add these queues to the tracker */
59 		manager.addQueue(reqRepQueue);
60 		manager.addQueue(notificationQueue);
61 	}
62 
63 	/**
64 	* Receives the head of the notification queue
65 	*/
66 	public byte[] awaitNotification()
67 	{
68 		/* The received notification */
69 		byte[] notification;
70 
71 		/* Await the notification */
72 		QueueItem queueItem = notificationQueue.dequeue();
73 
74 		/* Grab the notification's data */
75 		notification = queueItem.getData();
76 
77 		return notification;
78 	}
79 
80 	/**
81 	* Authenticates as a client with the server
82 	*
83 	* @param username the username to use
84 	* @param password the password to use
85 	* @returns bool true on successful authentication,
86 	* false otherwise
87 	*/
88 	public bool auth(string username, string password)
89 	{
90 		/* The protocol data to send */
91 		byte[] data = [0];
92 		data ~= cast(byte)username.length;
93 		data ~= username;
94 		data ~= password;
95 
96 		/* Send the protocol data */
97 		DataMessage protocolData = new DataMessage(reqRepQueue.getTag(), data);
98 		bSendMessage(socket, protocolData.encode());
99 
100 		/* Receive the server's response */
101 		byte[] resp = reqRepQueue.dequeue().getData();
102 
103 		return cast(bool)resp[0];
104 	}
105 
106 
107 	/**
108 	* Joins the given channel
109 	*
110 	* @param channel the channel to join
111 	* @returns bool true if the join was
112 	* successful, false otherwise
113 	*/
114 	public bool join(string channel)
115 	{
116 		/* TODO: DO oneshot as protocol supports csv channels */
117 
118 		/* The protocol data to send */
119 		byte[] data = [3];
120 		data ~= channel;
121 
122 		/* Send the protocol data */
123 		DataMessage protocolData = new DataMessage(reqRepQueue.getTag(), data);
124 		bSendMessage(socket, protocolData.encode());
125 
126 		/* Receive the server's response */
127 		byte[] resp = reqRepQueue.dequeue().getData();
128 
129 		return cast(bool)resp[0];
130 	}
131 
132 	/**
133 	* Set your status
134 	*
135 	*
136 	*/
137 	public void setStatus(string status)
138 	{
139 		/* The protocol data to send */
140 		byte[] data = [13];
141 		data ~= status;
142 
143 		/* Send the protocol data */
144 		DataMessage protocolData = new DataMessage(reqRepQueue.getTag(), data);
145 		bSendMessage(socket, protocolData.encode());
146 
147 		/* Receive the server's response */
148 		byte[] resp = reqRepQueue.dequeue().getData();
149 
150 		// return cast(bool)resp[0];
151 	}
152 
153 	/**
154 	* Get the memberinfo of a given user
155 	*
156 	* TODO: Return more thna just status
157 	*/
158 	public string getMemberInfo(string user)
159 	{
160 		/* The protocol data to send */
161 		byte[] data = [12];
162 		data ~= user;
163 
164 		/* Send the protocol data */
165 		DataMessage protocolData = new DataMessage(reqRepQueue.getTag(), data);
166 		bSendMessage(socket, protocolData.encode());
167 
168 		/* Receive the server's response */
169 		byte[] resp = reqRepQueue.dequeue().getData();
170 
171 
172 		string status;
173 
174 		/* If it worked */
175 		if(cast(bool)resp[0])
176 		{
177 			/* The member info */
178 			ubyte len1 = resp[1];
179 			ubyte len2 = resp[1+len1+1];
180 			status = cast(string)resp[1+len1+1+len2+1..resp.length];
181 		}
182 		else
183 		{
184 			/* TODO: Handle error */
185 		}
186 
187 		return status;
188 	}
189 
190 
191 	/**
192 	* Lists all properties of the given user
193 	*/
194 	public string[] getProperties(string user)
195 	{
196 		/* The protocol data to send */
197 		byte[] data = [15];
198 		data ~= user;
199 
200 		/* Send the protocol data */
201 		DataMessage protocolData = new DataMessage(reqRepQueue.getTag(), data);
202 		bSendMessage(socket, protocolData.encode());
203 
204 		/* Receive the server's response */
205 		byte[] resp = reqRepQueue.dequeue().getData();
206 
207 		/* Received list of properties */
208 		string[] properties;
209 
210 		/* If it worked */
211 		if(cast(bool)resp[0])
212 		{
213 			/* Get the property line */
214 			string propertyLine = cast(string)resp[1..resp.length];
215 
216 			properties = split(propertyLine, ",");
217 		}
218 
219 		return properties;
220 	}
221 
222 	/**
223 	* Get a property's value
224 	*/
225 	public string getProperty(string user, string property)
226 	{
227 		/* The property's value */
228 		string propertyValue;
229 
230 		/* The protocol data to send */
231 		byte[] data = [16];
232 		data ~= user~","~property;
233 
234 		/* Send the protocol data */
235 		DataMessage protocolData = new DataMessage(reqRepQueue.getTag(), data);
236 		bSendMessage(socket, protocolData.encode());
237 
238 		/* Receive the server's response */
239 		byte[] resp = reqRepQueue.dequeue().getData();
240 
241 		/* If it worked */
242 		if(cast(bool)resp[0])
243 		{
244 			/* Get the property line */
245 			propertyValue = cast(string)resp[1..resp.length];
246 		}
247 
248 		return propertyValue;
249 	}
250 
251 	/**
252 	* Check's whether the user has the given property
253 	*/
254 	public bool isProperty(string user, string property)
255 	{
256 		/* The property's value */
257 		bool status;
258 
259 		/* The protocol data to send */
260 		byte[] data = [19];
261 		data ~= user~","~property;
262 
263 		/* Send the protocol data */
264 		DataMessage protocolData = new DataMessage(reqRepQueue.getTag(), data);
265 		bSendMessage(socket, protocolData.encode());
266 
267 		/* Receive the server's response */
268 		byte[] resp = reqRepQueue.dequeue().getData();
269 
270 		/* If it worked */
271 		if(cast(bool)resp[0])
272 		{
273 			/* Get the property line */
274 			status = cast(bool)resp[1];
275 		}
276 
277 		return status;
278 	}
279 
280 	/**
281 	* Set's the given property of yourself to the given value
282 	*/
283 	public void setProperty(string property, string propertyValue)
284 	{
285 		/* The property's value */
286 		bool status;
287 
288 		/* The protocol data to send */
289 		byte[] data = [17];
290 		data ~= property~","~propertyValue;
291 
292 		/* Send the protocol data */
293 		DataMessage protocolData = new DataMessage(reqRepQueue.getTag(), data);
294 		bSendMessage(socket, protocolData.encode());
295 
296 		/* Receive the server's response */
297 		byte[] resp = reqRepQueue.dequeue().getData();
298 
299 		/* If it worked */
300 		if(cast(bool)resp[0])
301 		{
302 			
303 		}
304 	}
305 
306 	/**
307 	* Delete's the given property of yourself
308 	*/
309 	public void deleteProperty(string property)
310 	{
311 		/* The property's value */
312 		bool status;
313 
314 		/* The protocol data to send */
315 		byte[] data = [18];
316 		data ~= property;
317 
318 		/* Send the protocol data */
319 		DataMessage protocolData = new DataMessage(reqRepQueue.getTag(), data);
320 		bSendMessage(socket, protocolData.encode());
321 
322 		/* Receive the server's response */
323 		byte[] resp = reqRepQueue.dequeue().getData();
324 
325 		/* If it worked */
326 		if(cast(bool)resp[0])
327 		{
328 			
329 		}
330 	}
331 
332 	
333 
334 
335 	/**
336 	* Lists all the channels on the server
337 	*
338 	* @returns string[] the list of channels
339 	*/
340 	public string[] list()
341 	{
342 		/* List of channels */
343 		string[] channels;
344 
345 		/* The protocol data to send */
346 		byte[] data = [6];
347 
348 		/* Send the protocol data */
349 		DataMessage protocolDataMsg = new DataMessage(reqRepQueue.getTag(), data);
350 		bSendMessage(socket, protocolDataMsg.encode());
351 
352 		/* Receive the server's response */
353 		byte[] resp = reqRepQueue.dequeue().getData();
354 
355 		/* Only generate a list if command was successful */
356 		if(resp[0])
357 		{
358 			/* Generate the channel list */
359 			string channelList = cast(string)resp[1..resp.length];
360 			channels = split(channelList, ",");
361 		}
362 
363 		return channels;
364 	}
365 
366 	public Manager getManager()
367 	{
368 		return manager;
369 	}
370 
371 	/**
372 	* Sends a message to either a channel of user
373 	*
374 	* @param isUser whether or not we are sending to
375 	* a user, true if user, false if channel
376 	* @param location the username/channel to send to
377 	* @param message the message to send
378 	* @returns bool whether the send worked or not
379 	*/
380 	public bool sendMessage(bool isUser, string location, string message)
381 	{
382 		/* The protocol data to send */
383 		byte[] protocolData = [5];
384 
385 		/**
386 		* If we are sending to a user then the
387 		* type field is 0, however if to a channel
388 		* then it is one
389 		*
390 		* Here we encode that
391 		*/
392 		protocolData ~= [!isUser];
393 
394 		/* Encode the length of `location` */
395 		protocolData ~= [cast(byte)location.length];
396 
397 		/* Encode the user/channel name */
398 		protocolData ~= cast(byte[])location;
399 
400 		/* Encode the message */
401 		protocolData ~= cast(byte[])message;
402 
403 		/* Send the protocol data */
404 		DataMessage protocolDataMsg = new DataMessage(reqRepQueue.getTag(), protocolData);
405 		bSendMessage(socket, protocolDataMsg.encode());
406 
407 		/* Receive the server's response */
408 		byte[] resp = reqRepQueue.dequeue().getData();
409 
410 		return cast(bool)resp[0];
411 	}
412 
413 	/**
414 	* Returns the list of members in the
415 	* given channel
416 	*/
417 	public string[] getMembers(string channel)
418 	{
419 		/* The list of members */
420 		string[] members;
421 
422 		/* The protocol data to send */
423 		byte[] protocolData = [9];
424 
425 		/**
426 		* Encode the channel name
427 		*/
428 		protocolData ~= cast(byte[])channel;
429 
430 		/* Send the protocol data */
431 		DataMessage protocolDataMsg = new DataMessage(reqRepQueue.getTag(), protocolData);
432 		bSendMessage(socket, protocolDataMsg.encode());
433 
434 		/* Receive the server's response */
435 		byte[] resp = reqRepQueue.dequeue().getData();
436 
437 		/* If the operation completed successfully */
438 		if(resp[0])
439 		{
440 			string memberList = cast(string)resp[1..resp.length];
441 			members = split(memberList, ",");
442 		}
443 		/* If there was an error */
444 		else
445 		{
446 			/* TODO: Error handling */
447 		}
448 
449 		return members;
450 	}
451 
452 	/**
453 	* Returns the count of members in the
454 	* given channel
455 	*/
456 	public ulong getMemberCount(string channelName)
457 	{
458 		/* The member count */
459 		long memberCount;
460 	
461 		/* The protocol data to send */
462 		byte[] protocolData = [8];
463 
464 		/* Encode the channel name */
465 		protocolData ~= cast(byte[])channelName;
466 
467 		/* Send the protocol data */
468 		DataMessage protocolDataMsg = new DataMessage(reqRepQueue.getTag(), protocolData);
469 		bSendMessage(socket, protocolDataMsg.encode());
470 
471 		/* Receive the server's response */
472 		byte[] resp = reqRepQueue.dequeue().getData();
473 
474 		/* Check if the operation completed successfully */
475 		if(resp[0])
476 		{
477 			/* Length as byte array */
478 			byte[] numberBytes;
479 			numberBytes.length = 8;
480 
481 			/* As Skippy would say, this is jank, but I literay am so lazy now hehe */
482 			memberCount = *cast(long*)resp[1..resp.length].ptr;
483 
484 			/* Decode the length (Big Endian) to Little Endian */
485 			numberBytes[0] = *((cast(byte*)&memberCount)+7);
486 			numberBytes[1] = *((cast(byte*)&memberCount)+6);
487 			numberBytes[2] = *((cast(byte*)&memberCount)+5);
488 			numberBytes[3] = *((cast(byte*)&memberCount)+4);
489 			numberBytes[4] = *((cast(byte*)&memberCount)+3);
490 			numberBytes[5] = *((cast(byte*)&memberCount)+2);
491 			numberBytes[6] = *((cast(byte*)&memberCount)+1);
492 			numberBytes[7] = *((cast(byte*)&memberCount)+0);
493 
494 			memberCount = *cast(long*)numberBytes.ptr;
495 			
496 		}
497 		else
498 		{
499 			/* TODO: Error handling */
500 		}
501 
502 		return memberCount;
503 	}
504 
505 	public string getMotd()
506 	{
507 		/* The message of the day */
508 		string motd;
509 
510 		/* The protocol data to send */
511 		byte[] protocolData = [11];
512 
513 		/* Send the protocol data */
514 		DataMessage protocolDataMsg = new DataMessage(reqRepQueue.getTag(), protocolData);
515 		bSendMessage(socket, protocolDataMsg.encode());
516 
517 		/* Receive the server's response */
518 		byte[] resp = reqRepQueue.dequeue().getData();
519 
520 		/* Check if the operation completed successfully */
521 		if(resp[0])
522 		{
523 			/* Set the message of the day */
524 			motd = cast(string)resp[1..resp.length];
525 		}
526 		else
527 		{
528 			/* TODO: Error handling */
529 		}
530 
531 		return motd;
532 	}
533 
534 	/**
535 	* Disconnect from the server
536 	*
537 	* TODO: This is still a work in progress
538 	* due to tristanable's disconnect still
539 	* being a work in progress
540 	*/
541 	public void close()
542 	{
543 		/* FIXME: I must fix manager for this, the socket stays active and hangs the .join */
544 		/* FIXME (above): due to it being blocking, although I did think I closed the socket */
545 		/* TODO: Not the above, it's actually garbage collector */
546 		//manager.stopManager();	
547 		writeln("manager stopped");
548 	}
549 }