def __init__(self, screen, protocol): ''' __init__: Initializes the application window with both a display and an input screen. :param screen: A curses window object, representing the root window that the application runs in :param protocol: A protocols.Protocol object that the application will use to communicate with the server. ''' self.screen = screen curses.noecho() curses.cbreak() self.screen.keypad(1) self.screen.border(0) height, width = self.screen.getmaxyx() display_window = curses.newwin(height - 10, width, 1, 1) display_window.box() input_window = curses.newwin(9, width, height-10, 1) input_window.box() self.display = DisplayScreen(display_window) self.input_w = InputWindow(input_window) input_window.move(0, 0) self.P = protocol self.current_user = None self.mode = -1 self.exited = False self.screen.refresh() display_window.refresh() input_window.refresh()
class Application(object): ''' Application represents the entire state of the application: the current LoginUser (if applicable), the input window, the display window, the message fetching thread, and the *mode*. There are two modes, command mode and chat mode, and the current mode is stored in the mode field. mode -1 corresponds to "command mode", in which the command input window is displayed and the user's input mode m for m != -1 corresponds to "chat mode", in which the user can send and see their messages with another group or user. m corresponds to the conversation id of the group/user with which the user is chatting. ''' DOWN = 1 UP = -1 ESC_KEY = 27 screen = None cmd_history = [] prompt = "> " num_args = { "help" : {0, 1}, "login" : {1}, "mk-user" : {1}, "delete-account" : {0}, "ls-groups" : {0, 1}, "ls-users" : {0, 1}, "ls-group-members" : {1}, "mk-group" : {1}, "add-group-member" : {2}, "remove-group-member" : {2}, "talk-with" : {2}, "logout" : {0}, } usage = { 'login': "******", 'mk_user': "******", 'delete-account': "delete-account", 'ls-groups': "ls-groups [pattern (optional)]*", 'ls-users': "ls-users [pattern (optional)]*", 'ls-group-members': "ls-group-members [g_name]*", 'mk-group': "mk-group [g_name]*", 'add-group-member': "add-group-member [g_name] [username]*", 'remove-group-member': "remove-group-member [g_name] [username]*", 'talk-with': "talk-with [group/user] [group or user name]*", 'logout': "logout*", '': "*must be logged in" } def __init__(self, screen, protocol): ''' __init__: Initializes the application window with both a display and an input screen. :param screen: A curses window object, representing the root window that the application runs in :param protocol: A protocols.Protocol object that the application will use to communicate with the server. ''' self.screen = screen curses.noecho() curses.cbreak() self.screen.keypad(1) self.screen.border(0) height, width = self.screen.getmaxyx() display_window = curses.newwin(height - 10, width, 1, 1) display_window.box() input_window = curses.newwin(9, width, height-10, 1) input_window.box() self.display = DisplayScreen(display_window) self.input_w = InputWindow(input_window) input_window.move(0, 0) self.P = protocol self.current_user = None self.mode = -1 self.exited = False self.screen.refresh() display_window.refresh() input_window.refresh() def poll_for_messages(self, delay = 1): ''' poll_for_messages: An infinite loop, supposed to be run in a separate thread, that polls the server every delay seconds for new messages for the logged in user (or waits until a user logs in) :param delay: number of seconds between each polling attempt :return: None ''' while not self.exited: time.sleep(delay) if self.current_user is None: continue msgs = self.P.fetch_messages(self.current_user.u_id, self.current_user.checkpoint) for m in msgs: self.current_user.add_message(m) if self.mode != -1: # This setLines is necessary since in Python, an empty list and a # nonempty list will point to different objects in memory self.display.setLines(self.current_user.formatted_messages[self.mode]) self.displayScreen() def run(self): ''' run: The main loop of the application. Waits for user inputs, and acts accordingly. :return: None ''' thread.start_new_thread(self.poll_for_messages, tuple()) self.addCmdLine("Welcome to Chat!") self.addCmdLine("Login to start chatting, or type help to get a list of valid commands.") while True: try: assert not curses.isendwin() if self.mode == -1: self.display.setLines(self.cmd_history) else: if self.mode not in self.current_user.formatted_messages: self.current_user.formatted_messages[self.mode] = [] self.display.setLines(self.current_user.formatted_messages[self.mode]) self.displayScreen() # get user command c = self.screen.getch() if c == curses.KEY_UP: self.display.updown(self.UP) elif c == curses.KEY_DOWN: self.display.updown(self.DOWN) elif c == self.ESC_KEY: self.mode = -1 self.display.setLines(self.cmd_history, adjust=True) elif curses.keyname(c) == '^U': self.display.pageup() elif curses.keyname(c) == '^D': self.display.pagedown() elif c == ord('\n'): # Interpret command if self.mode == -1: self.execute_cmd(self.input_w.line) else: self.P.send_message(from_name=self.current_user.username, dest_id=self.mode, msg=self.input_w.line) self.input_w.clearLine() else: self.input_w.putchar(c) except KeyboardInterrupt as e: self.exited = True raise e except Exception as e: pass def displayScreen(self): ''' displayScreen: updates the display and input screens. :return: None ''' self.display.displayScreen() self.input_w.displayScreen() def addCmdLine(self, line): ''' addCmdLine: add a command to the command history. :param line: string; the command to add :return: None ''' self.cmd_history.append(line) def execute_cmd(self, cmd): ''' execute_cmd: interprets the given command to perform an action. :param cmd: string; the command to execute :return: None ''' # add the cmd to the outputLines cmd_args = cmd.strip().split() if not cmd_args: return self.addCmdLine(self.prompt + cmd) is_valid = cmd_args[0] in self.num_args and len(cmd_args[1:]) in self.num_args[cmd_args[0]] if not is_valid: self.addCmdLine("Invalid Command! Valid commands and usage:") for line in self.usage.values(): self.addCmdLine("\t" + line) else: # execute the cmd if cmd_args[0] == "help": if len(cmd_args) == 1: self.addCmdLine("Valid commands and usage:") for line in self.usage.values(): self.addCmdLine("\t" + line) self.addCmdLine('Shortcuts:') self.addCmdLine('\tCTRL-a: move cursor to start of line') self.addCmdLine('\tCTRL-e: move cursor to end of line') self.addCmdLine('\tCTRL-k: delete everything after cursor') self.addCmdLine('\tCTRL-u: page up') self.addCmdLine('\tCTRL-d: page down') self.addCmdLine('\tSHIFT-UP: previous command') self.addCmdLine('\tSHIFT-DOWN: next command') else: if cmd_args[0] in self.usage: self.addCmdLine(self.usage[cmd_args[1]]) else: self.addCmdLine("%s is not a valid commands. Valid commands and usage:" % cmd_args[1]) for line in self.usage.values(): self.addCmdLine("\t" + line) elif cmd_args[0] == "login": if self.current_user is not None: self.addCmdLine("Yo! You are already logged in as %s" % self.current_user.username) return username = cmd_args[1] users = self.P.list_accounts(username) if users == []: self.addCmdLine("Username %s does not exist. Please create a new account." % cmd_args[1]) else: user = users[0] self.current_user = LoginUser(user.username, user.u_id) self.addCmdLine("Logged in as %s" % cmd_args[1]) elif cmd_args[0] == "mk-user": if self.current_user is not None: self.addCmdLine("Cannot create a user while logged in") return user = self.P.create_account(cmd_args[1]) if user is not None: self.current_user = LoginUser(cmd_args[1], user.u_id) self.addCmdLine("Created account and logged in as %s" % cmd_args[1]) else: self.addCmdLine("Could not create account") # all following commands must be called while logged in elif self.current_user is None: self.addCmdLine("Cannot call %s while not logged in" % cmd_args[0]) return elif cmd_args[0] == "ls-groups": response = self.P.list_groups() if len(cmd_args) == 1 else self.P.list_groups(cmd_args[1]) for group in response: self.addCmdLine("Group Name: %s\t Group ID: %d" % (group.g_name, group.g_id)) elif cmd_args[0] == "ls-users": response = self.P.list_accounts() if len(cmd_args) == 1 else self.P.list_accounts(cmd_args[1]) for user in response: self.addCmdLine("Username: %s\t User ID: %d" % (user.username, user.u_id)) elif cmd_args[0] == "ls-group-members": response = self.P.list_group_members(cmd_args[1]) for user in response: self.addCmdLine("Username: %s\t User ID: %d" % (user.username, user.u_id)) elif cmd_args[0] == "mk-group": response = self.P.create_group(cmd_args[1]) if response is not None: self.addCmdLine("Created group %s" % cmd_args[1]) else: self.addCmdLine("Could not create group: %s" % response) elif cmd_args[0] == "add-group-member": response = self.P.add_group_member(cmd_args[1], cmd_args[2]) if response is None: self.addCmdLine("Added %s from group %s" % (cmd_args[2], cmd_args[1])) else: self.addCmdLine(response) elif cmd_args[0] == "remove-group-member": response = self.P.remove_group_member(cmd_args[1], cmd_args[2]) if response is None: self.addCmdLine("Removed %s from group %s" % (cmd_args[2], cmd_args[1])) else: self.addCmdLine(response) elif cmd_args[0] == "delete-account": response = self.P.remove_account(self.current_user.username) self.current_user = None if response is None: self.addCmdLine("Account Deleted") else: self.addCmdLine(response) elif cmd_args[0] == "logout": self.current_user = None self.addCmdLine("Logged out of account") elif cmd_args[0] == "talk-with": tpe = cmd_args[1] if tpe != "user" and tpe != "group": self.addCmdLine("Type of conversation must be either user or group.") return to_name = cmd_args[2] if tpe == "user": convo_id = self.P.create_conversation(self.current_user.username, to_name) self.mode = int(convo_id) elif tpe == "group": groups = self.P.list_groups(to_name) if groups == []: self.addCmdLine("Group %s does not exist." % to_name) else: self.mode = int(groups[0].g_id) self.display.setLines(self.cmd_history, adjust=True)