Merge remote-tracking branch 'upstream/master'

This commit is contained in:
luyijun 2014-10-22 00:37:49 +08:00
commit 73e57422c7
13 changed files with 66 additions and 34 deletions

View file

@ -346,7 +346,7 @@ node3 = MenuNode("node3", text=LOGIN_SCREEN_HELP,
class UnloggedInCmdSet(CmdSet): class UnloggedInCmdSet(CmdSet):
"Cmdset for the unloggedin state" "Cmdset for the unloggedin state"
key = "UnloggedinState" key = "DefaultUnloggedin"
priority = 0 priority = 0
def at_cmdset_creation(self): def at_cmdset_creation(self):
@ -361,6 +361,7 @@ class CmdUnloggedinLook(Command):
to the menu's own look command.. to the menu's own look command..
""" """
key = CMD_LOGINSTART key = CMD_LOGINSTART
aliases = ["look", "l"]
locks = "cmd:all()" locks = "cmd:all()"
def func(self): def func(self):

View file

@ -150,7 +150,7 @@ class MenuTree(object):
tree as needed. For safety, being in a menu will not survive a tree as needed. For safety, being in a menu will not survive a
server reboot. 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 'endnode' arguments. The startnode is where the user will start
upon first entering the menu. The endnode need not actually upon first entering the menu. The endnode need not actually
exist, the moment it is linked to and that link is used, the menu 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 Add a menu node object to the tree. Each node itself keeps
track of which nodes it is connected to. track of which nodes it is connected to.
""" """
menunode.init(self)
self.tree[menunode.key] = menunode self.tree[menunode.key] = menunode
def goto(self, key): def goto(self, key):
@ -206,6 +205,8 @@ class MenuTree(object):
# not exiting, look for a valid code. # not exiting, look for a valid code.
node = self.tree.get(key, None) node = self.tree.get(key, None)
if node: if node:
# initialize - this creates new cmdset
node.init(self)
if node.code: if node.code:
# Execute eventual code active on this # Execute eventual code active on this
# node. self.caller is available at this point. # node. self.caller is available at this point.
@ -266,7 +267,7 @@ class MenuNode(object):
code block, as well as ev. code block, as well as ev.
nodefaultcmds - if true, don't offer the default help and look commands nodefaultcmds - if true, don't offer the default help and look commands
in the node 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.key = key
self.cmdset = None self.cmdset = None

View file

@ -62,8 +62,6 @@ def _init_command(mcs, **kwargs):
if hasattr(mcs, 'arg_regex') and isinstance(mcs.arg_regex, basestring): if hasattr(mcs, 'arg_regex') and isinstance(mcs.arg_regex, basestring):
mcs.arg_regex = re.compile(r"%s" % mcs.arg_regex, re.I) mcs.arg_regex = re.compile(r"%s" % mcs.arg_regex, re.I)
else:
mcs.arg_regex = None
if not hasattr(mcs, "auto_help"): if not hasattr(mcs, "auto_help"):
mcs.auto_help = True mcs.auto_help = True
if not hasattr(mcs, 'is_exit'): if not hasattr(mcs, 'is_exit'):
@ -140,14 +138,16 @@ class Command(object):
locks = "" locks = ""
# used by the help system to group commands in lists. # used by the help system to group commands in lists.
help_category = "general" help_category = "general"
# This allows to turn off auto-help entry creation for individual commands. # This allows to turn off auto-help entry creation for individual commands.
auto_help = True 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: # auto-set (by Evennia on command instantiation) are:
# obj - which object this command is defined on # obj - which object this command is defined on
# sessid - which session-id (if any) is responsible for # sessid - which session-id (if any) is responsible for triggering this command
# triggering this command
#
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""the lockhandler works the same as for objects. """the lockhandler works the same as for objects.

View file

@ -195,7 +195,7 @@ class TickerHandler(object):
self.save_name = save_name self.save_name = save_name
self.ticker_pool = self.ticker_pool_class() 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. Tries to create a store_key for the object.
Returns a tuple (isdb, store_key) where isdb Returns a tuple (isdb, store_key) where isdb
@ -224,7 +224,7 @@ class TickerHandler(object):
objkey = id(obj) objkey = id(obj)
isdb = False isdb = False
# return sidb and store_key # return sidb and store_key
return isdb, (objkey, interval) return isdb, (objkey, interval, idstring)
def save(self): def save(self):
""" """
@ -237,9 +237,11 @@ class TickerHandler(object):
start_delays = dict((interval, ticker.task.next_call_time()) start_delays = dict((interval, ticker.task.next_call_time())
for interval, ticker in self.ticker_pool.tickers.items()) for interval, ticker in self.ticker_pool.tickers.items())
# update the timers for the tickers # 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) kwargs["_start_delay"] = start_delays.get(interval, None)
ServerConfig.objects.conf(key=self.save_name, ServerConfig.objects.conf(key=self.save_name,
value=dbserialize(self.ticker_storage)) value=dbserialize(self.ticker_storage))
else: else:
@ -254,30 +256,34 @@ class TickerHandler(object):
if ticker_storage: if ticker_storage:
self.ticker_storage = dbunserialize(ticker_storage) self.ticker_storage = dbunserialize(ticker_storage)
#print "restore:", self.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) 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) 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 Add object to tickerhandler. The object must have an at_tick
method. This will be called every interval seconds until the method. This will be called every interval seconds until the
object is unsubscribed from the ticker. 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: if isdb:
self.ticker_storage[store_key] = (args, kwargs) self.ticker_storage[store_key] = (args, kwargs)
self.save() self.save()
self.ticker_pool.add(store_key, obj, interval, *args, **kwargs) 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 Remove object from ticker, or only this object ticking
at a given interval. at a given interval.
""" """
if interval: if interval:
isdb, store_key = self._store_key(obj, interval) isdb, store_key = self._store_key(obj, interval, idstring)
if isdb: if isdb:
self.ticker_storage.pop(store_key, None) self.ticker_storage.pop(store_key, None)
self.save() self.save()
@ -287,7 +293,7 @@ class TickerHandler(object):
intervals = self.ticker_pool.tickers.keys() intervals = self.ticker_pool.tickers.keys()
should_save = False should_save = False
for interval in intervals: for interval in intervals:
isdb, store_key = self._store_key(obj, interval) isdb, store_key = self._store_key(obj, interval, idstring)
if isdb: if isdb:
self.ticker_storage.pop(store_key, None) self.ticker_storage.pop(store_key, None)
should_save = True should_save = True

View file

@ -28,6 +28,10 @@ def mxp_parse(text):
""" """
Replaces links to the correct format for MXP. Replaces links to the correct format for MXP.
""" """
text = text.replace("&", "&") \
.replace("<", "&lt;") \
.replace(">", "&gt;")
text = LINKS_SUB.sub(MXP_SEND, text) text = LINKS_SUB.sub(MXP_SEND, text)
return text return text
@ -47,6 +51,7 @@ class Mxp(object):
Client does not support MXP. Client does not support MXP.
""" """
self.protocol.protocol_flags["MXP"] = False self.protocol.protocol_flags["MXP"] = False
self.protocol.handshake_done()
def do_mxp(self, option): def do_mxp(self, option):
""" """

View file

@ -64,6 +64,15 @@ class PortalSessionHandler(SessionHandler):
# only use if session already has sessid (i.e. has already connected) # only use if session already has sessid (i.e. has already connected)
sessdata = session.get_sync_data() sessdata = session.get_sync_data()
if self.portal.amp_protocol: 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, self.portal.amp_protocol.call_remote_ServerAdmin(session.sessid,
operation=PCONNSYNC, operation=PCONNSYNC,
data=sessdata) data=sessdata)

View file

@ -50,6 +50,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
self.msdp = msdp.Msdp(self) self.msdp = msdp.Msdp(self)
# mxp support # mxp support
self.mxp = Mxp(self) self.mxp = Mxp(self)
# keepalive watches for dead links
self.transport.setTcpKeepAlive(1)
# add this new connection to sessionhandler so # add this new connection to sessionhandler so
# the Server becomes aware of it. # the Server becomes aware of it.
self.sessionhandler.connect(self) self.sessionhandler.connect(self)
@ -258,6 +260,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
if prompt: if prompt:
# Send prompt separately # Send prompt separately
prompt = ansi.parse_ansi(_RE_N.sub("", prompt) + "{n", strip_ansi=nomarkup, xterm256=xterm256) 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 = prompt.replace(IAC, IAC + IAC).replace('\n', '\r\n')
prompt += IAC + GA prompt += IAC + GA
self.transport.write(mccp_compress(self, prompt)) self.transport.write(mccp_compress(self, prompt))

View file

@ -46,6 +46,8 @@ class WebSocketClient(Protocol, Session):
""" """
client_address = self.transport.client client_address = self.transport.client
self.init_session("websocket", client_address, self.factory.sessionhandler) self.init_session("websocket", client_address, self.factory.sessionhandler)
# watch for dead links
self.transport.setTcpKeepAlive(1)
self.sessionhandler.connect(self) self.sessionhandler.connect(self)
def disconnect(self, reason=None): def disconnect(self, reason=None):
@ -122,10 +124,10 @@ class WebSocketClient(Protocol, Session):
oobstruct = self.sessionhandler.oobstruct_parser(kwargs.pop("oob")) oobstruct = self.sessionhandler.oobstruct_parser(kwargs.pop("oob"))
#print "oob data_out:", "OOB" + json.dumps(oobstruct) #print "oob data_out:", "OOB" + json.dumps(oobstruct)
self.sendLine("OOB" + json.dumps(oobstruct)) self.sendLine("OOB" + json.dumps(oobstruct))
if "prompt" in kwargs:
self.sendLine("PROMPT" + kwargs["prompt"])
raw = kwargs.get("raw", False) raw = kwargs.get("raw", False)
nomarkup = kwargs.get("nomarkup", False) nomarkup = kwargs.get("nomarkup", False)
if "prompt" in kwargs:
self.sendLine("PROMPT" + parse_html(kwargs["prompt"], strip_ansi=nomarkup))
if raw: if raw:
self.sendLine(text) self.sendLine(text)
else: else:

View file

@ -234,12 +234,6 @@ class ServerSession(Session):
Send Evennia -> User Send Evennia -> User
""" """
text = text if text else "" 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) self.sessionhandler.data_out(self, text=text, **kwargs)
def __eq__(self, other): def __eq__(self, other):

View file

@ -95,7 +95,7 @@ class Session(object):
and loads it into the correct properties of the session. and loads it into the correct properties of the session.
""" """
for propname, value in sessdata.items(): for propname, value in sessdata.items():
self.__dict__[propname] = value setattr(self, propname, value)
def at_sync(self): def at_sync(self):
""" """

View file

@ -220,6 +220,11 @@ class ServerSessionHandler(SessionHandler):
sessid = portalsessiondata.get("sessid") sessid = portalsessiondata.get("sessid")
session = self.sessions.get(sessid) session = self.sessions.get(sessid)
if session: 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) session.load_sync_data(portalsessiondata)
def portal_disconnect(self, sessid): def portal_disconnect(self, sessid):
@ -339,7 +344,7 @@ class ServerSessionHandler(SessionHandler):
session.logged_in = True session.logged_in = True
# sync the portal to the session # sync the portal to the session
sessdata = session.get_sync_data() sessdata = {"logged_in": True}
if not testmode: if not testmode:
self.server.amp_protocol.call_remote_PortalAdmin(session.sessid, self.server.amp_protocol.call_remote_PortalAdmin(session.sessid,
operation=SLOGIN, operation=SLOGIN,
@ -407,7 +412,7 @@ class ServerSessionHandler(SessionHandler):
def validate_sessions(self): def validate_sessions(self):
""" """
Check all currently connected sessions (logged in and not) 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() tcurr = time.time()
reason = _("Idle timeout exceeded, disconnecting.") reason = _("Idle timeout exceeded, disconnecting.")

View file

@ -305,11 +305,15 @@ def create_script(typeclass, key=None, obj=None, player=None, locks=None,
if persistent is not None: if persistent is not None:
new_script.persistent = persistent 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. # a new created script should usually be started.
if autostart: if autostart:
new_script.start() new_script.start()
new_db_script.save()
return new_script return new_script
#alias #alias
script = create_script script = create_script

View file

@ -782,7 +782,8 @@ def mod_import(module):
def all_from_module(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) mod = mod_import(module)
if not mod: if not mod: