mirror of
https://github.com/evennia/evennia.git
synced 2026-04-04 23:17:17 +02:00
Resolved issue122. Also clarified the functional sequence for disconnecting a session cleanly (avoiding circular calls that might happen if disconnection happens manually or automatically due to server shutdown). Removed the specific stopping callback from the webclient and put it in the mai
n server process instead, so all protocols can get a message when server shuts down with Ctrl-C.
This commit is contained in:
parent
6ecbda03ea
commit
939307a5c1
6 changed files with 118 additions and 68 deletions
|
|
@ -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] <player/user name or #id>")
|
||||
caller.msg("Usage: @delplayer[/delobj] <player/user name or #id> [: 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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue