diff --git a/src/commands/default/admin.py b/src/commands/default/admin.py index 8149e788e8..2340317102 100644 --- a/src/commands/default/admin.py +++ b/src/commands/default/admin.py @@ -58,15 +58,14 @@ class CmdBoot(MuxCommand): # Boot by player object pobj = caller.search("*%s" % args, global_search=True) if not pobj: - return - pobj = pobj - if pobj.has_player: + return + if pobj.character.has_player: if not has_perm(caller, pobj, 'can_boot'): string = "You don't have the permission to boot %s." pobj.msg(string) return # we have a bootable object with a connected user - matches = SESSIONS.sessions_from_object(pobj) + matches = SESSIONS.sessions_from_player(pobj) for match in matches: boot_list.append(match) else: @@ -87,10 +86,8 @@ class CmdBoot(MuxCommand): for session in boot_list: name = session.name - if feedback: - session.msg(feedback) - session.disconnectClient() - SESSIONS.remove_session(session) + session.msg(feedback) + session.disconnect() caller.msg("You booted %s." % name) @@ -120,7 +117,7 @@ class CmdDelPlayer(MuxCommand): args = self.args if not args: - caller.msg("Usage: @delplayer[/delobj] ") + caller.msg("Usage: @delplayer[/delobj] [: reason]") return reason = "" @@ -129,7 +126,7 @@ class CmdDelPlayer(MuxCommand): # We use player_search since we want to be sure to find also players # that lack characters. - players = PlayerDB.objects.filter(db_key=args) + players = caller.search("*%s" % args) if not players: try: players = PlayerDB.objects.filter(id=args) @@ -150,12 +147,13 @@ class CmdDelPlayer(MuxCommand): try: player = user.get_profile() except Exception: - player = None + player = None if not has_perm_string(caller, 'manage_players'): string = "You don't have the permissions to delete this player." caller.msg(string) return + string = "" name = user.username user.delete() @@ -164,7 +162,7 @@ class CmdDelPlayer(MuxCommand): player.delete() string = "Player %s was deleted." % name else: - string += "The User %s was deleted, but had no Player associated with it." % name + string += "The User %s was deleted. It had no Player associated with it." % name caller.msg(string) return diff --git a/src/server/server.py b/src/server/server.py index d90bb2099f..3d384419cc 100644 --- a/src/server/server.py +++ b/src/server/server.py @@ -16,7 +16,7 @@ if os.name == 'nt': os.path.dirname(os.path.abspath(__file__))))) from twisted.application import internet, service -from twisted.internet import protocol, reactor +from twisted.internet import protocol, reactor, defer from twisted.web import server, static from django.db import connection from django.conf import settings @@ -92,6 +92,10 @@ class Evennia(object): print '-'*50 + # set a callback if the server is killed abruptly, + # by Ctrl-C, reboot etc. + reactor.addSystemEventTrigger('before', 'shutdown',self.shutdown, _abrupt=True) + self.game_running = True # Server startup methods @@ -145,16 +149,21 @@ class Evennia(object): if WEBCLIENT_ENABLED: clientstring = '/client' print " webserver%s: " % clientstring + ", ".join([str(port) for port in WEBSERVER_PORTS]) - - def shutdown(self, message=None): + + def shutdown(self, message="{rThe server has been shutdown. Disconnecting.{n", _abrupt=False): """ - Gracefully disconnect everyone and kill the reactor. + If called directly, this disconnects everyone cleanly and shuts down the + reactor. If the server is killed by other means (Ctrl-C, reboot etc), this + might be called as a callback, at which point the reactor is already dead + and should not be tried to stop again (_abrupt=True). + + message - message to send to all connected sessions + _abrupt - only to be used by internal callback_mechanism. """ - if not message: - message = 'The server has been shutdown. Please check back soon.' SESSIONS.disconnect_all_sessions(reason=message) - reactor.callLater(0, reactor.stop) - + if not _abrupt: + reactor.callLater(0, reactor.stop) + #------------------------------------------------------------ # @@ -200,13 +209,14 @@ if WEBSERVER_ENABLED: if WEBCLIENT_ENABLED: # create ajax client processes at /webclientdata - from src.server.webclient import WebClient + from src.server.webclient import WebClient web_root.putChild("webclientdata", WebClient()) web_site = server.Site(web_root, logPath=settings.HTTP_LOG_FILE) for port in WEBSERVER_PORTS: # create the webserver webserver = internet.TCPServer(port, web_site) + #webserver = internet.SSLServer(port, web_site) webserver.setName('EvenniaWebServer%s' % port) EVENNIA.services.addService(webserver) diff --git a/src/server/session.py b/src/server/session.py index 75d8923904..3206afa923 100644 --- a/src/server/session.py +++ b/src/server/session.py @@ -2,6 +2,35 @@ This defines a generic session class. All protocols should implement this class and its hook methods. + + +The process of first connect: + + - The custom connection-handler for the respective + protocol should be called by the transport connection itself. + - The connect-handler handles whatever internal settings are needed + - The connection-handler calls session_connect() + - session_connect() setups sessions then calls session.at_connect() + +Disconnecting is a bit more complex in order to avoid circular calls +depending on if the disconnect happens automatically or manually from +a command. + +The process at automatic disconnect: + - The custom disconnect-handler for the respective protocol + should be called by the transport connection itself. This handler + should be defined with a keyword argument 'step' defaulting to 1. + - since step=1, the disconnect-handler calls session_disconnect() + - session_disconnect() removes session, then calls session.at_disconnect() + - session.at_disconnect() calls the custom disconnect-handler with + step=2 as argument + - since step=2, the disconnect-handler closes the connection and + performs all needed protocol cleanup. + +The process of manual disconnect: + - The command/outside function calls session.session_disconnect(). + - from here the process proceeds as the automatic disconnect above. + """ import time @@ -76,9 +105,9 @@ class SessionBase(object): self.cmd_total = 0 #self.channels_subscribed = {} SESSIONS.add_unloggedin_session(self) - # call hook method + # calling hook self.at_connect() - + def session_login(self, player): """ Private startup mechanisms that need to run at login @@ -106,7 +135,7 @@ class SessionBase(object): #call hook self.at_login() - def session_disconnect(self, reason=None): + def session_disconnect(self): """ Clean up the session, removing it from the game and doing some accounting. This method is used also for non-loggedin @@ -118,12 +147,12 @@ class SessionBase(object): if self.logged_in: character = self.get_character() if character: - character.player.at_disconnect(reason) uaccount = character.player.user uaccount.last_login = datetime.now() uaccount.save() - self.logged_in = False - SESSIONS.remove_session(self) + self.at_disconnect() + self.logged_in = False + SESSIONS.remove_session(self) def session_validate(self): """ @@ -275,11 +304,19 @@ class Session(SessionBase): """ pass - def at_disconnect(self, reason=None): + def at_disconnect(self): """ This method is called just before cleaning up the session (so still logged_in=True at this point). + + This method should not be called from commands, instead it + is called automatically by session_disconnect() as part of + the cleanup. + + This method MUST call the protocol-dependant disconnect-handler + with step=2 to finalize the closing of the connection! """ + # self.my-disconnect-handler(step=2) pass def at_data_in(self, string="", data=None): @@ -302,9 +339,9 @@ class Session(SessionBase): def login(self, player): "alias for at_login" self.at_login(player) - def logout(self): - "alias for at_logout" - self.at_disconnect() + def disconnect(self): + "alias for session_disconnect" + self.session_disconnect() def msg(self, string='', data=None): "alias for at_data_out" self.at_data_out(string, data) diff --git a/src/server/sessionhandler.py b/src/server/sessionhandler.py index 4f4a56c0c2..ddf66b3e05 100644 --- a/src/server/sessionhandler.py +++ b/src/server/sessionhandler.py @@ -97,13 +97,14 @@ class SessionHandler(object): else: return self.loggedin - def disconnect_all_sessions(self, reason=None): + def disconnect_all_sessions(self, reason="You have been disconnected."): """ Cleanly disconnect all of the connected sessions. """ - sessions = self.get_sessions(include_unloggedin=True) + sessions = self.get_sessions(include_unloggedin=True) for session in sessions: - session.session_disconnect(reason) + session.at_data_out(reason) + session.session_disconnect() self.session_count(0) def disconnect_duplicate_sessions(self, session): diff --git a/src/server/telnet.py b/src/server/telnet.py index 897b8cdd9d..897380307a 100644 --- a/src/server/telnet.py +++ b/src/server/telnet.py @@ -32,16 +32,23 @@ class TelnetProtocol(StatefulTelnetProtocol, session.Session): # initialize the session self.session_connect(self.getClientAddress()) - def connectionLost(self, reason="Disconnecting. Goodbye for now."): + def connectionLost(self, reason=None, step=1): """ This is executed when the connection is lost for - whatever reason. It should also be called from - self.at_disconnect() so one can close the connection - manually without having to know the name of this specific - method. + whatever reason. + + Closing the connection takes two steps + + step 1 - is the default and is used when this method is + called automatically. The method should then call self.session_disconnect(). + Step 2 - means this method is called from at_disconnect(). At this point + the sessions are assumed to have been handled, and so the transport can close + without further ado. """ - self.session_disconnect(reason) - self.transport.loseConnection() + if step == 1: + self.session_disconnect() + else: + self.transport.loseConnection() def getClientAddress(self): """ @@ -81,7 +88,7 @@ class TelnetProtocol(StatefulTelnetProtocol, session.Session): # show screen screen = ConnectScreen.objects.get_random_connect_screen() string = ansi.parse_ansi(screen.text) - self.lineSend(string) + self.at_data_out(string) def at_login(self): """ @@ -96,9 +103,8 @@ class TelnetProtocol(StatefulTelnetProtocol, session.Session): """ Disconnect from server """ - if reason: - self.lineSend(reason) - self.connectionLost(reason) + self.at_data_out(reason) + self.connectionLost(step=2) def at_data_out(self, string, data=None): """ diff --git a/src/server/webclient.py b/src/server/webclient.py index 39ff48e552..1ccd496836 100644 --- a/src/server/webclient.py +++ b/src/server/webclient.py @@ -64,7 +64,6 @@ class WebClient(resource.Resource): def __init__(self): self.requests = {} self.databuffer = {} - reactor.addSystemEventTrigger('before', 'shutdown',self._forced_disconnect) def getChild(self, path, request): """ @@ -78,15 +77,7 @@ class WebClient(resource.Resource): self.requests.get(suid, []).remove(request) except ValueError: pass - - def _forced_disconnect(self): - """ - Callback launched when webserver is closing forcefully (Ctrl-C, reboot etc) - All we do is make sure the connected clients are notitifed. - """ - for suid in self.requests.keys(): - self.lineSend(suid, parse_html("{rThe MUD server shut down. You were disconnected.{n")) - + def lineSend(self, suid, string, data=None): """ This adds the data to the buffer and/or sends it to @@ -105,17 +96,24 @@ class WebClient(resource.Resource): dataentries.append(jsonify({'msg':string, 'data':data})) self.databuffer[suid] = dataentries - def disconnect(self, suid): - "Disconnect session with given suid." - sess = SESSIONS.session_from_suid(suid) - if sess: + def disconnect(self, suid, step=1): + """ + Disconnect session with given suid. + + step 1 : call session_disconnect() + step 2 : finalize disconnection + """ + + if step == 1: + sess = SESSIONS.session_from_suid(suid) sess[0].session_disconnect() - if self.requests.has_key(suid): - for request in self.requests.get(suid, []): - request.finish() - del self.requests[suid] - if self.databuffer.has_key(suid): - del self.databuffer[suid] + else: + if self.requests.has_key(suid): + for request in self.requests.get(suid, []): + request.finish() + del self.requests[suid] + if self.databuffer.has_key(suid): + del self.databuffer[suid] def mode_init(self, request): """ @@ -230,8 +228,8 @@ class WebClientSession(session.Session): """ # show screen screen = ConnectScreen.objects.get_random_connect_screen() - string = parse_html(screen.text) - self.client.lineSend(self.suid, string) + #string = parse_html(screen.text) + self.at_data_out(screen.text) def at_login(self): """ @@ -248,7 +246,7 @@ class WebClientSession(session.Session): """ if reason: self.lineSend(self.suid, reason) - self.client.disconnect(self.suid) + self.client.disconnect(self.suid, step=2) def at_data_out(self, string='', data=None): """