Exemple #1
0
class TestChat(DCSATestCase):
    ###################################################################################################################
    # BASIC TESTS
    def test_should_start(self):
        self.c.start()
        self._registerStartedChatSession(self.c)

    def test_should_stop_if_started(self):
        self.test_should_start()
        self.c.stop()

    def test_should_raise_exception_if_not_started(self):
        self.assertRaises(Exception, self.c.stop)


    def test_should_open_listen_socket_on_start(self):
        self.test_should_start()
        self._try_to_connect(self.c.getListenAddress())


    def test_should_not_have_open_socket_until_start(self):
        self.assertRaises(socket.error, self._try_to_connect, self.c.getListenAddress())


    def test_should_close_socket_on_chat_stop(self):
        self.test_should_stop_if_started()
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.assertRaises(socket.error, s.connect, self.c.getListenAddress())

    def test_stop_should_reset_connected_status(self):
        self.c.start()
        assert(self.c._startComplete)
        time.sleep(0.1)
        self.c.stop()
        assert(not self.c._startComplete)

    def test_shouldnt_allow_multiple_instances_with_same_listen_address(self):
        self.test_should_start()
        self.c2 = Chat((Chat.DEFAULT_LISTEN_IP, getUnusedListenPort()))
        self.assertRaises(Exception, self.c2.start())

    def test_shouldnt_create_group_if_not_started(self):
        self.assertRaises(Exception, self.c.createGroup)

    def test_should_allow_multiple_instances_with_different_listen_address(self):
        self.test_should_start()
        self.c2 = Chat((Chat.DEFAULT_LISTEN_IP, getUnusedListenPort()))
        self.c2.start() 
        self._registerStartedChatSession(self.c2)
        self._try_to_connect(self.c2.getListenAddress())


    def test_should_be_able_to_create_group_if_not_already_in_a_group(self):
        self.test_should_start()
        self.c.createGroup()


    def test_shouldnt_allow_to_create_group_if_already_joined_a_group(self):
        self.test_agent_who_added_someone_should_turn_on_active_flag_if_new_user_receives_the_initial_updated_groupview_from_him()
        self.assertRaises(Exception, self.c2.createGroup())


    def test_shouldnt_be_able_to_create_group_if_already_created_a_group(self):
        self.test_should_be_able_to_create_group_if_not_already_in_a_group()
        self.assertRaises(Exception, self.c.createGroup())


    def test_should_be_able_to_join_group_if_not_already_in_a_group(self):
        self.test_should_allow_multiple_instances_with_different_listen_address()
        # Both "c" and "c2" are listening now
        self.c.createGroup()
        self.c.addUser(self.c2.getListenAddress())

    def test_new_user_should_initially_appear_offline_in_groupview_of_agent_that_added_him(self):
        self.test_should_allow_multiple_instances_with_different_listen_address()
        # Both "c" and "c2" are listening now
        self.c2.stop()
        self.assertRaises(socket.error, self._try_to_connect, self.c2.getListenAddress())
        time.sleep(0.1)
        self.c.createGroup()
        self.c.addUser(self.c2.getListenAddress())
        time.sleep(0.1)
        self.assertEqual(Chat.OFFLINE, self.c.userStatus(self.c2.getListenAddress()))


    def test_new_user_should_initially_appear_online_in_groupview_of_agent_that_added_him_if_online(self):
        self.test_should_be_able_to_join_group_if_not_already_in_a_group()
        time.sleep(0.1)
        self.assertEqual(Chat.ONLINE, self.c.userStatus(self.c2.getListenAddress()))
        
        
    def test_user_should_be_able_to_permanently_leave_the_group(self):
        self.test_new_user_should_initially_appear_online_in_groupview_of_agent_that_added_him_if_online()
        time.sleep(0.1)
        self.c2.leaveGroup()
        time.sleep(0.1)
        self.assertFalse(self.c2.getListenAddress() in self.c.groupView.getGroup())
        
    def test_should_log_at_leavers_log_when_user_leaves_group(self):
        self.test_user_should_be_able_to_permanently_leave_the_group()
        self.assertLogEntryContains('Left the group', self.c2._logfile)
    
    def test_should_log_at_others_log_when_user_leaves_group(self):
        self.test_user_should_be_able_to_permanently_leave_the_group()
        self.assertLogEntryContains('Member %s left the group' % str(self.c2.getListenAddress()), self.c._logfile)
        
    def test_new_user_should_receive_updated_groupview_command_from_agent_who_added_him(self):
        self.test_should_be_able_to_join_group_if_not_already_in_a_group()
        time.sleep(0.1)
        self.assertLogEntryContains('InitialGroupView received from %s' % str(self.c.getListenAddress()), self.c2._logfile)
        self.assertEqual(2, len(self.c2.groupView.getGroup()))        
        
            
    def test_agent_who_added_someone_should_turn_on_active_flag_if_new_user_receives_the_initial_updated_groupview_from_him(self):
        self.test_new_user_should_receive_updated_groupview_command_from_agent_who_added_him()
        self.assertEqual(Chat.ONLINE, self.c.userStatus(self.c2.getListenAddress()))
        
    
    def test_someone_who_is_logged_on_should_appear_as_online_in_other_agents_lists(self):
        self.test_agent_who_added_someone_should_turn_on_active_flag_if_new_user_receives_the_initial_updated_groupview_from_him()
        self.c3 = Chat((Chat.DEFAULT_LISTEN_IP, getUnusedListenPort()))
        self.c3.start()
        self._registerStartedChatSession(self.c3)
        self.c.addUser(self.c3.getListenAddress())
        time.sleep(0.5)
        assert(self.c3.address in self.c.getOnlineUsers())
        assert(self.c3.address in self.c2.getOnlineUsers())
    
    
    def test_should_periodically_ping_everyone_on_the_list_and_dont_notify_if_no_changes(self):
        self.test_agent_who_added_someone_should_turn_on_active_flag_if_new_user_receives_the_initial_updated_groupview_from_him()
        count = len(self.c.sentPackets)
        self.c.setPeriodicPingSleep(0.3)
        time.sleep(0.2 + PeriodicPingThread.POLL_INTERVAL)
        self.assertEqual(count, len(self.c.sentPackets))
    
    
    def test_should_periodically_ping_everyone_on_the_list_and_notify_if_changes(self):
        self._startThreeAgents()
        
        count = len(self.c.sentPackets)
        self.c3.logout()
        time.sleep(0.1)
        
        assert(self.c3.getListenAddress() in self.c.getOfflineUsers())
        assert(self.c3.getListenAddress() in self.c2.getOfflineUsers())
        
        self.c3.silentStart()
        
        self.c.setPeriodicPingSleep(0.3)
        time.sleep(0.5 + PeriodicPingThread.POLL_INTERVAL)
        
        self.c.setPeriodicPingSleep(60.0)
        
        assert(self.c3.getListenAddress() in self.c.getOnlineUsers())
        assert(self.c3.getListenAddress() in self.c2.getOnlineUsers())
        self.assertEqual('InitialGroupView', self.c3.receivedPackets()[-3].__class__.__name__)
        assert(len(self.c.sentPackets) > count)
        
        
    def test_should_properly_log_offline_members_that_are_now_online_at_others(self):
        self.test_should_periodically_ping_everyone_on_the_list_and_notify_if_changes()
        self.assertLogEntryContains('Member %s is online' % self.c3.getNick(), self.c._logfile)
        self.assertLogEntryContains('Member %s is online' % self.c3.getNick(), self.c2._logfile)
    
    
    def test_offline_agent_with_copy_of_groupview_should_try_connecting_to_everybody_in_his_list_after_logging_in(self):
        self._startThreeAgents()
        self.c.logout()
        time.sleep(0.1)
        self.c.silentStart() # don't try to connect to anyone but do open your listen port
        self.c2.logout()
        # now c3 is online and thinks c2 and c are both offline
        time.sleep(0.1)
        count1 = len(self.c.receivedPackets())
        count2 = len(self.c2.receivedPackets())
        count3 = len(self.c3.receivedPackets())
        
        self.c2.start()
        self._registerStartedChatSession(self.c2)
        time.sleep(0.1)
        assert((len(self.c.receivedPackets()) == count1 + 1) or (len(self.c3.receivedPackets()) == count3 + 1))
        

    def test_agent_should_list_online_agents(self):
        self.test_agent_who_added_someone_should_turn_on_active_flag_if_new_user_receives_the_initial_updated_groupview_from_him()
        assert(self.c2.getListenAddress() in self.c.getOnlineUsers())
        assert(self.c2.getListenAddress() not in self.c.getOfflineUsers())
    
    def test_agent_should_list_offline_agents(self):
        self.test_agent_who_added_someone_should_turn_on_active_flag_if_new_user_receives_the_initial_updated_groupview_from_him()
        self.c2.logout()
        time.sleep(0.1)
        assert(self.c2.getListenAddress() in self.c.getOfflineUsers())
        assert(self.c2.getListenAddress() not in self.c.getOnlineUsers())
    
    def test_should_ignore_messages_from_someone_not_in_the_group(self):
        self.test_should_be_able_to_create_group_if_not_already_in_a_group()
        self.c2 = Chat((Chat.DEFAULT_LISTEN_IP, getUnusedListenPort()))
        
        # send a message from c2 to c
        c1msgs = len(self.c.messages)
        c2rcv = len(self.c2.receivedPackets())
        c2nick = GroupView.defaultNickFromAddress(self.c2.getListenAddress())
        net.send(Message('hi', c2nick, 'lobby'), self.c2.getListenAddress(), self.c.getListenAddress())
        time.sleep(0.1)
        self.assertEqual(c1msgs, len(self.c.messages))
        self.assertEqual(c2rcv, len(self.c2.receivedPackets()))
        

    def test_shouldnt_be_able_to_create_group_if_already_joined_a_group(self):
        self.test_should_be_able_to_join_group_if_not_already_in_a_group()
        self.assertRaises(Exception, self.c2.createGroup())


    def test_should_send_updated_groupview_if_error_when_sending_message_to_supposedly_online_agent(self):
        self.test_someone_who_is_logged_on_should_appear_as_online_in_other_agents_lists()
        self.c3.stop()        
        self.c.sendMessage('hello world', 'lobby')
        time.sleep(0.1)

        assert(self.c3.getListenAddress() in self.c.getOfflineUsers())
        assert(self.c3.getListenAddress() in self.c2.getOfflineUsers())


    def test_broadcast_message_should_arrive_to_all_online_agents(self):
        self._startThreeAgents()
        time.sleep(0.1)
        self.c.sendMessage('hello world')
        time.sleep(0.1)
        self.assertEqual(1, len(self.c.messages))
        self.assertEqual(1, len(self.c2.messages))
        self.assertEqual(1, len(self.c3.messages))


    # LOGGING TESTS
    def test_should_log_chat_started(self):
        self.test_should_start()
        a = self.c.getListenAddress()
        self.assertLastLogEntryContains('Client started', self.c._logfile)
            
    def test_should_log_new_member_added(self):
        self.test_should_be_able_to_join_group_if_not_already_in_a_group()
        self.assertLogEntryContains('New member added %s' % str(self.c2.getListenAddress()), self.c._logfile)
    
    
    def test_should_log_joined_group(self):
        self.test_should_be_able_to_join_group_if_not_already_in_a_group()
        self.assertLogEntryContains('Joined Group through %s' % str(self.c.getListenAddress()), self.c2._logfile)
    
    def test_should_log_when_member_comes_online_at_others_log(self):
        self.test_new_user_should_receive_updated_groupview_command_from_agent_who_added_him()
        a = self.c2.getListenAddress()
        self.c3 = Chat((Chat.DEFAULT_LISTEN_IP, getUnusedListenPort()))
        self.c3.start()
        self._registerStartedChatSession(self.c3)
        time.sleep(0.1)
        self.c.addUser(self.c3.getListenAddress())
        time.sleep(0.1)
        self.assertLogEntryContains('New member added %s' % str(self.c3.getListenAddress()), self.c._logfile)
        

    def test_should_log_when_member_logs_out_at_members_log(self):
        self.test_agent_should_list_offline_agents()
        self.assertLogEntryContains('Logged out', self.c2._logfile)
        
    def test_should_log_when_member_logs_out_at_others_log(self):
        self.test_agent_should_list_offline_agents()
        self.assertLastLogEntryContains('Member %s logged out' % self.c2.getNick(), self.c._logfile)
        
    def test_should_log_updated_groupview_received(self):
        self.test_should_send_updated_groupview_if_error_when_sending_message_to_supposedly_online_agent()
        self.assertLogEntryContains('GroupUpdate received from %s' % self.c.getNick(), self.c2._logfile)
        
    def test_should_log_updated_groupview_sent(self):
        self.test_should_send_updated_groupview_if_error_when_sending_message_to_supposedly_online_agent()
        self.assertLogEntryContains('GroupUpdate sent', self.c._logfile)
        
        
    def test_should_log_member_turns_offline_violently(self):
        self.test_should_send_updated_groupview_if_error_when_sending_message_to_supposedly_online_agent()
        self.assertLogEntryContains('Member %s disconnected' % str(self.c3.getListenAddress()), self.c._logfile)
        
    
    def test_should_log_command_reception_from_unknown_agents(self):
        self.test_should_ignore_messages_from_someone_not_in_the_group()
        self.assertLogEntryContains('Packet received from unauthorized agent %s' % str(self.c2.getListenAddress()), self.c._logfile)
    
    
    def test_should_log_message_received(self):
        self.test_broadcast_message_should_arrive_to_all_online_agents()
        self.assertLogEntryContains('Message delivered from %s (#lobby): hello world' % self.c2.getNickOfAddress(self.c.getListenAddress()), self.c2._logfile)
        self.assertLogEntryContains('Message delivered from %s (#lobby): hello world' % self.c3.getNickOfAddress(self.c.getListenAddress()), self.c3._logfile)
Exemple #2
0
class MainWindow(wx.Frame):
    def __init__(self, parent, id, title):
        self.chat = None
        
        wx.Frame.__init__(self, parent, id, title, size=(800,600))
        
        font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
        font.SetPointSize(12)
                
        # MENUS
        filemenu = wx.Menu()
        
        fileAbout = filemenu.Append(wx.ID_ANY, "&About DSCA"," Information about this program")
        filemenu.AppendSeparator()
        fileExit = filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program")
        
        managemenu = wx.Menu()
        
        createGroup = managemenu.Append(wx.ID_ANY, "Create Group", "Create a new group if not already in a group")
        leaveGroup = managemenu.Append(wx.ID_ANY, "Leave Group", "Leave the group")
        addUser = managemenu.Append(wx.ID_ANY, "Add User", "Add user to group")
        managemenu.AppendSeparator()
        channelInfo = managemenu.Append(wx.ID_ANY, "Channel Info", "Get channels and channel membership info")
        channelMembership = managemenu.Append(wx.ID_ANY, "Join/Leave Channel", "Join or leave a channel")
        channelLog = managemenu.Append(wx.ID_ANY, "Channel Log History", "Get message history of a channel")
        
        menubar = wx.MenuBar()
        menubar.Append(filemenu, "&File")
        menubar.Append(managemenu, "Manage")
        self.SetMenuBar(menubar)
        
        # Set events.
        self.Bind(wx.EVT_MENU, self.OnAbout, fileAbout)
        self.Bind(wx.EVT_MENU, self.OnExit, fileExit)
        self.Bind(wx.EVT_MENU, self.OnCreateGroup, createGroup)
        self.Bind(wx.EVT_MENU, self.OnLeaveGroup, leaveGroup)
        self.Bind(wx.EVT_MENU, self.OnAddUser, addUser)
        self.Bind(wx.EVT_MENU, self.OnChannelInfo, channelInfo)
        self.Bind(wx.EVT_MENU, self.OnChannelMembership, channelMembership)
        self.Bind(wx.EVT_MENU, self.OnLogHistory, channelLog)
        
        mainPanel = wx.Panel(self, wx.ID_ANY)
        # Beginning of main window
        vbox = wx.BoxSizer(wx.VERTICAL)
        
        # Top button bar
        hbox0 = wx.BoxSizer(wx.HORIZONTAL)
        
        stxt = wx.StaticText(mainPanel, wx.ID_ANY, "IP Address: ")
        stxt.SetFont(font)
        hbox0.Add(stxt, 0, wx.ALL, 5)
        
        self.ip = wx.TextCtrl(mainPanel, wx.ID_ANY)
        hbox0.Add(self.ip, 2, wx.EXPAND | wx.ALL, 5)
        
        stxt1 = wx.StaticText(mainPanel, wx.ID_ANY, "Port: ")
        stxt1.SetFont(font)
        hbox0.Add(stxt1, 0, wx.ALL, 5)
        
        self.port = wx.TextCtrl(mainPanel, wx.ID_ANY)
        hbox0.Add(self.port, 1, wx.EXPAND | wx.ALL, 5)
        
        startButton = wx.Button(mainPanel, wx.ID_ANY, label="start")
        hbox0.Add(startButton, 0, wx.EXPAND | wx.ALL, 5)
        startButton.Bind(wx.EVT_BUTTON, self.OnStart)
        
        stopButton = wx.Button(mainPanel, wx.ID_ANY, label="stop")
        hbox0.Add(stopButton, 0, wx.EXPAND | wx.ALL, 5)
        stopButton.Bind(wx.EVT_BUTTON, self.OnStop)
        
        logoutButton = wx.Button(mainPanel, wx.ID_ANY, label="logout")
        hbox0.Add(logoutButton, 0, wx.EXPAND | wx.ALL, 5)
        logoutButton.Bind(wx.EVT_BUTTON, self.OnLogout)
        
        vbox.Add(hbox0, 0, wx.EXPAND)

        hbox02 = wx.BoxSizer(wx.HORIZONTAL)
        vbox02 = wx.BoxSizer(wx.VERTICAL)
        self.curNick = wx.StaticText(mainPanel, wx.ID_ANY, "No Nick")
        self.curNick.SetFont(font)
        self.curNick.SetForegroundColour('BLUE')
        vbox02.Add(self.curNick, 0, wx.LEFT | wx.RIGHT, 20)
        self.curAddr = wx.StaticText(mainPanel, wx.ID_ANY, "No Address")
        self.curAddr.SetFont(font)
        self.curAddr.SetForegroundColour('BLUE')
        vbox02.Add(self.curAddr, 0, wx.LEFT | wx.RIGHT, 20)
        hbox02.Add(vbox02, 2, wx.EXPAND)
        
        self.newNick = wx.TextCtrl(mainPanel, wx.ID_ANY)
        hbox02.Add(self.newNick, 1, wx.EXPAND | wx.LEFT, 50)
        nickButton = wx.Button(mainPanel, wx.ID_ANY, label="Change Nick")
        hbox02.Add(nickButton, 0, wx.EXPAND | wx.ALL, 5)
        nickButton.Bind(wx.EVT_BUTTON, self.OnNickChange)
        
        vbox.Add(hbox02, 0, wx.EXPAND)
                
        # main section of window (chat/group view)
        mainHbox = wx.BoxSizer(wx.HORIZONTAL)
        panel1 = wx.Panel(mainPanel, -1)
        panel1.SetBackgroundColour(wx.LIGHT_GREY)
        mainHbox.Add(panel1, 7, wx.EXPAND | wx.ALL, 5)
        vbox1 = wx.BoxSizer(wx.VERTICAL)
        
        # chat tabs, 1 tab per channel + a network tab for debugging info
        self.nb = wx.Notebook(panel1)
        # initially only lobby and network tabs exist
        self.lobby = wx.TextCtrl(self.nb, -1, style= wx.TE_MULTILINE | wx.TE_READONLY)
        self.network = wx.TextCtrl(self.nb, -1, style= wx.TE_MULTILINE | wx.TE_READONLY)
        # initialize tab dictionary
        self.channelTabs = {}
        self.nb.AddPage(self.lobby, 'lobby')
        self.nb.AddPage(self.network, 'network')
        vbox1.Add(self.nb, 1, wx.EXPAND)
        
        # MESSAGE SENDING INTERFACE
        panel11 = wx.Panel(panel1, -1)
        hbox1 = wx.BoxSizer(wx.HORIZONTAL)
        # channel selection control
        self.channelSel = wx.Choice(panel11)
        hbox1.Add(self.channelSel, 0, wx.LEFT | wx.TOP, 5)
        # message to send box
        self.msg = wx.TextCtrl(panel11, -1)
        hbox1.Add(self.msg, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, 10)
        # send button
        send = wx.Button(panel11, wx.ID_ANY, label='send')
        hbox1.Add(send, 0, wx.TOP | wx.RIGHT, 5)
        send.Bind(wx.EVT_BUTTON, self.OnSendMsg)
        panel11.SetSizer(hbox1)
        
        vbox1.Add(panel11, 0, wx.EXPAND | wx.BOTTOM, 5)
        panel1.SetSizer(vbox1)
        
        # group view (online and offline users)
        groupVbox = wx.BoxSizer(wx.VERTICAL)
        headerPanel1 = wx.Panel(mainPanel, -1)
        headerHbox1 = wx.BoxSizer(wx.HORIZONTAL)
        onlineHeader = wx.StaticText(headerPanel1, -1, "ONLINE")
        onlineHeader.SetFont(font)
        headerHbox1.Add(onlineHeader, 0, wx.EXPAND | wx.ALL, 5)
        headerPanel1.SetBackgroundColour('GREEN')
        onlineHeader.SetForegroundColour('WHITE')
        headerPanel1.SetSizer(headerHbox1)
        groupVbox.Add(headerPanel1, 0, wx.EXPAND)
        self.onlineList = wx.ListBox(mainPanel, wx.ID_ANY, style=wx.LB_SINGLE) 
        groupVbox.Add(self.onlineList, 1, wx.EXPAND)
        headerPanel2 = wx.Panel(mainPanel, -1)
        headerHbox2 = wx.BoxSizer(wx.HORIZONTAL)
        offlineHeader = wx.StaticText(headerPanel2, -1, "OFFLINE")
        offlineHeader.SetFont(font)
        headerHbox2.Add(offlineHeader, 0, wx.EXPAND | wx.ALL, 5)
        headerPanel2.SetBackgroundColour('RED')
        offlineHeader.SetForegroundColour('WHITE')
        headerPanel2.SetSizer(headerHbox2)
        groupVbox.Add(headerPanel2, 0, wx.EXPAND)
        self.offlineList = wx.ListBox(mainPanel, wx.ID_ANY, style=wx.LB_SINGLE)
        groupVbox.Add(self.offlineList, 1, wx.EXPAND)
        mainHbox.Add(groupVbox, 2, wx.EXPAND | wx.ALL, 5)
        vbox.Add(mainHbox, 3, wx.EXPAND | wx.ALL, 5)

        # FILE TRANSFER INTERFACE
        filePanel = wx.Panel(mainPanel, -1)
        fileVbox = wx.BoxSizer(wx.VERTICAL)
        
        # Change download directory controls
        fhbox0 = wx.BoxSizer(wx.HORIZONTAL)
        st0 = wx.StaticText(filePanel, -1, "Download Directory: ")
        st0.SetFont(font)
        fhbox0.Add(st0, 0, wx.ALL, 5)
        self.dldir = wx.TextCtrl(filePanel, wx.ID_ANY, style=wx.TE_READONLY)
        fhbox0.Add(self.dldir, 1, wx.EXPAND | wx.ALL, 5)
        st = wx.StaticText(filePanel, -1, "New Directory: ")
        st.SetFont(font)
        fhbox0.Add(st, 0, wx.ALL, 5)
        self.newdir = wx.TextCtrl(filePanel, wx.ID_ANY)
        fhbox0.Add(self.newdir, 1, wx.EXPAND | wx.ALL, 5)
        changeDir = wx.Button(filePanel, wx.ID_ANY, label="Change")
        fhbox0.Add(changeDir, 0, wx.RIGHT | wx.TOP, 5)
        changeDir.Bind(wx.EVT_BUTTON, self.OnChangeDir)
        fileVbox.Add(fhbox0, 0, wx.EXPAND)
        
        # "Offered Files" label
        fhbox1 = wx.BoxSizer(wx.HORIZONTAL)
        st1 = wx.StaticText(filePanel, -1, "Offered Files")
        st1.SetFont(font)
        fhbox1.Add(st1, 0)
        fileVbox.Add(fhbox1, 0, wx.LEFT | wx.TOP, 5)
        
        # List of offered files and accept button
        fhbox2 = wx.BoxSizer(wx.HORIZONTAL)
        self.offeredList = wx.ListBox(filePanel, wx.ID_ANY, style=wx.LB_SINGLE)
        fhbox2.Add(self.offeredList, 1, wx.LEFT | wx.EXPAND)
        acceptButton = wx.Button(filePanel, wx.ID_ANY, label="Accept File")
        fhbox2.Add(acceptButton, 0, wx.TOP | wx.BOTTOM | wx.RIGHT, 5)
        acceptButton.Bind(wx.EVT_BUTTON, self.OnAcceptFile)
        fileVbox.Add(fhbox2, 1, wx.LEFT | wx.RIGHT | wx.EXPAND)

        # File sending controls
        fhbox3 = wx.BoxSizer(wx.HORIZONTAL)
        # File field
        st2 = wx.StaticText(filePanel, -1, "File: ")
        st2.SetFont(font)
        fhbox3.Add(st2, 0, wx.TOP | wx.LEFT, 5)
        self.file = wx.TextCtrl(filePanel, wx.ID_ANY)
        fhbox3.Add(self.file, 2, wx.EXPAND | wx.ALL, 5)
        # To field
        st3 = wx.StaticText(filePanel, -1, "To: ")
        st3.SetFont(font)
        fhbox3.Add(st3, 0, wx.TOP | wx.LEFT, 5)
        self.fileTo = wx.TextCtrl(filePanel, wx.ID_ANY)
        fhbox3.Add(self.fileTo, 1, wx.EXPAND | wx.ALL, 5)
        # send button
        offerFile = wx.Button(filePanel, wx.ID_ANY, label="Offer File")
        fhbox3.Add(offerFile, 0, wx.RIGHT | wx.TOP, 5)
        offerFile.Bind(wx.EVT_BUTTON, self.OnOfferFile)
        fileVbox.Add(fhbox3, 0, wx.LEFT | wx.RIGHT | wx.EXPAND)
        
        filePanel.SetSizer(fileVbox)
        vbox.Add(filePanel, 1, wx.EXPAND)
        
        mainPanel.SetSizer(vbox)
        self.Centre()
        self.Show(True)
        
        pub.subscribe(self.__OnLog, 'log')
        pub.subscribe(self.__OnMessage, 'message')
        pub.subscribe(self.__OnUpdate, 'update')
        pub.subscribe(self.__OnLeave, 'group.leave')
        
    def OnAbout(self, e):
        dlg = wx.MessageDialog(self, "Distributed Secure Chat Application", "About DSCA", wx.OK)
        dlg.ShowModal()
        dlg.Destroy()
        
    def OnExit(self, e):
        self.Close(True)
    
    def OnCreateGroup(self, e):
        self.chat.createGroup()
    
    def OnLeaveGroup(self, e):
        self.chat.leaveGroup()
    
    def OnAddUser(self, e):
        if self.chat:
            dlg = AddUser(self, wx.ID_ANY, "Add User", self.chat)
            dlg.ShowModal()
            dlg.Destroy()
        
    def OnChannelInfo(self, e):
        if self.chat:
            dlg = ChannelInfo(self, wx.ID_ANY, 'Channel Info', self.chat)
            dlg.ShowModal()
            dlg.Destroy()
        
    def OnChannelMembership(self, e):
        if self.chat:
            dlg = ChannelMembership(self, wx.ID_ANY, 'Channel Membership', self.chat)
            dlg.ShowModal()
            dlg.Destroy()
        self.UpdateChannels()
    
    def OnLogHistory(self, e):
        if self.chat:
            dlg = ChannelLog(self, wx.ID_ANY, 'Channel Log History', self.chat)
            dlg.ShowModal()
            dlg.Destroy()
        
    def UpdateChannels(self):
        channels = self.chat.getChannels()
        items = channels.keys()
        items.append('lobby')
        self.channelSel.SetItems(items)
        tabs = self.channelTabs.keys()
        for chan in items:
            if (chan not in tabs) and (chan != 'lobby'):
                index = self.nb.GetPageCount() - 1
                self.channelTabs[chan] = ChannelPage(self.nb, index)
                self.UpdateChannel(chan)
                self.nb.InsertPage(index, self.channelTabs[chan], chan)
        for tab in tabs:
            if tab not in items:
                page = self.channelTabs[tab]
                self.nb.SetSelection(0)
                index = page.index
                self.nb.DeletePage(index)
                del self.channelTabs[tab]
                for i in range(index, (self.nb.GetPageCount() - 1)):
                    page2 = self.nb.GetPage(i)
                    page2.index = i
        
    def OnStart(self, e):
        '''Starts a chat session. Not necessary to specifically enable output to the GUI. 
        Output is dependent upon subscribing to events published by Chat.'''
        atexit.register(self.quiet_stop)
        if not self.chat:
            if (not self.ip.IsEmpty()) and (not self.port.IsEmpty()):
                ipString = str(self.ip.GetLineText(0))
                ipString = ipString.strip()
                portString = str(self.port.GetLineText(0))
                portString = portString.strip()
                listen_address = (str(ipString), int(portString))
                self.chat = Chat(listen_address)
            else:
                self.chat = Chat()
            
            self.ip.Clear()
            self.port.Clear()
            self.curAddr.SetLabel(str(self.chat.getListenAddress()))
            self.dldir.Clear()
            self.dldir.AppendText(os.path.abspath(self.chat.getDownloadDir()))
            
        elif (not self.ip.IsEmpty()) and (not self.port.IsEmpty()):
            dlg = wx.MessageDialog(self, 'Start failed.\nChat already started.', "WARNING", wx.OK)
            dlg.ShowModal()
            dlg.Destroy()
            return
        try:
            self.chat.start()
        except:
            self.chat = None
            raise
        
    def quiet_stop(self):
        try:
            self.chat.stop()
            self.chat = None
        except:
            pass
        
    def OnStop(self, e):
        if self.chat:
            self.chat.stop()
        self.chat = None
    
    def OnLogout(self, e):
        self.chat.logout()
    
    def OnSendMsg(self, e):
        channel = self.channelSel.GetStringSelection()
        if not channel:
            channel = 'lobby'
        self.chat.sendMessage(self.msg.GetLineText(0), channel)
        self.msg.Clear()
        
    def OnChangeDir(self, e):
        if not self.newdir.IsEmpty():
            self.chat.setDownloadDir(self.newdir.GetLineText(0))
            self.newdir.Clear()
            self.dldir.Clear()
            self.dldir.AppendText(os.path.abspath(self.chat.getDownloadDir()))
    
    def OnAcceptFile(self, e):
        selection = str(self.offeredList.GetStringSelection())
        cookie = (selection.split(':'))[1]
        cookie = cookie.lstrip()
        self.network.AppendText("I made you a cookie... %s" % cookie)
        try:
            self.chat.acceptFileOffer(cookie)
        except InvalidFileCookieException:
            dlg = wx.MessageDialog(self, "File Accept Failed", "FAIL", wx.OK)
            dlg.ShowModal()
            dlg.Destroy()
        
        
    def OnOfferFile(self, e):
        fpath = os.path.abspath(self.file.GetLineText(0))
        nick = str(self.fileTo.GetLineText(0))
        self.file.Clear()
        self.fileTo.Clear()
        try:
            target_addr = self.chat.getAddressOfNick(nick)
        except NickNotFoundException:
            dlg = wx.MessageDialog(self, "'%s' is not a valid user" % nick, "ERROR", wx.OK)
            dlg.ShowModal()
            dlg.Destroy()
        else:
            try:
                self.chat.offerFile(fpath, target_addr)
            except FileNotFoundException:
                dlg = wx.MessageDialog(self, "File '%s' does not exist" % fpath, "ERROR", wx.OK)
                dlg.ShowModal()
                dlg.Destroy()
        
    def OnNickChange(self, e):
        if not self.newNick.IsEmpty():
            newNick = str(self.newNick.GetLineText(0))
            newNick = newNick.strip()
            if newNick != '':
                self.newNick.Clear()
                self.chat.changeNick(newNick)
    
    def __OnLog(self, message):
        channel = message.data['channel']
        msg = message.data['msg']
        if 'time' in message.data:
            sec = message.data['time']
            tstring = time.strftime("%H:%M:%S  ", time.localtime(sec))
            msg = tstring + msg
        wx.CallAfter(self.AfterLog, channel, msg)
        
    def AfterLog(self, channel, msg):
        if channel == 'lobby':
            self.lobby.AppendText(msg + '\n')
        elif channel in self.channelTabs:
            tab = self.channelTabs[channel]
            tab.text.AppendText(msg + '\n')
        self.network.AppendText(msg + '\n')
    
    def __OnMessage(self, message):
        channel = message.data['channel']
        msg = message.data['msg']
        if 'time' in message.data:
            sec = message.data['time']
            tstring = time.strftime("%H:%M:%S  ", time.localtime(sec))
            msg = tstring + msg
        wx.CallAfter(self.AfterMessage, channel, msg)
        
    def AfterMessage(self, channel, msg):
        if channel == 'lobby':
            self.lobby.AppendText(msg + '\n')
        elif channel in self.channelTabs:
           tab = self.channelTabs[channel]
           tab.text.AppendText(msg + '\n')
            
        
    def __OnUpdate(self, message):
        field = message.data['field']
        if 'value' in message.data:
            value = message.data['value']
        if field == 'myNick':
            wx.CallAfter(self.UpdateMyNick, value)
        elif field == 'nick':
            wx.CallAfter(self.UpdateNick, value)
        elif field == 'channel':
            wx.CallAfter(self.UpdateChannel, value)
        elif field == 'group':
            wx.CallAfter(self.UpdateGroup)
        elif field == 'fileOffers':
            wx.CallAfter(self.UpdateOfferedFiles)
    
    def UpdateMyNick(self, nick):
        try:
            self.curNick.SetLabel(nick)
        except:
            pass
        self.UpdateNick(self.chat.getListenAddress())
        
    def UpdateNick(self, sender):
        for channelName in self.chat.channels:
            channel = self.chat.getChannel(channelName)
            if sender in channel.getUserAddresses():
                self.UpdateChannel(channelName)
        self.UpdateGroup()
    
    def UpdateChannel(self, channelName):
        if channelName in self.channelTabs:
            tab = self.channelTabs[channelName]
            chan = self.chat.getChannel(channelName)
            tab.users.SetItems(chan.getUsers())
        
    def UpdateGroup(self):
        if self.chat:
            online = self.chat.getOnlineUsers()
            offline = self.chat.getOfflineUsers()
            onlineNicks = []
            for addr in online:
                nick = self.chat.getNickOfAddress(addr)
                onlineNicks.append(nick)
            offlineNicks = []
            for addr in offline:
                nick = self.chat.getNickOfAddress(addr)
                offlineNicks.append(nick)
            self.onlineList.SetItems(onlineNicks)
            self.offlineList.SetItems(offlineNicks)
        else:
            self.onlineList.Clear()
            self.offlineList.Clear()
    
    def UpdateOfferedFiles(self):
        foss = self.chat.getFileOffers()
        now = time.time()
        files = []
        for cookie in foss:
            fof = foss[cookie]
            if fof['expires_on'] < now:
                continue
            files.append("%s: %s" % (os.path.basename(fof['filename']), cookie))
        self.offeredList.SetItems(files)
        
    def __OnLeave(self, message=None):
        wx.CallAfter(self.Leave)
    
    def Leave(self):
        self.curNick.SetLabel("No Nick")
        self.curAddr.SetLabel("No Address")
        for i in range(1, (self.nb.GetPageCount() -1)):
            self.nb.DeletePage(1)
        self.lobby.Clear()
        self.network.Clear()
        self.onlineList.Clear()
        self.offlineList.Clear()
        self.chat = None