1 /**
2 * DChannel
3 *
4 * Represents a channel and its
5 * associated information such
6 * as its name, topic, members
7 */
8 
9 module dnetd.dchannel;
10 
11 import dnetd.dconnection : DConnection;
12 import core.sync.mutex : Mutex;
13 import std.conv : to;
14 import std.stdio : writeln;
15 
16 public class DChannel
17 {
18 	/**
19 	* Channel information
20 	*/
21 	private string name;
22 	//private string topic;
23 
24 	/**
25 	* Users in this channel
26 	*/
27 	private DConnection[] members;
28 	private Mutex memberLock;
29 
30 	this(string name)
31 	{
32 		/* Initialize the lock */
33 		memberLock = new Mutex();
34 
35 		this.name = name;
36 	}
37 
38 	public string getName()
39 	{
40 		return name;
41 	}
42 
43 	/**
44 	* Joins the given client to this channel
45 	*/
46 	public bool join(DConnection client)
47 	{
48 		/* Send a message stating the user has joined (TODO: This should be done later, possibly, how defensive should we program) */
49 		broadcastJoin(client);
50 
51 		/* Lock the members list */
52 		writeln("join: mutex lock (about to call)");
53 		memberLock.lock();
54 		writeln("join: mutex lock (completed)");
55 
56 		/**
57 		* Don't allow the user to join a channel he
58 		* is already in
59 		*/
60 		bool isPresent = false;
61 		
62 		foreach(DConnection member; members)
63 		{
64 			if(client is member)
65 			{
66 				isPresent = true;
67 				break;
68 			}
69 		}
70 
71 		/**
72 		* TODO: Error handling if the calling DConnection fails midway 
73 		* and doesn't unlock it
74 		*/
75 
76 		/* Only join channel if not already joined */
77 		if(!isPresent)
78 		{
79 			/* Add the client */
80 			members ~= client;			
81 		}
82 
83 		/* Unlock the members list */
84 		writeln("join: mutex unlock (about to call)");
85 		memberLock.unlock();
86 		writeln("join: mutex unlock (completed)");
87 
88 		return isPresent;
89 	}
90 
91 	/**
92 	* Returns the number of members in this channel
93 	*/
94 	public ulong getMemberCount()
95 	{
96 		/* The count of members */
97 		ulong memberCount;
98 		
99 		/* Lock the members list */
100 		memberLock.lock();
101 
102 		/* Get the member count */
103 		memberCount = members.length;
104 
105 		/* Unlock the members list */
106 		memberLock.unlock();
107 
108 		return memberCount;
109 	}
110 
111 	public bool isMember(DConnection client)
112 	{
113 		/* Whether or not you are a member */
114 		bool isMem;
115 
116 		/* Lock the members list */
117 		memberLock.lock();
118 
119 		/* CHeck if you are in this channel */
120 		foreach(DConnection member; members)
121 		{
122 			if(member is client)
123 			{
124 				isMem = true;
125 				break;
126 			}
127 		}
128 
129 		/* Unlock the members list */
130 		memberLock.unlock();
131 
132 		return isMem;
133 	}
134 
135 	/**
136 	* Removes the given client from this channel
137 	*/
138 	public void leave(DConnection client)
139 	{
140 		/* Lock the members list */
141 		memberLock.lock();
142 
143 		/* TODO: Get a better implementation */
144 
145 		/* Create a new list without the `client` */
146 		DConnection[] newMembers;
147 		foreach(DConnection currentMember; members)
148 		{
149 			if(!(currentMember is client))
150 			{
151 				newMembers ~= currentMember;	
152 			}
153 		}
154 
155 		/* Set it as the new list */
156 		members = newMembers;
157 
158 		/* Unlock the members list */
159 		memberLock.unlock();
160 
161 		/* Send broadcast leave message */
162 		broadcastLeave(client);
163 	}
164 
165 	/**
166 	* Sends a message to all users of this
167 	* channel that the given user has left
168 	*/
169 	private void broadcastLeave(DConnection left)
170 	{
171 		/* Lock the members list */
172 		memberLock.lock();
173 		
174 		/* Send left message here */
175 		foreach(DConnection currentMember; members)
176 		{
177 			sendLeaveMessage(currentMember, left);
178 		}
179 
180 		/* Unlock the members list */
181 		memberLock.unlock();
182 	}
183 
184 	/**
185 	* Sends a message to the user stating the given
186 	* (other) user has left the channel
187 	*/
188 	private void sendLeaveMessage(DConnection member, DConnection left)
189 	{
190 		/* The protocol data to send */
191 		byte[] protocolData;
192 
193 		/* Set the notificaiton type to `channel status` */
194 		protocolData ~= [1];
195 
196 		/* Set the sub-type to leave */
197 		protocolData ~= [0];
198 
199 		/* Set the channel notificaiton type to `member leave` */
200 		protocolData ~= cast(byte[])left.getUsername();
201 
202 		/* Write the notification */
203 		member.writeSocket(0, protocolData);
204 	}
205 
206 	/**
207 	* Sends a message to all users of this
208 	* channel that the given user has joined
209 	*/
210 	private void broadcastJoin(DConnection joined)
211 	{
212 		/* Lock the members list */
213 		memberLock.lock();
214 		
215 		/* Send join message here */
216 		foreach(DConnection currentMember; members)
217 		{
218 			sendJoinMessage(currentMember, joined);
219 		}
220 
221 		/* Unlock the members list */
222 		memberLock.unlock();
223 	}
224 
225 	/**
226 	* Sends a message to the user stating the given
227 	* (other) user has joined the channel
228 	*/
229 	private void sendJoinMessage(DConnection member, DConnection joined)
230 	{
231 		/* The protocol data to send */
232 		byte[] protocolData;
233 
234 		/* Set the notificaiton type to `channel status` */
235 		protocolData ~= [1];
236 
237 		/* Set the sub-type to join */
238 		protocolData ~= [1];
239 
240 		/* Set the channel notificaiton type to `member join` */
241 		protocolData ~= cast(byte[])joined.getUsername();
242 
243 		/* Write the notification */
244 		member.writeSocket(0, protocolData);
245 	}
246 
247 
248 
249 	public bool sendMessage(DConnection sender, string message)
250 	{
251 		bool status;
252 
253 		/* The protocol data to send */
254 		byte[] msg;
255 
256 		/* Set the notificaiton type to `message notification` */
257 		msg ~= [0];
258 
259 		/**
260 		* Format
261 		* 0 - dm
262 		* 1 - channel (this case)
263 		* byte length of name of channel/person (dm case)
264 		* message-bytes
265 		*/
266 		msg ~= [cast(byte)1,(cast(byte)sender.getUsername().length)]~cast(byte[])sender.getUsername()~cast(byte[])message;
267 		
268 		/* Send the message to everyone else in the channel */
269 		foreach(DConnection member; members)
270 		{
271 			/* Skip sending to self */
272 			if(!(member is sender))
273 			{
274 				/* Send the message */
275 				writeln("Delivering message '"~message~"' for channel '"~name~"' to user '"~member.getUsername()~"'...");
276 				status = member.writeSocket(0, msg);
277 
278 				if(status)
279 				{
280 					writeln("Delivered message '"~message~"' for channel '"~name~"' to user '"~member.getUsername()~"'!");	
281 				}
282 				else
283 				{
284 					writeln("Failed to deliver message '"~message~"' for channel '"~name~"' to user '"~member.getUsername()~"'!");	
285 				}
286 			}
287 		}
288 
289 
290 		/* TODO: Don't, retur true */
291 		return true;
292 	}
293 
294 	/**
295 	* Returns a list of all the members
296 	*/
297 	public DConnection[] getMembers()
298 	{
299 		/* Members list */
300 		DConnection[] memberList;
301 
302 		/* Lock the members list */
303 		memberLock.lock();
304 		
305 		memberList = members;
306 
307 		/* Unlock the members list */
308 		memberLock.unlock();
309 
310 		return memberList;
311 	}
312 
313 
314 	public override string toString()
315 	{
316 		string toStr;
317 
318 		/* Lock the members list */
319 		memberLock.lock();
320 		
321 		toStr = "DChannel [Name: "~name~", Members: "~to!(string)(members)~"]";
322 
323 		/* Unlock the members list */
324 		memberLock.unlock();
325 
326 		return toStr;
327 	}
328 	
329 }