diff --git a/contrib/menu_login.py b/contrib/menu_login.py index f7d92e899b..5947beb003 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 35b2a41990..dbab4e1f9e 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 04bd773283..bec477b9bb 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 9dd7c33e56..cf3421a9ba 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 94eb3ac7f0..56463ce473 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 9ff5d122d7..290dd04b4d 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 436f34afa5..1118dd9e2c 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 d17702f471..f8b14f6c5d 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 3498d74ffc..825ef2c574 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 a0e8c81ca4..2b2d499854 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 d4498054f1..b47579b78b 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 7414697a4e..6a2ac42a88 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 d348f45bdd..0b690a784a 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: