Improved CLI and chat connection.

parent 5fafb3dc
import asyncore, socket
from Queue import Queue
import re
import random
class ClientConnection(object, asyncore.dispatcher):
......@@ -12,7 +13,11 @@ class ClientConnection(object, asyncore.dispatcher):
self.authenticated = 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
self.event_list = { }
......@@ -20,9 +25,14 @@ class ClientConnection(object, asyncore.dispatcher):
self.send_queue = Queue()
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):
if 'debug' in self.event_list:
self.event_list['debug'](msg)
self.event_list['debug'](self, msg)
else:
print msg
......@@ -31,14 +41,25 @@ class ClientConnection(object, asyncore.dispatcher):
self.port = addr[1]
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=''):
if not user:
if not name:
return self.user
self.user = name
self.authenticated = False
self.authentification_sent = True
self.send_queue.put('USER %s' % name)
def handle_connect(self):
"""Called when a connection is established."""
pass
if 'connect' in self.event_list:
self.event_list['connect'](self)
def handle_close(self):
self.close()
......@@ -48,16 +69,21 @@ class ClientConnection(object, asyncore.dispatcher):
multi_line = False
response_format = False
# Receive a (multiline) message.
while True:
chunk = self.recv(1)
if not chunk:
raise RuntimeError("socket connection broken")
raise RuntimeError('socket connection broken')
if chunk in ['-','+']:
response_format = True
elif chunk == ':':
multi_line = True
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
multi_line = False
buf += chunk
......@@ -65,6 +91,7 @@ class ClientConnection(object, asyncore.dispatcher):
buf = buf[:-1]
self.debug_log('< %s' % buf)
# Invoke the proper callback function.
if not self.verified:
self.verify_server(buf)
elif response_format:
......@@ -86,28 +113,46 @@ class ClientConnection(object, asyncore.dispatcher):
self.debug_log('Failed to verify connection')
sys.exit(-1)
self.debug_log('Server version is %s' % match.group(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.event_list['verify'](self, buf, match.group(1))
self.verified = True
self.send_queue.put('CHAT')
def parse_response(self, buf):
if 'response' in self.event_list:
self.event_list['response'](self, buf)
if not self.authenticated:
if not self.authentification_sent:
self.send_queue.put('USER %s' % self.user)
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):
if 'notify' in self.event_list:
self.event_list['notify'](msg)
self.event_list['notify'](self, buf)
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__':
client = ClientConnection()
client.connect(('ow150.science.uva.nl', 16897))
asyncore.loop()
client.init_loop()
......@@ -13,4 +13,28 @@ class BaseBar(object):
# Initialise bar
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
from Queue import Queue
class BaseWindow(object):
def __init__(self, main, top, left, height, width):
......@@ -12,29 +13,37 @@ class BaseWindow(object):
self.width = width
self.lines = []
self.line_queue = Queue()
# Initialise window
self.window = curses.newwin(height, width, top, left)
def clear(self):
self.lines = []
self.window.clear()
self.window.refresh()
self.refresh()
def redraw(self):
for y in range(self.height):
self.window.move(y, 0)
self.window.clear()
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:]):
try:
self.window.addstr(y, 0, line.ljust(self.width))
except curses.error:
pass
self.refresh()
def refresh(self):
self.window.refresh()
def append(self, msg):
while msg:
self.lines += [msg[:self.width]]
self.line_queue.put(msg[:self.width])
msg = msg[self.width:]
self.redraw()
......@@ -111,7 +111,7 @@ class CLI:
# If the help screen is currently displayed, hide it.
if main.chat_window.displayed_help:
main.chat_window.displayed_help = False
main.chat_window.clear()
main.chat_window.redraw()
else:
# Stop user input handler (which will exit the application).
main.execute('quit')
......@@ -130,13 +130,16 @@ class CLI:
# Initialise the connection and bind event handlers.
main.connection = ClientConnection()
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))
# The chat connection is ran in a separate thread.
import asyncore
self.connection_thread = threading.Thread()
self.connection_thread.run = asyncore.loop
self.connection_thread.run = main.connection.init_loop
self.connection_thread.start()
def disconnect(main):
......@@ -183,6 +186,8 @@ All commands listed below should be preceded by a slash:
help This help page.
quit Quit this chat application (shortcut: ^c).
user Change your username to USER.
"""
for i, line in enumerate(help_text.split('\n')):
......@@ -203,6 +208,9 @@ All commands listed below should be preceded by a slash:
sys.exit(0)
def user(main, name):
main.connection.username(name)
def _exec(main, cmd):
# Skip the leading "/exec " of the command
exec cmd[6:]
......@@ -214,11 +222,34 @@ All commands listed below should be preceded by a slash:
'disconnect': disconnect,
'exec': _exec,
'help': help,
'quit': quit
'quit': quit,
'user': user,
}
def debug_log(self, msg):
self.debug_window.append(msg)
def refresh(self):
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):
"""
......@@ -227,6 +258,7 @@ All commands listed below should be preceded by a slash:
"""
self.info_bar.display(msg)
self.refresh()
def execute(self, cmd, args=[], raw_command=''):
......
......@@ -16,6 +16,12 @@ class CommandBar(BaseBar):
# Initialise textbox
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):
#if c == curses.KEY_HOME:
# self.x = self.y = 0
......
......@@ -13,10 +13,3 @@ class InfoBar(BaseBar):
+ ' or "/help" for help.'
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:
curses.echo()
curses.endwin()
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