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.client; 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 gprintln("Channel/User Notification: "~to!(string)(data)); 177 178 179 180 /* If this is a new message channel notification */ 181 if(data[0] == 0) 182 { 183 gprintln("New channel message received", DebugType.WARNING); 184 185 /* Decode the [usernameLength, username] */ 186 ubyte usernameLength = data[1]; 187 gprintln("ChannelMessage: (Username length): "~to!(string)(usernameLength)); 188 string username = cast(string)data[2..2+usernameLength]; 189 gprintln("ChannelMessage: (Username): "~username); 190 191 /* Decode the [channelLength, channel] */ 192 ubyte channelLength = data[2+usernameLength]; 193 gprintln("ChannelMessage: (Channel length): "~to!(string)(channelLength)); 194 string channel = cast(string)data[2+usernameLength+1..2+usernameLength+1+channelLength]; 195 gprintln("ChannelMessage: (Channel): "~channel); 196 197 findChannel(channel).receiveMessage(username, cast(string)data[2+usernameLength+1+channelLength..data.length]); 198 } 199 /* If this is a new direct message notification */ 200 else if(data[0] == 1) 201 { 202 gprintln("New direct message received", DebugType.WARNING); 203 204 /* Decode the [usernameLength, username] (username here is recipient's) */ 205 ubyte recipientLength = data[1]; 206 gprintln("DirectMessage: (Recipient length): "~to!(string)(recipientLength)); 207 string recipient = cast(string)data[2..2+recipientLength]; 208 gprintln("DirectMessage: (Recipient): "~recipient); 209 210 /* Decode the [usernameLength, username] (username here is sender's) */ 211 ubyte sendersLength = data[2+recipientLength]; 212 gprintln("DirectMessage: (Sender length): "~to!(string)(sendersLength)); 213 string sender = cast(string)data[2+recipientLength+1..2+recipientLength+1+sendersLength]; 214 gprintln("DirectMessage: (Sender): "~sender); 215 216 217 218 /* The message is the remainder */ 219 string message = cast(string)data[2+recipientLength+1+sendersLength..data.length]; 220 gprintln("DirectMessage: (Message): "~message); 221 222 /** 223 * TODO: DIfferes from channels, channels we never get delivered those we have no tab for as we haven't joined them 224 * and because server side knows we haven't joined iot we don't receive the notifivcaiton, eher however, there is no 225 * user tab possibly yet, so we will need to add it our selves */ 226 User userArea = findUser(sender); 227 228 if(userArea) 229 { 230 userArea.receiveMessage(sender, message); 231 } 232 else 233 { 234 /* Add a new UserArea which will generate a new tab for us */ 235 addDirectMessage_unsafe(sender); 236 237 /* The above statement adds an entry for us, now let's find the added UserArea */ 238 userArea = findUser(sender); 239 240 /* Now let's add the direct message */ 241 userArea.receiveMessage(sender, message); 242 243 } 244 245 246 } 247 else 248 { 249 /* TODO: Handle this */ 250 gprintln("FOk"); 251 } 252 253 } 254 /* Channel notification (ntype=1) */ 255 else if(notificationType == 1) 256 { 257 /* Get the sub-type */ 258 ubyte subType = data[1]; 259 260 /* If the notification was leave (stype=0) */ 261 if(subType == 0) 262 { 263 /* LeaveInfo: <channel>,<username> */ 264 string[] leaveInfo = split(cast(string)data[2..data.length],","); 265 writeln("LeaveInfo: ",leaveInfo); 266 267 /* Decode the LeaveInfo */ 268 string channel = leaveInfo[0]; 269 string username = leaveInfo[1]; 270 271 /* Find the channel */ 272 Channel matchedChannel = findChannel(channel); 273 274 /* Channel leave */ 275 matchedChannel.channelLeave(username); 276 } 277 /* If the notification was join (stype=1) */ 278 else if(subType == 1) 279 { 280 /* JoinInfo: <channel>,<username> */ 281 string[] joinInfo = split(cast(string)data[2..data.length],","); 282 writeln("JoinInfo: ",joinInfo); 283 284 /* Decode the JoinInfo */ 285 string channel = joinInfo[0]; 286 string username = joinInfo[1]; 287 288 /* Find the channel */ 289 Channel matchedChannel = findChannel(channel); 290 291 /* Channel join */ 292 matchedChannel.channelJoin(username); 293 } 294 /* TODO: Unknown */ 295 else 296 { 297 298 } 299 } 300 } 301 302 303 private void addUserDM(User newUser) 304 { 305 /* TODO: However this we need to mutex for the areas as we could recieve a new message by watcher which adds for us */ 306 chansLock.lock(); 307 areas ~= newUser; 308 chansLock.unlock(); 309 } 310 311 /** 312 * Opens a new tab for a new direct message 313 * 314 * (To be called by a handler, which auto-mutexes) 315 * 316 * 1. Will add a new area 317 * 2. Will add a new tab to the notebook switcher 318 * 3. Will switch the current tab to said tab 319 */ 320 public void addDirectMessage_unsafe(string username) 321 { 322 323 324 325 326 /* TODO: Get box over here etc. */ 327 328 gprintln("Henlo begi"); 329 330 /* Check if we have joined this user already */ 331 User foundUser = findUser(username); 332 333 gprintln("Henlo"); 334 335 /* If we have joined this user before */ 336 if(foundUser) 337 { 338 /* TODO: Switch to */ 339 writeln("nope time: "~username); 340 341 342 } 343 /* If we haven't joined this user before */ 344 else 345 { 346 /* Create the new User area */ 347 User newUser = new User(this, username); 348 349 /* Add the user */ 350 addUserDM(newUser); 351 352 /* Set as the `foundChannel` */ 353 foundUser = newUser; 354 355 /* Get the Widgets container for this channel and add a tab for it */ 356 notebookSwitcher.add(newUser.getBox()); 357 notebookSwitcher.setTabReorderable(newUser.getBox(), true); 358 notebookSwitcher.setTabLabelText(newUser.getBox(), newUser.getUsername()); 359 360 writeln("hdsjghjsd"); 361 362 writeln("first time: "~username); 363 364 // /* Get the user's list */ 365 // newChannel.populateUsersList(); 366 } 367 368 /* Render recursively all children of the container and then the container itself */ 369 box.showAll(); 370 371 372 373 374 375 376 } 377 378 379 /** 380 * Attempts to find the User object you are looking for 381 */ 382 public User findUser(string username) 383 { 384 User result; 385 386 chansLock.lock(); 387 388 /** 389 * Loop through each MessageArea and only inspect those 390 * whose type is `Channel` 391 */ 392 foreach(MessageArea area; areas) 393 { 394 395 /* Make sure the object is of type `Channel` */ 396 if(typeid(area) == typeid(User)) 397 { 398 /* Down-cast */ 399 User user = cast(User)area; 400 401 /* Find the matching channel */ 402 if(cmp(user.getUsername(), username) == 0) 403 { 404 result = user; 405 break; 406 } 407 } 408 } 409 410 import std.stdio; 411 writeln("\""~username~"\""); 412 413 414 chansLock.unlock(); 415 416 return result; 417 } 418 419 420 421 public void joinChannel(string channelName) 422 { 423 /* Check if we have joined this channel already */ 424 Channel foundChannel = findChannel(channelName); 425 426 /* If we have joined this channel before */ 427 if(foundChannel) 428 { 429 /* TODO: Switch to */ 430 writeln("nope time: "~channelName); 431 432 433 } 434 /* If we haven't joined this channel before */ 435 else 436 { 437 /* Join the channel */ 438 client.join(channelName); 439 440 /* Create the Channel object */ 441 Channel newChannel = new Channel(this, channelName); 442 443 /* Add the channel */ 444 addChannel(newChannel); 445 446 /* Set as the `foundChannel` */ 447 foundChannel = newChannel; 448 449 /* Get the Widgets container for this channel and add a tab for it */ 450 notebookSwitcher.add(newChannel.getBox()); 451 notebookSwitcher.setTabReorderable(newChannel.getBox(), true); 452 notebookSwitcher.setTabLabelText(newChannel.getBox(), newChannel.getName()); 453 454 writeln("hdsjghjsd"); 455 456 writeln("first time: "~channelName); 457 458 /* Get the user's list */ 459 newChannel.populateUsersList(); 460 } 461 462 /* Render recursively all children of the container and then the container itself */ 463 box.showAll(); 464 } 465 466 467 private void channelList() 468 { 469 te(); 470 channelList_unsafe(); 471 tl(); 472 } 473 474 public DClient getClient() 475 { 476 return client; 477 } 478 479 /** 480 * Lists all channels and displays them 481 * 482 * Only to be aclled when locked (i.e. by the event 483 * loop signal dispatch or when we lock it 484 * i.e. `channelList`) 485 */ 486 private void channelList_unsafe() 487 { 488 string[] channelList = client.list(); 489 490 foreach(string channel; channelList) 491 { 492 channels.add(new Label(channel)); 493 channels.showAll(); 494 } 495 } 496 497 /** 498 * Attempts to find the Channel object you are looking for 499 */ 500 public Channel findChannel(string channelName) 501 { 502 Channel result; 503 504 chansLock.lock(); 505 506 /** 507 * Loop through each MessageArea and only inspect those 508 * whose type is `Channel` 509 */ 510 foreach(MessageArea area; areas) 511 { 512 513 /* Make sure the object is of type `Channel` */ 514 if(typeid(area) == typeid(Channel)) 515 { 516 /* Down-cast */ 517 Channel channel = cast(Channel)area; 518 519 /* Find the matching channel */ 520 if(cmp(channel.getName(), channelName) == 0) 521 { 522 result = channel; 523 break; 524 } 525 } 526 } 527 528 import std.stdio; 529 writeln("\""~channelName~"\""); 530 531 532 chansLock.unlock(); 533 534 return result; 535 } 536 537 /** 538 * Adds the given channel to the tarcking list 539 * 540 * This adds the Channel object to the list of 541 * channels joined 542 * 543 * TODO: Migrate the gui.d channel join selectChannel 544 * here 545 * NOTE: You must manually join it though 546 */ 547 public void addChannel(Channel newChannel) 548 { 549 /* Add the channel to the `chans` tracking list */ 550 chansLock.lock(); 551 areas ~= newChannel; 552 chansLock.unlock(); 553 554 /* Add the channel to the channels list (sidebar) */ 555 writeln("Adding channel "~newChannel.getName()); 556 Label babaBooey = new Label(newChannel.getName()); /* TODO: Fuck Pango, fix here but yeah _ */ 557 babaBooey.setUseMarkup(false); 558 babaBooey.setText(newChannel.getName()); 559 channels.add(babaBooey); 560 } 561 562 /** 563 * Called when you select a channel in the sidebar 564 * 565 * This moves you to the correct notebook tab for 566 * that channel 567 */ 568 private void viewChannel(ListBox s) 569 { 570 /* Get the name of the channel selected */ 571 string channelSelected = (cast(Label)(s.getSelectedRow().getChild())).getText(); 572 573 /* Check if we have joined this channel already */ 574 Channel foundChannel = findChannel(channelSelected); 575 writeln(foundChannel is null); 576 577 /* Switch to the channel's pane */ 578 notebookSwitcher.setCurrentPage(foundChannel.getBox()); 579 580 box.showAll(); 581 } 582 583 584 /** 585 * Creates a message box 586 * 587 * A message box consists of two labels 588 * one being the name of the person who sent 589 * the message and the next being the message 590 * itself 591 */ 592 private Box createMessageBox() 593 { 594 return null; 595 } 596 597 private Box getChatPane() 598 { 599 /* The main page of the tab */ 600 Box box = new Box(GtkOrientation.HORIZONTAL, 1); 601 602 /* The channels box */ 603 Box channelBox = new Box(GtkOrientation.VERTICAL, 1); 604 605 /* The channel's list */ 606 channels = new ListBox(); 607 channels.addOnSelectedRowsChanged(&viewChannel); 608 609 channelBox.add(new Label("Channels")); 610 channelBox.add(channels); 611 612 // /* The user's box */ 613 // Box userBox = new Box(GtkOrientation.VERTICAL, 1); 614 615 // /* The user's list */ 616 // users = new ListBox(); 617 618 // userBox.add(new Label("Users")); 619 // userBox.add(users); 620 621 // /* The text box */ 622 // Box textBox = new Box(GtkOrientation.VERTICAL, 1); 623 // textBox.add(currentChannelLabel); 624 // textArea = new ListBox(); 625 // import gtk.ScrolledWindow; 626 627 // ScrolledWindow scrollTextChats = new ScrolledWindow(textArea); 628 // textBox.add(scrollTextChats); 629 // import gtk.TextView; 630 // textBox.add(new TextView()); 631 632 633 // import gtk.TextView; 634 // TextView f = new TextView(); 635 // textBox.add(f); 636 637 notebookSwitcher = new Notebook(); 638 notebookSwitcher.setScrollable(true); 639 //notebookSwitcher.add(newnew Label("test")); 640 641 box.add(channelBox); 642 box.add(notebookSwitcher); 643 // box.add(textBox); 644 //box.packEnd(notebookSwitcher,0,0,0); 645 646 // textBox.setChildPacking(scrollTextChats, true, true, 0, GtkPackType.START); 647 box.setChildPacking(notebookSwitcher, true, true, 0, GtkPackType.START); 648 649 650 651 return box; 652 } 653 654 private int getPageNum() 655 { 656 return gui.notebook.pageNum(box); 657 } 658 659 public void shutdown() 660 { 661 /* This is called from gui.d */ 662 int pageNum = getPageNum(); 663 664 if(pageNum == -1) 665 { 666 /* TODO: Error handling */ 667 } 668 else 669 { 670 gui.notebook.removePage(pageNum); 671 gui.notebook.showAll(); 672 } 673 } 674 }