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 gprintln(newUser.getBox()); 294 295 /* Get the Widgets container for this channel and add a tab for it */ 296 notebookSwitcher.add(newUser.getBox()); 297 notebookSwitcher.setTabReorderable(newUser.getBox(), true); 298 notebookSwitcher.setTabLabelText(newUser.getBox(), newUser.getUsername()); 299 300 writeln("hdsjghjsd"); 301 302 writeln("first time: "~username); 303 304 // /* Get the user's list */ 305 // newChannel.populateUsersList(); 306 } 307 308 /* Render recursively all children of the container and then the container itself */ 309 box.showAll(); 310 311 312 313 314 315 316 } 317 318 319 /** 320 * Attempts to find the User object you are looking for 321 */ 322 public User findUser(string username) 323 { 324 User result; 325 326 chansLock.lock(); 327 328 /** 329 * Loop through each MessageArea and only inspect those 330 * whose type is `Channel` 331 */ 332 foreach(MessageArea area; areas) 333 { 334 335 /* Make sure the object is of type `Channel` */ 336 if(typeid(area) == typeid(User)) 337 { 338 /* Down-cast */ 339 User user = cast(User)area; 340 341 /* Find the matching channel */ 342 if(cmp(user.getUsername(), username) == 0) 343 { 344 result = user; 345 break; 346 } 347 } 348 } 349 350 import std.stdio; 351 writeln("\""~username~"\""); 352 353 354 chansLock.unlock(); 355 356 return result; 357 } 358 359 360 361 public void joinChannel(string channelName) 362 { 363 /* Check if we have joined this channel already */ 364 Channel foundChannel = findChannel(channelName); 365 366 /* If we have joined this channel before */ 367 if(foundChannel) 368 { 369 /* TODO: Switch to */ 370 writeln("nope time: "~channelName); 371 372 373 } 374 /* If we haven't joined this channel before */ 375 else 376 { 377 /* Join the channel */ 378 client.join(channelName); 379 380 /* Create the Channel object */ 381 Channel newChannel = new Channel(this, channelName); 382 383 /* Add the channel */ 384 addChannel(newChannel); 385 386 /* Set as the `foundChannel` */ 387 foundChannel = newChannel; 388 389 /* Get the Widgets container for this channel and add a tab for it */ 390 notebookSwitcher.add(newChannel.getBox()); 391 notebookSwitcher.setTabReorderable(newChannel.getBox(), true); 392 notebookSwitcher.setTabLabelText(newChannel.getBox(), newChannel.getName()); 393 394 writeln("hdsjghjsd"); 395 396 writeln("first time: "~channelName); 397 398 /* Get the user's list */ 399 newChannel.populateUsersList(); 400 } 401 402 /* Render recursively all children of the container and then the container itself */ 403 box.showAll(); 404 } 405 406 407 private void channelList() 408 { 409 te(); 410 channelList_unsafe(); 411 tl(); 412 } 413 414 public DClient getClient() 415 { 416 return client; 417 } 418 419 /** 420 * Lists all channels and displays them 421 * 422 * Only to be aclled when locked (i.e. by the event 423 * loop signal dispatch or when we lock it 424 * i.e. `channelList`) 425 */ 426 private void channelList_unsafe() 427 { 428 string[] channelList = client.list(); 429 430 foreach(string channel; channelList) 431 { 432 channels.add(new Label(channel)); 433 channels.showAll(); 434 } 435 } 436 437 /** 438 * Attempts to find the Channel object you are looking for 439 */ 440 public Channel findChannel(string channelName) 441 { 442 Channel result; 443 444 chansLock.lock(); 445 446 /** 447 * Loop through each MessageArea and only inspect those 448 * whose type is `Channel` 449 */ 450 foreach(MessageArea area; areas) 451 { 452 453 /* Make sure the object is of type `Channel` */ 454 if(typeid(area) == typeid(Channel)) 455 { 456 /* Down-cast */ 457 Channel channel = cast(Channel)area; 458 459 /* Find the matching channel */ 460 if(cmp(channel.getName(), channelName) == 0) 461 { 462 result = channel; 463 break; 464 } 465 } 466 } 467 468 import std.stdio; 469 writeln("\""~channelName~"\""); 470 471 472 chansLock.unlock(); 473 474 return result; 475 } 476 477 /** 478 * Adds the given channel to the tarcking list 479 * 480 * This adds the Channel object to the list of 481 * channels joined 482 * 483 * TODO: Migrate the gui.d channel join selectChannel 484 * here 485 * NOTE: You must manually join it though 486 */ 487 public void addChannel(Channel newChannel) 488 { 489 /* Add the channel to the `chans` tracking list */ 490 chansLock.lock(); 491 areas ~= newChannel; 492 chansLock.unlock(); 493 494 /* Add the channel to the channels list (sidebar) */ 495 writeln("Adding channel "~newChannel.getName()); 496 Label babaBooey = new Label(newChannel.getName()); /* TODO: Fuck Pango, fix here but yeah _ */ 497 babaBooey.setUseMarkup(false); 498 babaBooey.setText(newChannel.getName()); 499 channels.add(babaBooey); 500 } 501 502 /** 503 * Called when you select a channel in the sidebar 504 * 505 * This moves you to the correct notebook tab for 506 * that channel 507 */ 508 private void viewChannel(ListBox s) 509 { 510 /* Get the name of the channel selected */ 511 string channelSelected = (cast(Label)(s.getSelectedRow().getChild())).getText(); 512 513 /* Check if we have joined this channel already */ 514 Channel foundChannel = findChannel(channelSelected); 515 writeln(foundChannel is null); 516 517 /* Switch to the channel's pane */ 518 notebookSwitcher.setCurrentPage(foundChannel.getBox()); 519 520 box.showAll(); 521 } 522 523 524 /** 525 * Creates a message box 526 * 527 * A message box consists of two labels 528 * one being the name of the person who sent 529 * the message and the next being the message 530 * itself 531 */ 532 private Box createMessageBox() 533 { 534 return null; 535 } 536 537 private Box getChatPane() 538 { 539 /* The main page of the tab */ 540 Box box = new Box(GtkOrientation.HORIZONTAL, 1); 541 542 /* The channels box */ 543 Box channelBox = new Box(GtkOrientation.VERTICAL, 1); 544 545 /* The channel's list */ 546 channels = new ListBox(); 547 channels.addOnSelectedRowsChanged(&viewChannel); 548 549 channelBox.add(new Label("Channels")); 550 channelBox.add(channels); 551 552 // /* The user's box */ 553 // Box userBox = new Box(GtkOrientation.VERTICAL, 1); 554 555 // /* The user's list */ 556 // users = new ListBox(); 557 558 // userBox.add(new Label("Users")); 559 // userBox.add(users); 560 561 // /* The text box */ 562 // Box textBox = new Box(GtkOrientation.VERTICAL, 1); 563 // textBox.add(currentChannelLabel); 564 // textArea = new ListBox(); 565 // import gtk.ScrolledWindow; 566 567 // ScrolledWindow scrollTextChats = new ScrolledWindow(textArea); 568 // textBox.add(scrollTextChats); 569 // import gtk.TextView; 570 // textBox.add(new TextView()); 571 572 573 // import gtk.TextView; 574 // TextView f = new TextView(); 575 // textBox.add(f); 576 577 notebookSwitcher = new Notebook(); 578 notebookSwitcher.setScrollable(true); 579 //notebookSwitcher.add(newnew Label("test")); 580 581 box.add(channelBox); 582 box.add(notebookSwitcher); 583 // box.add(textBox); 584 //box.packEnd(notebookSwitcher,0,0,0); 585 586 // textBox.setChildPacking(scrollTextChats, true, true, 0, GtkPackType.START); 587 box.setChildPacking(notebookSwitcher, true, true, 0, GtkPackType.START); 588 589 590 591 return box; 592 } 593 594 private int getPageNum() 595 { 596 return gui.notebook.pageNum(box); 597 } 598 599 public void shutdown() 600 { 601 /* This is called from gui.d */ 602 int pageNum = getPageNum(); 603 604 if(pageNum == -1) 605 { 606 /* TODO: Error handling */ 607 } 608 else 609 { 610 gui.notebook.removePage(pageNum); 611 gui.notebook.showAll(); 612 } 613 } 614 }