diff --git a/contrib/menu_login.py b/contrib/menu_login.py index f7d92e899..5947beb00 100644 --- a/contrib/menu_login.py +++ b/contrib/menu_login.py @@ -346,7 +346,7 @@ node3 = MenuNode("node3", text=LOGIN_SCREEN_HELP, class UnloggedInCmdSet(CmdSet): "Cmdset for the unloggedin state" - key = "UnloggedinState" + key = "DefaultUnloggedin" priority = 0 def at_cmdset_creation(self): @@ -361,6 +361,7 @@ class CmdUnloggedinLook(Command): to the menu's own look command.. """ key = CMD_LOGINSTART + aliases = ["look", "l"] locks = "cmd:all()" def func(self): diff --git a/contrib/menusystem.py b/contrib/menusystem.py index 35b2a4199..dbab4e1f9 100644 --- a/contrib/menusystem.py +++ b/contrib/menusystem.py @@ -150,7 +150,7 @@ class MenuTree(object): tree as needed. For safety, being in a menu will not survive a server reboot. - A menutree have two special node keys given by 'startnode' and + A menutree has two special node keys given by 'startnode' and 'endnode' arguments. The startnode is where the user will start upon first entering the menu. The endnode need not actually exist, the moment it is linked to and that link is used, the menu @@ -188,7 +188,6 @@ class MenuTree(object): Add a menu node object to the tree. Each node itself keeps track of which nodes it is connected to. """ - menunode.init(self) self.tree[menunode.key] = menunode def goto(self, key): @@ -206,6 +205,8 @@ class MenuTree(object): # not exiting, look for a valid code. node = self.tree.get(key, None) if node: + # initialize - this creates new cmdset + node.init(self) if node.code: # Execute eventual code active on this # node. self.caller is available at this point. @@ -266,7 +267,7 @@ class MenuNode(object): code block, as well as ev. nodefaultcmds - if true, don't offer the default help and look commands in the node - separator - this string will be put on the line between menu nodes5B. + separator - this string will be put on the line between menu nodes. """ self.key = key self.cmdset = None diff --git a/src/commands/command.py b/src/commands/command.py index 04bd77328..bec477b9b 100644 --- a/src/commands/command.py +++ b/src/commands/command.py @@ -62,8 +62,6 @@ def _init_command(mcs, **kwargs): if hasattr(mcs, 'arg_regex') and isinstance(mcs.arg_regex, basestring): mcs.arg_regex = re.compile(r"%s" % mcs.arg_regex, re.I) - else: - mcs.arg_regex = None if not hasattr(mcs, "auto_help"): mcs.auto_help = True if not hasattr(mcs, 'is_exit'): @@ -140,14 +138,16 @@ class Command(object): locks = "" # used by the help system to group commands in lists. help_category = "general" - # This allows to turn off auto-help entry creation for individual commands. auto_help = True + # optimization for quickly separating exit-commands from normal commands + is_exit = False + # define the command not only by key but by the regex form of its arguments + arg_regex = None + # auto-set (by Evennia on command instantiation) are: # obj - which object this command is defined on - # sessid - which session-id (if any) is responsible for - # triggering this command - # + # sessid - which session-id (if any) is responsible for triggering this command def __init__(self, **kwargs): """the lockhandler works the same as for objects. diff --git a/src/scripts/tickerhandler.py b/src/scripts/tickerhandler.py index 9dd7c33e5..cf3421a9b 100644 --- a/src/scripts/tickerhandler.py +++ b/src/scripts/tickerhandler.py @@ -195,7 +195,7 @@ class TickerHandler(object): self.save_name = save_name self.ticker_pool = self.ticker_pool_class() - def _store_key(self, obj, interval): + def _store_key(self, obj, interval, idstring=""): """ Tries to create a store_key for the object. Returns a tuple (isdb, store_key) where isdb @@ -224,7 +224,7 @@ class TickerHandler(object): objkey = id(obj) isdb = False # return sidb and store_key - return isdb, (objkey, interval) + return isdb, (objkey, interval, idstring) def save(self): """ @@ -237,9 +237,11 @@ class TickerHandler(object): start_delays = dict((interval, ticker.task.next_call_time()) for interval, ticker in self.ticker_pool.tickers.items()) # update the timers for the tickers - for (obj, interval), (args, kwargs) in self.ticker_storage.items(): + #for (obj, interval, idstring), (args, kwargs) in self.ticker_storage.items(): + for store_key, (args, kwargs) in self.ticker_storage.items(): + interval = store_key[1] + # this is a mutable, so it's updated in-place in ticker_storage kwargs["_start_delay"] = start_delays.get(interval, None) - ServerConfig.objects.conf(key=self.save_name, value=dbserialize(self.ticker_storage)) else: @@ -254,30 +256,34 @@ class TickerHandler(object): if ticker_storage: self.ticker_storage = dbunserialize(ticker_storage) #print "restore:", self.ticker_storage - for (obj, interval), (args, kwargs) in self.ticker_storage.items(): + for store_key, (args, kwargs) in self.ticker_storage.items(): + if len(store_key) == 2: + # old form of store_key - update it + store_key = (store_key[0], store_key[1], "") + obj, interval, idstring = store_key obj = unpack_dbobj(obj) - _, store_key = self._store_key(obj, interval) + _, store_key = self._store_key(obj, interval, idstring) self.ticker_pool.add(store_key, obj, interval, *args, **kwargs) - def add(self, obj, interval, *args, **kwargs): + def add(self, obj, interval, idstring="", *args, **kwargs): """ Add object to tickerhandler. The object must have an at_tick method. This will be called every interval seconds until the object is unsubscribed from the ticker. """ - isdb, store_key = self._store_key(obj, interval) + isdb, store_key = self._store_key(obj, interval, idstring) if isdb: self.ticker_storage[store_key] = (args, kwargs) self.save() self.ticker_pool.add(store_key, obj, interval, *args, **kwargs) - def remove(self, obj, interval=None): + def remove(self, obj, interval=None, idstring=""): """ Remove object from ticker, or only this object ticking at a given interval. """ if interval: - isdb, store_key = self._store_key(obj, interval) + isdb, store_key = self._store_key(obj, interval, idstring) if isdb: self.ticker_storage.pop(store_key, None) self.save() @@ -287,7 +293,7 @@ class TickerHandler(object): intervals = self.ticker_pool.tickers.keys() should_save = False for interval in intervals: - isdb, store_key = self._store_key(obj, interval) + isdb, store_key = self._store_key(obj, interval, idstring) if isdb: self.ticker_storage.pop(store_key, None) should_save = True diff --git a/src/server/portal/mxp.py b/src/server/portal/mxp.py index 94eb3ac7f..56463ce47 100644 --- a/src/server/portal/mxp.py +++ b/src/server/portal/mxp.py @@ -28,6 +28,10 @@ def mxp_parse(text): """ Replaces links to the correct format for MXP. """ + text = text.replace("&", "&") \ + .replace("<", "<") \ + .replace(">", ">") + text = LINKS_SUB.sub(MXP_SEND, text) return text @@ -47,6 +51,7 @@ class Mxp(object): Client does not support MXP. """ self.protocol.protocol_flags["MXP"] = False + self.protocol.handshake_done() def do_mxp(self, option): """ diff --git a/src/server/portal/portalsessionhandler.py b/src/server/portal/portalsessionhandler.py index 9ff5d122d..290dd04b4 100644 --- a/src/server/portal/portalsessionhandler.py +++ b/src/server/portal/portalsessionhandler.py @@ -64,6 +64,15 @@ class PortalSessionHandler(SessionHandler): # only use if session already has sessid (i.e. has already connected) sessdata = session.get_sync_data() if self.portal.amp_protocol: + # we only send sessdata that should not have changed + # at the server level at this point + sessdata = dict((key, val) for key, val in sessdata.items() if key in ("protocol_key", + "address", + "sessid", + "suid", + "conn_time", + "protocol_flags", + "server_data",)) self.portal.amp_protocol.call_remote_ServerAdmin(session.sessid, operation=PCONNSYNC, data=sessdata) diff --git a/src/server/portal/telnet.py b/src/server/portal/telnet.py index 436f34afa..1118dd9e2 100644 --- a/src/server/portal/telnet.py +++ b/src/server/portal/telnet.py @@ -50,6 +50,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): self.msdp = msdp.Msdp(self) # mxp support self.mxp = Mxp(self) + # keepalive watches for dead links + self.transport.setTcpKeepAlive(1) # add this new connection to sessionhandler so # the Server becomes aware of it. self.sessionhandler.connect(self) @@ -258,6 +260,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): if prompt: # Send prompt separately prompt = ansi.parse_ansi(_RE_N.sub("", prompt) + "{n", strip_ansi=nomarkup, xterm256=xterm256) + if mxp: + prompt = mxp_parse(prompt) prompt = prompt.replace(IAC, IAC + IAC).replace('\n', '\r\n') prompt += IAC + GA self.transport.write(mccp_compress(self, prompt)) diff --git a/src/server/portal/websocket_client.py b/src/server/portal/websocket_client.py index d17702f47..f8b14f6c5 100644 --- a/src/server/portal/websocket_client.py +++ b/src/server/portal/websocket_client.py @@ -46,6 +46,8 @@ class WebSocketClient(Protocol, Session): """ client_address = self.transport.client self.init_session("websocket", client_address, self.factory.sessionhandler) + # watch for dead links + self.transport.setTcpKeepAlive(1) self.sessionhandler.connect(self) def disconnect(self, reason=None): @@ -122,10 +124,10 @@ class WebSocketClient(Protocol, Session): oobstruct = self.sessionhandler.oobstruct_parser(kwargs.pop("oob")) #print "oob data_out:", "OOB" + json.dumps(oobstruct) self.sendLine("OOB" + json.dumps(oobstruct)) - if "prompt" in kwargs: - self.sendLine("PROMPT" + kwargs["prompt"]) raw = kwargs.get("raw", False) nomarkup = kwargs.get("nomarkup", False) + if "prompt" in kwargs: + self.sendLine("PROMPT" + parse_html(kwargs["prompt"], strip_ansi=nomarkup)) if raw: self.sendLine(text) else: diff --git a/src/server/serversession.py b/src/server/serversession.py index 3498d74ff..825ef2c57 100644 --- a/src/server/serversession.py +++ b/src/server/serversession.py @@ -234,12 +234,6 @@ class ServerSession(Session): Send Evennia -> User """ text = text if text else "" - #if text is None: - # text = "" - #else: - # text = to_unicode(text) - # text = to_str(text, self.encoding) - self.sessionhandler.data_out(self, text=text, **kwargs) def __eq__(self, other): diff --git a/src/server/session.py b/src/server/session.py index a0e8c81ca..2b2d49985 100644 --- a/src/server/session.py +++ b/src/server/session.py @@ -95,7 +95,7 @@ class Session(object): and loads it into the correct properties of the session. """ for propname, value in sessdata.items(): - self.__dict__[propname] = value + setattr(self, propname, value) def at_sync(self): """ diff --git a/src/server/sessionhandler.py b/src/server/sessionhandler.py index d4498054f..b47579b78 100644 --- a/src/server/sessionhandler.py +++ b/src/server/sessionhandler.py @@ -220,6 +220,11 @@ class ServerSessionHandler(SessionHandler): sessid = portalsessiondata.get("sessid") session = self.sessions.get(sessid) if session: + # since some of the session properties may have had + # a chance to change already before the portal gets here + # the portal doesn't send all sessiondata here, but only + # ones which should only be changed from portal (like + # protocol_flags etc) session.load_sync_data(portalsessiondata) def portal_disconnect(self, sessid): @@ -339,7 +344,7 @@ class ServerSessionHandler(SessionHandler): session.logged_in = True # sync the portal to the session - sessdata = session.get_sync_data() + sessdata = {"logged_in": True} if not testmode: self.server.amp_protocol.call_remote_PortalAdmin(session.sessid, operation=SLOGIN, @@ -407,7 +412,7 @@ class ServerSessionHandler(SessionHandler): def validate_sessions(self): """ Check all currently connected sessions (logged in and not) - and see if any are dead. + and see if any are dead or idle """ tcurr = time.time() reason = _("Idle timeout exceeded, disconnecting.") diff --git a/src/utils/create.py b/src/utils/create.py index 7414697a4..6a2ac42a8 100644 --- a/src/utils/create.py +++ b/src/utils/create.py @@ -305,11 +305,15 @@ def create_script(typeclass, key=None, obj=None, player=None, locks=None, if persistent is not None: new_script.persistent = persistent + # must do this before starting the script since some + # scripts may otherwise run for a very short time and + # try to delete itself before we have a time to save it. + new_db_script.save() + # a new created script should usually be started. if autostart: new_script.start() - new_db_script.save() return new_script #alias script = create_script diff --git a/src/utils/utils.py b/src/utils/utils.py index d348f45bd..0b690a784 100644 --- a/src/utils/utils.py +++ b/src/utils/utils.py @@ -782,7 +782,8 @@ def mod_import(module): def all_from_module(module): """ - Return all global-level variables from a module as a dict + Return all global-level variables from a module as a dict. + Ignores modules and variable names starting with an underscore. """ mod = mod_import(module) if not mod: