X-Git-Url: https://mudpy.org/gitweb?a=blobdiff_plain;f=lib%2Fmuff%2Fmuffuser.py;h=1d12be3d11ab16c312737b5dfaa48afa39d1c919;hb=da136e612520ef6a3a19d99563e44b6518f91e7e;hp=ebe0c32fb03716a73ab4281765ce1409426c2be7;hpb=994d6e52ce3d5c719991e8f798642cdb8a24b7d1;p=mudpy.git diff --git a/lib/muff/muffuser.py b/lib/muff/muffuser.py index ebe0c32..1d12be3 100644 --- a/lib/muff/muffuser.py +++ b/lib/muff/muffuser.py @@ -6,7 +6,7 @@ # user accounts are stored in ini-style files supported by ConfigParser import ConfigParser -# test for existence of the account dir with os.listdir and os.mkdir to make it +# os is used to test for existence of the account dir and, if necessary, make it import os # string.replace is used to perform substitutions for color codes and the like @@ -45,7 +45,7 @@ class User: self.password_tries = 1 # the current state of the user - self.state = "entering account name" + self.state = "entering_account_name" # flag to indicate whether a menu has been displayed self.menu_seen = False @@ -65,14 +65,112 @@ class User: # flag to indicate the current echo status of the client self.echoing = True + # the active avatar + self.avatar = None + # an object containing persistent account data self.record = ConfigParser.SafeConfigParser() + def quit(self): + """Log, save, close the connection and remove.""" + if self.name: message = "User " + self.name + else: message = "An unnamed user" + message += " logged out." + muffmisc.log(message) + self.save() + self.connection.close() + self.remove() + + def reload(self): + """Save, load a new user and relocate the connection.""" + + # unauthenticated connections get the boot + if not self.authenticated: + muffmisc.log("An unauthenticated user was disconnected during reload.") + self.state = "disconnecting" + + # authenticated users + else: + + # save and get out of the list + self.save() + self.remove() + + # create a new user object + new_user = muffuser.User() + + # give it the same name + new_user.name = self.name + + # load from file + new_user.load() + + # set everything else equivalent + new_user.address = self.address + new_user.last_address = self.last_address + new_user.connection = self.connection + new_user.authenticated = self.authenticated + new_user.password_tries = self.password_tries + new_user.state = self.state + new_user.menu_seen = self.menu_seen + new_user.error = self.error + new_user.input_queue = self.input_queue + new_user.output_queue = self.output_queue + new_user.partial_input = self.partial_input + new_user.echoing = self.echoing + + # add it to the list + muffvars.userlist.append(new_user) + + # get rid of the old user object + del(self) + + def replace_old_connections(self): + """Disconnect active users with the same name.""" + + # the default return value + return_value = False + + # iterate over each user in the list + for old_user in muffvars.userlist: + + # the name is the same but it's not us + if old_user.name == self.name and old_user is not self: + + # make a note of it + muffmisc.log("User " + self.name + " reconnected--closing old connection to " + old_user.address + ".") + old_user.send("$(eol)$(red)New connection from " + self.address + ". Terminating old connection...$(nrm)$(eol)") + self.send("$(eol)$(red)Taking over old connection from " + old_user.address + ".$(nrm)") + + # close the old connection + old_user.connection.close() + + # replace the old connection with this one + old_user.connection = self.connection + old_user.last_address = old_user.address + old_user.address = self.address + old_user.echoing = self.echoing + + # take this one out of the list and delete + self.remove() + del(self) + return_value = True + break + + # true if an old connection was replaced, false if not + return return_value + + def authenticate(self): + """Flag the user as authenticated and disconnect duplicates.""" + if not self.state is "authenticated": + muffmisc.log("User " + self.name + " logged in.") + self.authenticated = True + def load(self): """Retrieve account data from cold storage.""" # what the filename for the user account should be - filename = muffconf.config_data.get("files", "accounts") + "/" + self.name + filename = muffconf.get("files", "accounts") + "/" + self.name # try to load the password hash and last connection ipa try: @@ -88,7 +186,7 @@ class User: """Retrieve the user's account password hash from storage.""" # what the filename for the user account could be - filename = muffconf.config_data.get("files", "accounts") + "/" + self.proposed_name + filename = muffconf.get("files", "accounts") + "/" + self.proposed_name # create a temporary account record object temporary_record = ConfigParser.SafeConfigParser() @@ -120,30 +218,42 @@ class User: self.record.set("account", "last_address", self.address) # the account files live here - account_path = muffconf.config_data.get("files", "accounts") + account_path = muffconf.get("files", "accounts") # the filename to which we'll write filename = account_path + "/" + self.name.lower() - # if the directory doesn't exist, create it - # TODO: create account_path with 0700 perms + # open the user account file for writing try: - if os.listdir(account_path): pass - except: - os.mkdir(account_path, ) + record_file = file(filename, "w") - # open the user account file for writing - # TODO: create filename with 0600 perms - record_file = file(filename, "w") + # if the directory doesn't exist, create it first + except IOError: + os.makedirs(account_path) + record_file = file(filename, "w") # dump the account data to it self.record.write(record_file) # close the user account file + record_file.flush() record_file.close() + # set the permissions to 0600 + os.chmod(filename, 0600) + def show_menu(self): """Send the user their current menu.""" - self.send(muffmenu.get_menu(self)) + if not self.menu_seen: + self.menu_choices = muffmenu.get_menu_choices(self) + self.send(muffmenu.get_menu(self.state, self.error, self.echoing, self.menu_choices), "") + self.menu_seen = True + self.error = False + self.adjust_echoing() + + def adjust_echoing(self): + """Adjust echoing to match state menu requirements.""" + if self.echoing and not muffmenu.menu_echo_on(self.state): self.echoing = False + elif not self.echoing and muffmenu.menu_echo_on(self.state): self.echoing = True def remove(self): """Remove a user from the list of connected users.""" @@ -153,41 +263,124 @@ class User: """Send arbitrary text to a connected user.""" # only when there is actual output - if output: + #if output: - # start with a newline, append the message, then end - # with the optional eol string passed to this function - output = "$(eol)" + output + eol + # start with a newline, append the message, then end + # with the optional eol string passed to this function + # and the ansi escape to return to normal text + output = "\r\n" + output + eol + chr(27) + "[0m" - # replace eol markers with a crlf - # TODO: search for markers and replace from a dict - output = string.replace(output, "$(eol)", "\r\n") + # find and replace macros in the output + output = muffmisc.replace_macros(self, output) - # replace display markers with ansi escapse sequences - output = string.replace(output, "$(bld)", chr(27)+"[1m") - output = string.replace(output, "$(nrm)", chr(27)+"[0m") - output = string.replace(output, "$(blk)", chr(27)+"[30m") - output = string.replace(output, "$(grn)", chr(27)+"[32m") - output = string.replace(output, "$(red)", chr(27)+"[31m") + # wrap the text at 80 characters + # TODO: prompt user for preferred wrap width + output = muffmisc.wrap_ansi_text(output, 80) - # the user's account name - output = string.replace(output, "$(account)", self.name) + # drop the formatted output into the output queue + self.output_queue.append(output) - # wrap the text at 80 characters - # TODO: prompt user for preferred wrap width - output = muffmisc.wrap_ansi_text(output, 80) + # try to send the last item in the queue, remove it and + # flag that menu display is not needed + try: + self.connection.send(self.output_queue[0]) + self.output_queue.remove(self.output_queue[0]) + self.menu_seen = False - # drop the formatted output into the output queue - self.output_queue.append(output) + # but if we can't, that's okay too + except: + pass - # try to send the last item in the queue, remove it and - # flag that menu display is not needed - try: - self.connection.send(self.output_queue[0]) - self.output_queue.remove(self.output_queue[0]) - self.menu_seen = False + def pulse(self): + """All the things to do to the user per increment.""" + + # if the world is terminating, disconnect + if muffvars.terminate_world: + self.state = "disconnecting" + self.menu_seen = False + + # show the user a menu as needed + self.show_menu() + + # disconnect users with the appropriate state + if self.state == "disconnecting": + self.quit() + + # the user is unique and not flagged to disconnect + else: + + # check for input and add it to the queue + self.enqueue_input() + + # there is input waiting in the queue + if self.input_queue: muffcmds.handle_user_input(self) + + def enqueue_input(self): + """Process and enqueue any new input.""" + + # check for some input + try: + input_data = self.connection.recv(1024) + except: + input_data = "" + + # we got something + if input_data: + + # tack this on to any previous partial + self.partial_input += input_data + + # separate multiple input lines + new_input_lines = self.partial_input.split("\n") + + # if input doesn't end in a newline, replace the + # held partial input with the last line of it + if not self.partial_input.endswith("\n"): + self.partial_input = new_input_lines.pop() + + # otherwise, chop off the extra null input and reset + # the held partial input + else: + new_input_lines.pop() + self.partial_input = "" + + # iterate over the remaining lines + for line in new_input_lines: + + # filter out non-printables + line = filter(lambda x: x>=' ' and x<='~', line) + + # strip off extra whitespace + line = line.strip() - # but if we can't, that's okay too - except: - pass + # put on the end of the queue + self.input_queue.append(line) + + def new_avatar(self): + """Instantiate a new, unconfigured avatar for this user.""" + try: + counter = muffvars.variable_data.getint("counters", "next_actor") + except: + muffmisc.log("get next_actor failed") + counter = 1 + while muffuniv.element_exists("actor:" + repr(counter)): counter += 1 + muffvars.variable_data.set("counters", "next_actor", counter + 1) + self.avatar = muffuniv.Element("actor:" + repr(counter)) + try: + avatars = self.record.get("account", "avatars").split() + except: + avatars = [] + avatars.append(self.avatar.key) + self.record.set("account", "avatars", " ".join(avatars)) + + def list_avatar_names(self): + """A test function to list names of assigned avatars.""" + try: + avatars = self.record.get("account", "avatars").split() + except: + avatars = [] + avatar_names = [] + for avatar in avatars: + avatar_names.append(muffuniv.universe.contents[avatar].get("name")) + return avatar_names