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