1 module Connection; 2 3 import core.thread; 4 import gui; 5 import gdk.Threads : te = threadsEnter, tl = threadsLeave; 6 import gtk.Box; 7 import std.stdio; 8 import libdnet.dclient; 9 import std.socket; 10 import gtk.ListBox; 11 import gtk.Label; 12 13 import areas.Channel; 14 import areas.MessageArea; 15 import areas.User; 16 import std.string; 17 18 import core.sync.mutex; 19 20 import gtk.Notebook; 21 22 import std.conv; 23 24 import gogga; 25 26 27 28 public final class Connection : Thread 29 { 30 private GUI gui; 31 private Box box; 32 private ListBox channels; 33 34 private DClient client; 35 private Address address; 36 private string[] auth; 37 private string statusText; 38 39 /* TODO: Check if we need to protect */ 40 /* TODO: So far usage is in signal handlers (mutex safved) and within te-tl lock for notifications */ 41 private string currentChannel; /* TODO: Used to track what notificaitons come throug */ 42 private Label currentChannelLabel; /* TODO: Using getChild would be nicer, but yeah, this is for the title */ 43 44 /** 45 * All joined Channel-s in this Connection 46 */ 47 private Notebook notebookSwitcher; 48 private MessageArea[] areas; /*TODO: Technically locking by GTK would make this not needed */ 49 private Mutex chansLock; 50 private MessageArea focusedArea; 51 52 53 // public void setPrescence(string pres) 54 // { 55 // /* The new status */ 56 // string newStatus = 57 // statusText = ""; 58 // statusText = 59 // } 60 61 this(GUI gui, Address address, string[] auth) 62 { 63 super(&worker); 64 this.gui = gui; 65 this.address = address; 66 this.auth = auth; 67 68 /* Initialize locks */ 69 initializeLocks(); 70 71 statusText = "Hey there, I'm using Dnet!"; 72 73 /* Start the notification atcher */ 74 start(); 75 } 76 77 /** 78 * Initializes all locks (other than GDK) 79 */ 80 private void initializeLocks() 81 { 82 chansLock = new Mutex(); 83 } 84 85 private void worker() 86 { 87 /* Create a new Label */ 88 currentChannelLabel = new Label("CHANNEL NAME GOES HERE"); 89 90 /** 91 * Setup the tab for this connection 92 */ 93 te(); 94 box = getChatPane(); 95 gui.notebook.add(box); 96 //gui.notebook.setTabReorderable(box, true); 97 //gui.notebook setChildPacking(box, true, true, 0, GtkPackType.START); 98 // gui.mainWindow. 99 gui.notebook.setTabLabelText(box, auth[0]~"@"~address.toString()); 100 gui.notebook.showAll(); 101 tl(); 102 103 104 /** 105 * Connects and logs in 106 */ 107 client = new DClient(address); 108 client.auth(auth[0], auth[1]); /* TODO: DO this without auth (the list in the loop, crahses server) */ 109 110 /* Set your prescense */ 111 client.setProperty("pres","available"); 112 113 114 115 // te(); 116 // import ProfileWIndow; 117 // ProfileWindow profile = new ProfileWindow(this, auth[0]); 118 // tl(); 119 120 /** 121 * Notification loop 122 * 123 * Awaits notifications and then displays them 124 */ 125 while(true) 126 { 127 /* Receive a notification */ 128 byte[] notificationData = client.awaitNotification(); 129 gprintln("A new notification has arrived"); 130 gprintln("Notification data: "~to!(string)(notificationData)); 131 132 te(); 133 // import std.conv; 134 // textArea.add(new Label(to!(string)(notificationData))); 135 // textArea.showAll(); 136 137 138 process(notificationData); 139 //gui.mainWindow.showAll(); 140 141 // import gtk.InfoBar; 142 // InfoBar notificationBar = new InfoBar(); 143 // notificationBar.add(new Label("Poes")); 144 // notificationBar.setMessageType(GtkMessageType.INFO); 145 // notificationBar 146 147 // box.add(notificationBar); 148 // notificationBar.showAll(); 149 150 notebookSwitcher.showAll(); 151 152 153 tl(); 154 155 //Thread.sleep(dur!("seconds")(2)); 156 } 157 } 158 159 /** 160 * Processes an incoming notification 161 * accordingly 162 */ 163 private void process(byte[] data) 164 { 165 /* Get the notification type */ 166 ubyte notificationType = data[0]; 167 gprintln("NotificationType: "~to!(string)(notificationType)); 168 169 /* For normal message (to channel or user) */ 170 if(notificationType == 0) 171 { 172 /* TODO: Handle private messages */ 173 174 /* Decode is a test for assuming channel message received */ 175 data = data[1..data.length]; 176 177 /* Decode the [usernameLength, username] */ 178 ubyte usernameLength = data[1]; 179 gprintln("NormalMessage: (Username length): "~to!(string)(usernameLength)); 180 string username = cast(string)data[2..2+usernameLength]; 181 gprintln("NormalMessage: (Username): "~username); 182 183 /* Decode the [channelLength, channel] */ 184 ubyte channelLength = data[2+usernameLength]; 185 gprintln("NormalMessage: (Channel length): "~to!(string)(channelLength)); 186 string channel = cast(string)data[2+usernameLength+1..2+usernameLength+1+channelLength]; 187 gprintln("NormalMessage: (Channel): "~channel); 188 189 190 findChannel(channel).receiveMessage(username, cast(string)data[2+usernameLength+1+channelLength..data.length]); 191 } 192 /* Channel notification (ntype=1) */ 193 else if(notificationType == 1) 194 { 195 /* Get the sub-type */ 196 ubyte subType = data[1]; 197 198 /* If the notification was leave (stype=0) */ 199 if(subType == 0) 200 { 201 /* LeaveInfo: <channel>,<username> */ 202 string[] leaveInfo = split(cast(string)data[2..data.length],","); 203 writeln("LeaveInfo: ",leaveInfo); 204 205 /* Decode the LeaveInfo */ 206 string channel = leaveInfo[0]; 207 string username = leaveInfo[1]; 208 209 /* Find the channel */ 210 Channel matchedChannel = findChannel(channel); 211 212 /* Channel leave */ 213 matchedChannel.channelLeave(username); 214 } 215 /* If the notification was join (stype=1) */ 216 else if(subType == 1) 217 { 218 /* JoinInfo: <channel>,<username> */ 219 string[] joinInfo = split(cast(string)data[2..data.length],","); 220 writeln("JoinInfo: ",joinInfo); 221 222 /* Decode the JoinInfo */ 223 string channel = joinInfo[0]; 224 string username = joinInfo[1]; 225 226 /* Find the channel */ 227 Channel matchedChannel = findChannel(channel); 228 229 /* Channel join */ 230 matchedChannel.channelJoin(username); 231 } 232 /* TODO: Unknown */ 233 else 234 { 235 236 } 237 } 238 } 239 240 241 private void addUserDM(User newUser) 242 { 243 /* TODO: However this we need to mutex for the areas as we could recieve a new message by watcher which adds for us */ 244 chansLock.lock(); 245 areas ~= newUser; 246 chansLock.unlock(); 247 } 248 249 /** 250 * Opens a new tab for a new direct message 251 * 252 * (To be called by a handler, which auto-mutexes) 253 * 254 * 1. Will add a new area 255 * 2. Will add a new tab to the notebook switcher 256 * 3. Will switch the current tab to said tab 257 */ 258 public void addDirectMessage_unsafe(string username) 259 { 260 261 262 263 264 /* TODO: Get box over here etc. */ 265 266 gprintln("Henlo begi"); 267 268 /* Check if we have joined this user already */ 269 User foundUser = findUser(username); 270 271 gprintln("Henlo"); 272 273 /* If we have joined this user before */ 274 if(foundUser) 275 { 276 /* TODO: Switch to */ 277 writeln("nope time: "~username); 278 279 280 } 281 /* If we haven't joined this user before */ 282 else 283 { 284 /* Create the new User area */ 285 User newUser = new User(this, username); 286 287 /* Add the user */ 288 addUserDM(newUser); 289 290 /* Set as the `foundChannel` */ 291 foundUser = newUser; 292 293 /* Get the Widgets container for this channel and add a tab for it */ 294 notebookSwitcher.add(newUser.getBox()); 295 notebookSwitcher.setTabReorderable(newUser.getBox(), true); 296 notebookSwitcher.setTabLabelText(newUser.getBox(), newUser.getUsername()); 297 298 writeln("hdsjghjsd"); 299 300 writeln("first time: "~username); 301 302 // /* Get the user's list */ 303 // newChannel.populateUsersList(); 304 } 305 306 /* Render recursively all children of the container and then the container itself */ 307 box.showAll(); 308 309 310 311 312 313 314 } 315 316 317 /** 318 * Attempts to find the User object you are looking for 319 */ 320 public User findUser(string username) 321 { 322 User result; 323 324 chansLock.lock(); 325 326 /** 327 * Loop through each MessageArea and only inspect those 328 * whose type is `Channel` 329 */ 330 foreach(MessageArea area; areas) 331 { 332 333 /* Make sure the object is of type `Channel` */ 334 if(typeid(area) == typeid(User)) 335 { 336 /* Down-cast */ 337 User user = cast(User)area; 338 339 /* Find the matching channel */ 340 if(cmp(user.getUsername(), username) == 0) 341 { 342 result = user; 343 break; 344 } 345 } 346 } 347 348 import std.stdio; 349 writeln("\""~username~"\""); 350 351 352 chansLock.unlock(); 353 354 return result; 355 } 356 357 358 359 public void joinChannel(string channelName) 360 { 361 /* Check if we have joined this channel already */ 362 Channel foundChannel = findChannel(channelName); 363 364 /* If we have joined this channel before */ 365 if(foundChannel) 366 { 367 /* TODO: Switch to */ 368 writeln("nope time: "~channelName); 369 370 371 } 372 /* If we haven't joined this channel before */ 373 else 374 { 375 /* Join the channel */ 376 client.join(channelName); 377 378 /* Create the Channel object */ 379 Channel newChannel = new Channel(this, channelName); 380 381 /* Add the channel */ 382 addChannel(newChannel); 383 384 /* Set as the `foundChannel` */ 385 foundChannel = newChannel; 386 387 /* Get the Widgets container for this channel and add a tab for it */ 388 notebookSwitcher.add(newChannel.getBox()); 389 notebookSwitcher.setTabReorderable(newChannel.getBox(), true); 390 notebookSwitcher.setTabLabelText(newChannel.getBox(), newChannel.getName()); 391 392 writeln("hdsjghjsd"); 393 394 writeln("first time: "~channelName); 395 396 /* Get the user's list */ 397 newChannel.populateUsersList(); 398 } 399 400 /* Render recursively all children of the container and then the container itself */ 401 box.showAll(); 402 } 403 404 405 private void channelList() 406 { 407 te(); 408 channelList_unsafe(); 409 tl(); 410 } 411 412 public DClient getClient() 413 { 414 return client; 415 } 416 417 /** 418 * Lists all channels and displays them 419 * 420 * Only to be aclled when locked (i.e. by the event 421 * loop signal dispatch or when we lock it 422 * i.e. `channelList`) 423 */ 424 private void channelList_unsafe() 425 { 426 string[] channelList = client.list(); 427 428 foreach(string channel; channelList) 429 { 430 channels.add(new Label(channel)); 431 channels.showAll(); 432 } 433 } 434 435 /** 436 * Attempts to find the Channel object you are looking for 437 */ 438 public Channel findChannel(string channelName) 439 { 440 Channel result; 441 442 chansLock.lock(); 443 444 /** 445 * Loop through each MessageArea and only inspect those 446 * whose type is `Channel` 447 */ 448 foreach(MessageArea area; areas) 449 { 450 451 /* Make sure the object is of type `Channel` */ 452 if(typeid(area) == typeid(Channel)) 453 { 454 /* Down-cast */ 455 Channel channel = cast(Channel)area; 456 457 /* Find the matching channel */ 458 if(cmp(channel.getName(), channelName) == 0) 459 { 460 result = channel; 461 break; 462 } 463 } 464 } 465 466 import std.stdio; 467 writeln("\""~channelName~"\""); 468 469 470 chansLock.unlock(); 471 472 return result; 473 } 474 475 /** 476 * Adds the given channel to the tarcking list 477 * 478 * This adds the Channel object to the list of 479 * channels joined 480 * 481 * TODO: Migrate the gui.d channel join selectChannel 482 * here 483 * NOTE: You must manually join it though 484 */ 485 public void addChannel(Channel newChannel) 486 { 487 /* Add the channel to the `chans` tracking list */ 488 chansLock.lock(); 489 areas ~= newChannel; 490 chansLock.unlock(); 491 492 /* Add the channel to the channels list (sidebar) */ 493 writeln("Adding channel "~newChannel.getName()); 494 Label babaBooey = new Label(newChannel.getName()); /* TODO: Fuck Pango, fix here but yeah _ */ 495 babaBooey.setUseMarkup(false); 496 babaBooey.setText(newChannel.getName()); 497 channels.add(babaBooey); 498 } 499 500 /** 501 * Called when you select a channel in the sidebar 502 * 503 * This moves you to the correct notebook tab for 504 * that channel 505 */ 506 private void viewChannel(ListBox s) 507 { 508 /* Get the name of the channel selected */ 509 string channelSelected = (cast(Label)(s.getSelectedRow().getChild())).getText(); 510 511 /* Check if we have joined this channel already */ 512 Channel foundChannel = findChannel(channelSelected); 513 writeln(foundChannel is null); 514 515 /* Switch to the channel's pane */ 516 notebookSwitcher.setCurrentPage(foundChannel.getBox()); 517 518 box.showAll(); 519 } 520 521 522 /** 523 * Creates a message box 524 * 525 * A message box consists of two labels 526 * one being the name of the person who sent 527 * the message and the next being the message 528 * itself 529 */ 530 private Box createMessageBox() 531 { 532 return null; 533 } 534 535 private Box getChatPane() 536 { 537 /* The main page of the tab */ 538 Box box = new Box(GtkOrientation.HORIZONTAL, 1); 539 540 /* The channels box */ 541 Box channelBox = new Box(GtkOrientation.VERTICAL, 1); 542 543 /* The channel's list */ 544 channels = new ListBox(); 545 channels.addOnSelectedRowsChanged(&viewChannel); 546 547 channelBox.add(new Label("Channels")); 548 channelBox.add(channels); 549 550 // /* The user's box */ 551 // Box userBox = new Box(GtkOrientation.VERTICAL, 1); 552 553 // /* The user's list */ 554 // users = new ListBox(); 555 556 // userBox.add(new Label("Users")); 557 // userBox.add(users); 558 559 // /* The text box */ 560 // Box textBox = new Box(GtkOrientation.VERTICAL, 1); 561 // textBox.add(currentChannelLabel); 562 // textArea = new ListBox(); 563 // import gtk.ScrolledWindow; 564 565 // ScrolledWindow scrollTextChats = new ScrolledWindow(textArea); 566 // textBox.add(scrollTextChats); 567 // import gtk.TextView; 568 // textBox.add(new TextView()); 569 570 571 // import gtk.TextView; 572 // TextView f = new TextView(); 573 // textBox.add(f); 574 575 notebookSwitcher = new Notebook(); 576 notebookSwitcher.setScrollable(true); 577 //notebookSwitcher.add(newnew Label("test")); 578 579 box.add(channelBox); 580 box.add(notebookSwitcher); 581 // box.add(textBox); 582 //box.packEnd(notebookSwitcher,0,0,0); 583 584 // textBox.setChildPacking(scrollTextChats, true, true, 0, GtkPackType.START); 585 box.setChildPacking(notebookSwitcher, true, true, 0, GtkPackType.START); 586 587 588 589 return box; 590 } 591 592 private int getPageNum() 593 { 594 return gui.notebook.pageNum(box); 595 } 596 597 public void shutdown() 598 { 599 /* This is called from gui.d */ 600 int pageNum = getPageNum(); 601 602 if(pageNum == -1) 603 { 604 /* TODO: Error handling */ 605 } 606 else 607 { 608 gui.notebook.removePage(pageNum); 609 gui.notebook.showAll(); 610 } 611 } 612 }