diff --git a/evennia/server/amp.py b/evennia/server/amp.py index 18ad21599f..792fc7d6a0 100644 --- a/evennia/server/amp.py +++ b/evennia/server/amp.py @@ -559,7 +559,8 @@ class AMPProtocol(amp.AMP): elif operation == SSYNC: # server_session_sync # server wants to save session data to the portal, # maybe because it's about to shut down. - portal_sessionhandler.server_session_sync(kwargs.get("sessiondata")) + portal_sessionhandler.server_session_sync(kwargs.get("sessiondata"), + kwargs.get("clean", True)) # set a flag in case we are about to shut down soon self.factory.server_restart_mode = True diff --git a/evennia/server/inputfuncs.py b/evennia/server/inputfuncs.py index a7ca375437..2759a4d0a1 100644 --- a/evennia/server/inputfuncs.py +++ b/evennia/server/inputfuncs.py @@ -1,5 +1,19 @@ """ -Handlers for input commands +Functions for processing input commands. + +All global functions in this module whose name does not start with "_" +is considered an inputfunc. Each function must have the following +callsign: + + inputfunc(session, *args, **kwargs) + +There is one special function, the "default" function, which is called +on a no-match. It has this callsign: + + default(session, cmdname, *args, **kwargs) + +Evennia knows which modules to use for inputfuncs by +settings.INPUT_FUNC_MODULES. """ from future.utils import viewkeys @@ -9,11 +23,17 @@ from evennia.commands.cmdhandler import cmdhandler from evennia.utils.logger import log_err from evennia.utils.utils import to_str + _IDLE_COMMAND = settings.IDLE_COMMAND _GA = object.__getattribute__ _SA = object.__setattr__ _NA = lambda o: "N/A" +_ERROR_INPUT = "Inputfunc {name}({session}): Wrong/unrecognized input: {inp}" + + +# All global functions are inputfuncs available to process inputs + def text(session, *args, **kwargs): """ Main text input from the client. This will execute a command @@ -51,11 +71,16 @@ def text(session, *args, **kwargs): cmdhandler(session, text, callertype="session", session=session) session.update_session_counters() + def echo(session, *args, **kwargs): + """ + Echo test function + """ print "Inputfunc echo:", session, args, kwargs session.data_out(text="Echo returns: ") session.data_out(echo=(args, kwargs)) + def default(session, cmdname, *args, **kwargs): """ Default catch-function. This is like all other input functions except @@ -67,8 +92,89 @@ def default(session, cmdname, *args, **kwargs): " args, kwargs: {args}, {kwargs}" log_err(err.format(sessid=session.sessid, cmdname=cmdname, args=args, kwargs=kwargs)) + +def client_settings(session, *args, **kwargs): + """ + This allows the client an OOB way to inform us about its name and capabilities. + This will be integrated into the session settings + + Kwargs: + client (str): A client identifier, like "mushclient". + version (str): A client version + ansi (bool): Supports ansi colors + xterm256 (bool): Supports xterm256 colors or not + mxp (bool): Supports MXP or not + utf-8 (bool): Supports UTF-8 or not + screenreader (bool): Screen-reader mode on/off + mccp (bool): MCCP compression on/off + screenheight (int): Screen height in lines + screenwidth (int): Screen width in characters + + """ + flags = session.protocol_flags + tflags = flags["TTYPE"] + for key, value in kwargs.iteritems(): + key = key.lower() + if key == "client": + tflags["CLIENTNAME"] = to_str(value) + elif key == "version": + if "CLIENTNAME" in tflags: + tflags["CLIENTNAME"] = "%s %s" % (tflags["CLIENTNAME"], to_str(value)) + elif key == "ansi": + tflags["ANSI"] = bool(value) + elif key == "xterm256": + tflags["256 COLORS"] = bool(value) + elif key == "mxp": + flags["MXP"] = bool(value) + elif key == "utf-8": + tflags["UTF-8"] = bool(value) + elif key == "screenreader": + flags["SCREENREADER"] = bool(value) + elif key == "mccp": + flags["MCCP"] = bool(value) + elif key == "screenheight": + flags["SCREENHEIGHT"] = int(value) + elif key == "screenwidth": + flags["SCREENWIDTH"] = int(value) + else: + err = _ERROR_INPUT.format( + name="client_settings", session=session, inp=key) + session.msg(text=err) + flags["TTYPE"] = tflags + session.protocol_flags = flags + # we must update the portal as well + session.sessionhandler.session_portal_sync(session) + + +def login(session, *args, **kwargs): + """ + Peform a login. This only works if session is currently not logged + in. This will also automatically throttle too quick attempts. + + Kwargs: + name (str): Player name + password (str): Plain-text password + + """ + if not session.logged_in and "name" in kwargs and "password" in kwargs: + from evennia.commands.default.unloggedin import create_normal_player + player = create_normal_player(session, kwargs["name"], kwargs["password"]) + if player: + session.sessionhandler.login(session, player) + + +# aliases for GMCP +core_hello = client_settings # Core.Hello +core_supports_set = client_settings # Core.Supports.Set +char_login = login # Char.Login + + #------------------------------------------------------------------------------------ + + + + #------------------------------------------------------------ # All OOB commands must be on the form # cmdname(session, *args, **kwargs) diff --git a/evennia/server/portal/portalsessionhandler.py b/evennia/server/portal/portalsessionhandler.py index a765b5fc44..959df40deb 100644 --- a/evennia/server/portal/portalsessionhandler.py +++ b/evennia/server/portal/portalsessionhandler.py @@ -236,27 +236,29 @@ class PortalSessionHandler(SessionHandler): """ session.load_sync_data(data) - def server_session_sync(self, serversessions): + def server_session_sync(self, serversessions, clean=True): """ Server wants to save data to the portal, maybe because it's about to shut down. We don't overwrite any sessions here, just - update them in-place and remove any that are out of sync - (which should normally not be the case) + update them in-place. Args: serversessions (dict): This is a dictionary `{sessid:{property:value},...}` describing the properties to sync on all sessions. + clean (bool): If True, remove any Portal sessions that are + not included in serversessions. """ to_save = [sessid for sessid in serversessions if sessid in self] - to_delete = [sessid for sessid in self if sessid not in to_save] # save protocols for sessid in to_save: self[sessid].load_sync_data(serversessions[sessid]) - # disconnect out-of-sync missing protocols - for sessid in to_delete: - self.server_disconnect(sessid) + if clean: + # disconnect out-of-sync missing protocols + to_delete = [sessid for sessid in self if sessid not in to_save] + for sessid in to_delete: + self.server_disconnect(sessid) def count_loggedin(self, include_unloggedin=False): """ diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index d952eb18c1..e2a05eace6 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -446,6 +446,18 @@ class ServerSessionHandler(SessionHandler): operation=SSYNC, sessiondata=sessdata) + def session_portal_sync(self, session): + """ + This is called by the server when it wants to sync a single session + with the Portal for whatever reason. Returns a deferred! + + """ + sessdata = {session.sessid: session.get_sync_data()} + return self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION, + operation=SSYNC, + sessiondata=sessdata, + clean=False) + def disconnect_all_sessions(self, reason="You have been disconnected."): """ Cleanly disconnect all of the connected sessions.