Improved CLI and chat connection.

parent 5fafb3dc
import asyncore, socket import asyncore, socket
from Queue import Queue from Queue import Queue
import re import re
import random
class ClientConnection(object, asyncore.dispatcher): class ClientConnection(object, asyncore.dispatcher):
...@@ -12,7 +13,11 @@ class ClientConnection(object, asyncore.dispatcher): ...@@ -12,7 +13,11 @@ class ClientConnection(object, asyncore.dispatcher):
self.authenticated = False self.authenticated = False
self.authentification_sent = False self.authentification_sent = False
self.user = 'Anonymous' self.retrieved_user_list = False
self.request_user_list_sent = False
# Generate a random user name
self.user = 'Anonymous' + str(int(random.random()*10000))
# Connection/chat event callbacks # Connection/chat event callbacks
self.event_list = { } self.event_list = { }
...@@ -20,9 +25,14 @@ class ClientConnection(object, asyncore.dispatcher): ...@@ -20,9 +25,14 @@ class ClientConnection(object, asyncore.dispatcher):
self.send_queue = Queue() self.send_queue = Queue()
self.send_buffer = '' self.send_buffer = ''
def init_loop(self):
"""Initialise the asyncore loop (blocking)."""
# Set a timeout of 0.1 seconds and use poll() when available.
asyncore.loop(.1, True)
def debug_log(self, msg): def debug_log(self, msg):
if 'debug' in self.event_list: if 'debug' in self.event_list:
self.event_list['debug'](msg) self.event_list['debug'](self, msg)
else: else:
print msg print msg
...@@ -31,14 +41,25 @@ class ClientConnection(object, asyncore.dispatcher): ...@@ -31,14 +41,25 @@ class ClientConnection(object, asyncore.dispatcher):
self.port = addr[1] self.port = addr[1]
super(ClientConnection, self).connect(addr) super(ClientConnection, self).connect(addr)
def retrieve_users(self):
"""
Retreive list of users active at the chat server.
"""
self.send_queue.put('NAMES')
def username(self, name=''): def username(self, name=''):
if not user: if not name:
return self.user return self.user
self.user = name self.user = name
self.authenticated = False
self.authentification_sent = True
self.send_queue.put('USER %s' % name)
def handle_connect(self): def handle_connect(self):
"""Called when a connection is established.""" """Called when a connection is established."""
pass if 'connect' in self.event_list:
self.event_list['connect'](self)
def handle_close(self): def handle_close(self):
self.close() self.close()
...@@ -48,16 +69,21 @@ class ClientConnection(object, asyncore.dispatcher): ...@@ -48,16 +69,21 @@ class ClientConnection(object, asyncore.dispatcher):
multi_line = False multi_line = False
response_format = False response_format = False
# Receive a (multiline) message.
while True: while True:
chunk = self.recv(1) chunk = self.recv(1)
if not chunk: if not chunk:
raise RuntimeError("socket connection broken") raise RuntimeError('socket connection broken')
if chunk in ['-','+']: if chunk in ['-','+']:
response_format = True response_format = True
elif chunk == ':': elif chunk == ':':
multi_line = True multi_line = True
elif chunk == '\n' and buf[-1] == '\r': elif chunk == '\n' and buf[-1] == '\r':
if not multi_line or buf[-3:] == '\r\n\r': if not multi_line:
break
if buf[-3:] == '\r\n\r':
# Remove trailing '\n\r' from multi line response
buf = buf[:-2]
break break
multi_line = False multi_line = False
buf += chunk buf += chunk
...@@ -65,6 +91,7 @@ class ClientConnection(object, asyncore.dispatcher): ...@@ -65,6 +91,7 @@ class ClientConnection(object, asyncore.dispatcher):
buf = buf[:-1] buf = buf[:-1]
self.debug_log('< %s' % buf) self.debug_log('< %s' % buf)
# Invoke the proper callback function.
if not self.verified: if not self.verified:
self.verify_server(buf) self.verify_server(buf)
elif response_format: elif response_format:
...@@ -86,28 +113,46 @@ class ClientConnection(object, asyncore.dispatcher): ...@@ -86,28 +113,46 @@ class ClientConnection(object, asyncore.dispatcher):
self.debug_log('Failed to verify connection') self.debug_log('Failed to verify connection')
sys.exit(-1) sys.exit(-1)
if 'verify' in self.event_list:
self.event_list['verify'](buf, match.group(1))
else:
self.debug_log('Server version is %s' % match.group(1)) self.debug_log('Server version is %s' % match.group(1))
if 'verify' in self.event_list:
self.event_list['verify'](self, buf, match.group(1))
self.verified = True self.verified = True
self.send_queue.put('CHAT') self.send_queue.put('CHAT')
def parse_response(self, buf): def parse_response(self, buf):
if 'response' in self.event_list:
self.event_list['response'](self, buf)
if not self.authenticated: if not self.authenticated:
if not self.authentification_sent: if not self.authentification_sent:
self.send_queue.put('USER %s' % self.user) self.send_queue.put('USER %s' % self.user)
self.authentification_sent = True self.authentification_sent = True
elif buf[1:10] == 'Username':
# TODO: handle 'username is taken'.
self.authenticated = True
if 'authenticated' in self.event_list:
self.event_list['authenticated'](self, self.user)
#if not self.retrieved_user_list:
# if not self.request_user_list_sent:
# self.request_user_list_sent = True
# self.retrieve_users()
# else:
# # TODO: process user list
# self.retrieved_user_list = True
# #self.debug_log('i Users: %s' % str(buf.split('\r\n')[:-1]))
def parse_notification(self, buf): def parse_notification(self, buf):
if 'notify' in self.event_list: if 'notify' in self.event_list:
self.event_list['notify'](msg) self.event_list['notify'](self, buf)
def writable(self): def writable(self):
return not self.send_queue.empty() or len(self.send_buffer) write = not self.send_queue.empty() or len(self.send_buffer)
return write
if __name__ == '__main__': if __name__ == '__main__':
client = ClientConnection() client = ClientConnection()
client.connect(('ow150.science.uva.nl', 16897)) client.connect(('ow150.science.uva.nl', 16897))
asyncore.loop() client.init_loop()
...@@ -13,4 +13,28 @@ class BaseBar(object): ...@@ -13,4 +13,28 @@ class BaseBar(object):
# Initialise bar # Initialise bar
self.bar = curses.newwin(1, width, top, left) self.bar = curses.newwin(1, width, top, left)
self.color_pair = curses.color_pair(0)
self._prefix = ''
def prefix(self, value=''):
if not value:
return self._prefix
self._prefix = value
self.display(self._msg)
def display(self, msg):
self._msg = msg
if self.prefix:
msg = self.prefix() + ' ' + msg
# Curses will raise a 'curses.error' when the last possible character is
# written. This exception should therefore always be catched. The raised
# exception truncates the message to the width of the bar.
try:
self.bar.addstr(0, 0, msg.ljust(self.width), self.color_pair)
except curses.error:
pass
self.refresh()
def refresh(self):
self.bar.refresh()
import curses import curses
from Queue import Queue
class BaseWindow(object): class BaseWindow(object):
def __init__(self, main, top, left, height, width): def __init__(self, main, top, left, height, width):
...@@ -12,29 +13,37 @@ class BaseWindow(object): ...@@ -12,29 +13,37 @@ class BaseWindow(object):
self.width = width self.width = width
self.lines = [] self.lines = []
self.line_queue = Queue()
# Initialise window # Initialise window
self.window = curses.newwin(height, width, top, left) self.window = curses.newwin(height, width, top, left)
def clear(self): def clear(self):
self.lines = []
self.window.clear() self.window.clear()
self.window.refresh() self.refresh()
def redraw(self): def redraw(self):
for y in range(self.height): self.window.clear()
self.window.move(y, 0)
while not self.line_queue.empty():
self.lines.append(self.line_queue.get())
if self.displayed_help:
return
for y, line in enumerate(self.lines[-self.height:]): for y, line in enumerate(self.lines[-self.height:]):
try: try:
self.window.addstr(y, 0, line.ljust(self.width)) self.window.addstr(y, 0, line.ljust(self.width))
except curses.error: except curses.error:
pass pass
self.refresh()
def refresh(self):
self.window.refresh() self.window.refresh()
def append(self, msg): def append(self, msg):
while msg: while msg:
self.lines += [msg[:self.width]] self.line_queue.put(msg[:self.width])
msg = msg[self.width:] msg = msg[self.width:]
self.redraw() self.redraw()
...@@ -111,7 +111,7 @@ class CLI: ...@@ -111,7 +111,7 @@ class CLI:
# If the help screen is currently displayed, hide it. # If the help screen is currently displayed, hide it.
if main.chat_window.displayed_help: if main.chat_window.displayed_help:
main.chat_window.displayed_help = False main.chat_window.displayed_help = False
main.chat_window.clear() main.chat_window.redraw()
else: else:
# Stop user input handler (which will exit the application). # Stop user input handler (which will exit the application).
main.execute('quit') main.execute('quit')
...@@ -130,13 +130,16 @@ class CLI: ...@@ -130,13 +130,16 @@ class CLI:
# Initialise the connection and bind event handlers. # Initialise the connection and bind event handlers.
main.connection = ClientConnection() main.connection = ClientConnection()
main.connection.main = main main.connection.main = main
main.connection.event_list['debug'] = main.debug_log main.connection.event_list['debug'] = main.debug_event
main.connection.event_list['connect'] = main.connect_event
main.connection.event_list['notify'] = main.notify_event
main.connection.event_list['authenticated'] = \
main.authenticated_event
main.connection.connect((host, port)) main.connection.connect((host, port))
# The chat connection is ran in a separate thread. # The chat connection is ran in a separate thread.
import asyncore
self.connection_thread = threading.Thread() self.connection_thread = threading.Thread()
self.connection_thread.run = asyncore.loop self.connection_thread.run = main.connection.init_loop
self.connection_thread.start() self.connection_thread.start()
def disconnect(main): def disconnect(main):
...@@ -183,6 +186,8 @@ All commands listed below should be preceded by a slash: ...@@ -183,6 +186,8 @@ All commands listed below should be preceded by a slash:
help This help page. help This help page.
quit Quit this chat application (shortcut: ^c). quit Quit this chat application (shortcut: ^c).
user Change your username to USER.
""" """
for i, line in enumerate(help_text.split('\n')): for i, line in enumerate(help_text.split('\n')):
...@@ -203,6 +208,9 @@ All commands listed below should be preceded by a slash: ...@@ -203,6 +208,9 @@ All commands listed below should be preceded by a slash:
sys.exit(0) sys.exit(0)
def user(main, name):
main.connection.username(name)
def _exec(main, cmd): def _exec(main, cmd):
# Skip the leading "/exec " of the command # Skip the leading "/exec " of the command
exec cmd[6:] exec cmd[6:]
...@@ -214,11 +222,34 @@ All commands listed below should be preceded by a slash: ...@@ -214,11 +222,34 @@ All commands listed below should be preceded by a slash:
'disconnect': disconnect, 'disconnect': disconnect,
'exec': _exec, 'exec': _exec,
'help': help, 'help': help,
'quit': quit 'quit': quit,
'user': user,
} }
def debug_log(self, msg): def refresh(self):
self.debug_window.append(msg) self.chat_window.refresh()
self.debug_window.refresh()
self.info_bar.refresh()
self.command_bar.refresh()
self.command_bar.focus()
self.stdscr.refresh()
def authenticated_event(self, conn, username):
self.info_bar.prefix('[%s @ %s]' % (username, conn.host))
self.refresh()
def connect_event(self, conn):
self.info_bar.prefix('[%s]' % conn.host)
self.display_info('')
self.refresh()
def debug_event(self, conn, msg):
self.chat_window.append(msg)
self.refresh()
def notify_event(self, conn, msg):
self.chat_window.append(msg)
self.refresh()
def display_info(self, msg): def display_info(self, msg):
""" """
...@@ -227,6 +258,7 @@ All commands listed below should be preceded by a slash: ...@@ -227,6 +258,7 @@ All commands listed below should be preceded by a slash:
""" """
self.info_bar.display(msg) self.info_bar.display(msg)
self.refresh()
def execute(self, cmd, args=[], raw_command=''): def execute(self, cmd, args=[], raw_command=''):
......
...@@ -16,6 +16,12 @@ class CommandBar(BaseBar): ...@@ -16,6 +16,12 @@ class CommandBar(BaseBar):
# Initialise textbox # Initialise textbox
self.textbox = Textbox(self.bar) self.textbox = Textbox(self.bar)
def focus(self):
"""
Set the cursor to the current positon of this bar.
"""
self.bar.move(*self.bar.getyx())
def handle_input(self, c): def handle_input(self, c):
#if c == curses.KEY_HOME: #if c == curses.KEY_HOME:
# self.x = self.y = 0 # self.x = self.y = 0
......
...@@ -13,10 +13,3 @@ class InfoBar(BaseBar): ...@@ -13,10 +13,3 @@ class InfoBar(BaseBar):
+ ' or "/help" for help.' + ' or "/help" for help.'
self.display(start_message) self.display(start_message)
def display(self, msg):
try:
self.bar.addstr(0, 0, msg.ljust(self.width), self.color_pair)
except curses.error:
pass
self.bar.refresh()
...@@ -10,4 +10,8 @@ except Exception, e: ...@@ -10,4 +10,8 @@ except Exception, e:
curses.echo() curses.echo()
curses.endwin() curses.endwin()
print 'Error:', e print 'Error:', e
#finally:
# curses.nocbreak();
# curses.echo()
# curses.endwin()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment