Trunk: Merged griatch-branch. This implements a new reload mechanism - splitting Evennia into two processes: Server and Portal with different tasks. Also cleans and fixes several bugs in script systems as well as introduces i18n (courtesy of raydeejay).
This commit is contained in:
parent
14dae44a46
commit
f13e8cdf7c
50 changed files with 3175 additions and 2565 deletions
394
src/server/amp.py
Normal file
394
src/server/amp.py
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
"""
|
||||
Contains the protocols, commands, and client factory needed for the server
|
||||
to service the MUD portal proxy.
|
||||
|
||||
The separation works like this:
|
||||
|
||||
Portal - (AMP client) handles protocols. It contains a list of connected sessions in a
|
||||
dictionary for identifying the respective player connected. If it looses the AMP connection
|
||||
it will automatically try to reconnect.
|
||||
|
||||
Server - (AMP server) Handles all mud operations. The server holds its own list
|
||||
of sessions tied to player objects. This is synced against the portal at startup
|
||||
and when a session connects/disconnects
|
||||
|
||||
"""
|
||||
import os
|
||||
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError:
|
||||
import pickle
|
||||
from twisted.protocols import amp
|
||||
from twisted.internet import protocol, defer, reactor
|
||||
from django.conf import settings
|
||||
from src.utils import utils
|
||||
from src.server.models import ServerConfig
|
||||
from src.scripts.models import ScriptDB
|
||||
from src.players.models import PlayerDB
|
||||
from src.server.serversession import ServerSession
|
||||
|
||||
PORTAL_RESTART = os.path.join(settings.GAME_DIR, "portal.restart")
|
||||
SERVER_RESTART = os.path.join(settings.GAME_DIR, "server.restart")
|
||||
|
||||
# i18n
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
# Signals
|
||||
|
||||
|
||||
|
||||
|
||||
def get_restart_mode(restart_file):
|
||||
"""
|
||||
Parse the server/portal restart status
|
||||
"""
|
||||
if os.path.exists(restart_file):
|
||||
flag = open(restart_file, 'r').read()
|
||||
return flag == "True"
|
||||
return False
|
||||
|
||||
class AmpServerFactory(protocol.ServerFactory):
|
||||
"""
|
||||
This factory creates new AMPProtocol protocol instances to use for accepting
|
||||
connections from TCPServer.
|
||||
"""
|
||||
def __init__(self, server):
|
||||
"""
|
||||
server: The Evennia server service instance
|
||||
protocol: The protocol the factory creates instances of.
|
||||
"""
|
||||
self.server = server
|
||||
self.protocol = AMPProtocol
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
"""
|
||||
Start a new connection, and store it on the service object
|
||||
"""
|
||||
#print "Evennia Server connected to Portal at %s." % addr
|
||||
self.server.amp_protocol = AMPProtocol()
|
||||
self.server.amp_protocol.factory = self
|
||||
return self.server.amp_protocol
|
||||
|
||||
|
||||
|
||||
class AmpClientFactory(protocol.ReconnectingClientFactory):
|
||||
"""
|
||||
This factory creates new AMPProtocol protocol instances to use to connect
|
||||
to the MUD server. It also maintains the portal attribute
|
||||
on the ProxyService instance, which is used for piping input
|
||||
from Telnet to the MUD server.
|
||||
"""
|
||||
# Initial reconnect delay in seconds.
|
||||
initialDelay = 1
|
||||
#factor = 1.5
|
||||
maxDelay = 1
|
||||
|
||||
def __init__(self, portal):
|
||||
self.portal = portal
|
||||
self.protocol = AMPProtocol
|
||||
|
||||
def startedConnecting(self, connector):
|
||||
"""
|
||||
Called when starting to try to connect to the MUD server.
|
||||
"""
|
||||
pass
|
||||
#print 'AMP started to connect:', connector
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
"""
|
||||
Creates an AMPProtocol instance when connecting to the server.
|
||||
"""
|
||||
#print "Portal connected to Evennia server at %s." % addr
|
||||
self.resetDelay()
|
||||
self.portal.amp_protocol = AMPProtocol()
|
||||
self.portal.amp_protocol.factory = self
|
||||
return self.portal.amp_protocol
|
||||
|
||||
def clientConnectionLost(self, connector, reason):
|
||||
"""
|
||||
Called when the AMP connection to the MUD server is lost.
|
||||
"""
|
||||
if not get_restart_mode(SERVER_RESTART):
|
||||
self.portal.sessions.announce_all(_(" Portal lost connection to Server."))
|
||||
protocol.ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
|
||||
|
||||
def clientConnectionFailed(self, connector, reason):
|
||||
"""
|
||||
Called when an AMP connection attempt to the MUD server fails.
|
||||
"""
|
||||
self.portal.sessions.announce_all(" ...")
|
||||
protocol.ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
|
||||
|
||||
|
||||
class MsgPortal2Server(amp.Command):
|
||||
"""
|
||||
Message portal -> server
|
||||
"""
|
||||
arguments = [('sessid', amp.Integer()),
|
||||
('msg', amp.String()),
|
||||
('data', amp.String())]
|
||||
errors = [(Exception, 'EXCEPTION')]
|
||||
response = []
|
||||
|
||||
class MsgServer2Portal(amp.Command):
|
||||
"""
|
||||
Message server -> portal
|
||||
"""
|
||||
arguments = [('sessid', amp.Integer()),
|
||||
('msg', amp.String()),
|
||||
('data', amp.String())]
|
||||
errors = [(Exception, 'EXCEPTION')]
|
||||
response = []
|
||||
|
||||
class ServerAdmin(amp.Command):
|
||||
"""
|
||||
Portal -> Server
|
||||
|
||||
Sent when the portal needs to perform admin
|
||||
operations on the server, such as when a new
|
||||
session connects or resyncs
|
||||
"""
|
||||
arguments = [('sessid', amp.Integer()),
|
||||
('operation', amp.String()),
|
||||
('data', amp.String())]
|
||||
errors = [(Exception, 'EXCEPTION')]
|
||||
response = []
|
||||
|
||||
class PortalAdmin(amp.Command):
|
||||
"""
|
||||
Server -> Portal
|
||||
|
||||
Sent when the server needs to perform admin
|
||||
operations on the portal.
|
||||
"""
|
||||
arguments = [('sessid', amp.Integer()),
|
||||
('operation', amp.String()),
|
||||
('data', amp.String())]
|
||||
errors = [(Exception, 'EXCEPTION')]
|
||||
response = []
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Core AMP protocol for communication Server <-> Portal
|
||||
#------------------------------------------------------------
|
||||
|
||||
class AMPProtocol(amp.AMP):
|
||||
"""
|
||||
This is the protocol that the MUD server and the proxy server
|
||||
communicate to each other with. AMP is a bi-directional protocol, so
|
||||
both the proxy and the MUD use the same commands and protocol.
|
||||
|
||||
AMP specifies responder methods here and connect them to amp.Command
|
||||
subclasses that specify the datatypes of the input/output of these methods.
|
||||
"""
|
||||
|
||||
# helper methods
|
||||
|
||||
def connectionMade(self):
|
||||
"""
|
||||
This is called when a connection is established
|
||||
between server and portal. It is called on both sides,
|
||||
so we need to make sure to only trigger resync from the
|
||||
server side.
|
||||
"""
|
||||
if hasattr(self.factory, "portal"):
|
||||
sessdata = self.factory.portal.sessions.get_all_sync_data()
|
||||
self.call_remote_ServerAdmin(0,
|
||||
"PSYNC",
|
||||
data=sessdata)
|
||||
if get_restart_mode(SERVER_RESTART):
|
||||
msg = _(" ... Server restarted.")
|
||||
self.factory.portal.sessions.announce_all(msg)
|
||||
|
||||
|
||||
# Error handling
|
||||
|
||||
def errback(self, e, info):
|
||||
"error handler, to avoid dropping connections on server tracebacks."
|
||||
e.trap(Exception)
|
||||
print _("AMP Error for %(info)s: %(e)s") % {'info': info, 'e': e.getErrorMessage()}
|
||||
|
||||
|
||||
# Message definition + helper methods to call/create each message type
|
||||
|
||||
# Portal -> Server Msg
|
||||
|
||||
def amp_msg_portal2server(self, sessid, msg, data):
|
||||
"""
|
||||
Relays message to server. This method is executed on the Server.
|
||||
"""
|
||||
#print "msg portal -> server (server side):", sessid, msg
|
||||
self.factory.server.sessions.data_in(sessid, msg, pickle.loads(utils.to_str(data)))
|
||||
return {}
|
||||
MsgPortal2Server.responder(amp_msg_portal2server)
|
||||
|
||||
def call_remote_MsgPortal2Server(self, sessid, msg, data=""):
|
||||
"""
|
||||
Access method called by the Portal and executed on the Portal.
|
||||
"""
|
||||
#print "msg portal->server (portal side):", sessid, msg
|
||||
self.callRemote(MsgPortal2Server,
|
||||
sessid=sessid,
|
||||
msg=msg,
|
||||
data=utils.to_str(pickle.dumps(data))).addErrback(self.errback, "MsgPortal2Server")
|
||||
|
||||
# Server -> Portal message
|
||||
|
||||
def amp_msg_server2portal(self, sessid, msg, data):
|
||||
"""
|
||||
Relays message to Portal. This method is executed on the Portal.
|
||||
"""
|
||||
#print "msg server->portal (portal side):", sessid, msg
|
||||
self.factory.portal.sessions.data_out(sessid, msg, pickle.loads(utils.to_str(data)))
|
||||
return {}
|
||||
MsgServer2Portal.responder(amp_msg_server2portal)
|
||||
|
||||
def call_remote_MsgServer2Portal(self, sessid, msg, data=""):
|
||||
"""
|
||||
Access method called by the Server and executed on the Server.
|
||||
"""
|
||||
#print "msg server->portal (server side):", sessid, msg, data
|
||||
self.callRemote(MsgServer2Portal,
|
||||
sessid=sessid,
|
||||
msg=utils.to_str(msg),
|
||||
data=utils.to_str(pickle.dumps(data))).addErrback(self.errback, "MsgServer2Portal")
|
||||
|
||||
|
||||
# Server administration from the Portal side
|
||||
|
||||
def amp_server_admin(self, sessid, operation, data):
|
||||
"""
|
||||
This allows the portal to perform admin
|
||||
operations on the server. This is executed on the Server.
|
||||
|
||||
"""
|
||||
data = pickle.loads(utils.to_str(data))
|
||||
|
||||
#print "serveradmin (server side):", sessid, operation, data
|
||||
|
||||
if operation == 'PCONN': #portal_session_connect
|
||||
# create a new, session and sync it
|
||||
sess = ServerSession()
|
||||
sess.sessionhandler = self.factory.server.sessions
|
||||
sess.load_sync_data(data)
|
||||
if sess.logged_in and sess.uid:
|
||||
# this can happen in the case of auto-authenticating protocols like SSH
|
||||
|
||||
sess.player = PlayerDB.objects.get_player_from_uid(sess.uid)
|
||||
sess.at_sync() # this runs initialization without acr
|
||||
|
||||
self.factory.server.sessions.portal_connect(sessid, sess)
|
||||
|
||||
elif operation == 'PDISCONN': #'portal_session_disconnect'
|
||||
# session closed from portal side
|
||||
self.factory.server.sessions.portal_disconnect(sessid)
|
||||
|
||||
elif operation == 'PSYNC': #'portal_session_sync'
|
||||
# force a resync of sessions when portal reconnects to server (e.g. after a server reboot)
|
||||
# the data kwarg contains a dict {sessid: {arg1:val1,...}} representing the attributes
|
||||
# to sync for each session.
|
||||
sesslist = []
|
||||
server_sessionhandler = self.factory.server.sessions
|
||||
for sessid, sessdict in data.items():
|
||||
sess = ServerSession()
|
||||
sess.sessionhandler = server_sessionhandler
|
||||
sess.load_sync_data(sessdict)
|
||||
if sess.uid:
|
||||
sess.player = PlayerDB.objects.get_player_from_uid(sess.uid)
|
||||
sess.at_sync()
|
||||
sesslist.append(sess)
|
||||
# replace sessions on server
|
||||
server_sessionhandler.portal_session_sync(sesslist)
|
||||
|
||||
# after sync is complete we force-validate all scripts (this starts everthing)
|
||||
init_mode = ServerConfig.objects.conf("server_restart_mode", default=None)
|
||||
ScriptDB.objects.validate(init_mode=init_mode)
|
||||
ServerConfig.objects.conf("server_restart_mode", delete=True)
|
||||
|
||||
else:
|
||||
raise Exception(_("operation %(op)s not recognized.") % {'op': operation})
|
||||
|
||||
|
||||
return {}
|
||||
ServerAdmin.responder(amp_server_admin)
|
||||
|
||||
def call_remote_ServerAdmin(self, sessid, operation="", data=""):
|
||||
"""
|
||||
Access method called by the Portal and Executed on the Portal.
|
||||
"""
|
||||
#print "serveradmin (portal side):", sessid, operation, data
|
||||
data = utils.to_str(pickle.dumps(data))
|
||||
|
||||
self.callRemote(ServerAdmin,
|
||||
sessid=sessid,
|
||||
operation=operation,
|
||||
data=data).addErrback(self.errback, "ServerAdmin")
|
||||
|
||||
# Portal administraton from the Server side
|
||||
|
||||
def amp_portal_admin(self, sessid, operation, data):
|
||||
"""
|
||||
This allows the server to perform admin
|
||||
operations on the portal. This is executed on the Portal.
|
||||
"""
|
||||
data = pickle.loads(utils.to_str(data))
|
||||
|
||||
#print "portaladmin (portal side):", sessid, operation, data
|
||||
if operation == 'SLOGIN': # 'server_session_login'
|
||||
# a session has authenticated; sync it.
|
||||
sess = self.factory.portal.sessions.get_session(sessid)
|
||||
sess.load_sync_data(data)
|
||||
|
||||
elif operation == 'SDISCONN': #'server_session_disconnect'
|
||||
# the server is ordering to disconnect the session
|
||||
self.factory.portal.sessions.server_disconnect(sessid, reason=data)
|
||||
|
||||
elif operation == 'SDISCONNALL': #'server_session_disconnect_all'
|
||||
# server orders all sessions to disconnect
|
||||
self.factory.portal.sessions.server_disconnect_all(reason=data)
|
||||
|
||||
elif operation == 'SSHUTD': #server_shutdown'
|
||||
# the server orders the portal to shut down
|
||||
self.factory.portal.shutdown(restart=False)
|
||||
|
||||
elif operation == 'SSYNC': #'server_session_sync'
|
||||
# server wants to save session data to the portal, maybe because
|
||||
# it's about to shut down. We don't overwrite any sessions,
|
||||
# just update data on them and remove eventual ones that are
|
||||
# out of sync (shouldn't happen normally).
|
||||
|
||||
portal_sessionhandler = self.factory.portal.sessions.sessions
|
||||
|
||||
to_save = [sessid for sessid in data if sessid in portal_sessionhandler.sessions]
|
||||
to_delete = [sessid for sessid in data if sessid not in to_save]
|
||||
|
||||
# save protocols
|
||||
for sessid in to_save:
|
||||
portal_sessionhandler.sessions[sessid].load_sync_data(data[sessid])
|
||||
# disconnect missing protocols
|
||||
for sessid in to_delete:
|
||||
portal_sessionhandler.server_disconnect(sessid)
|
||||
else:
|
||||
raise Exception(_("operation %(op)s not recognized.") % {'op': operation})
|
||||
return {}
|
||||
PortalAdmin.responder(amp_portal_admin)
|
||||
|
||||
def call_remote_PortalAdmin(self, sessid, operation="", data=""):
|
||||
"""
|
||||
Access method called by the server side.
|
||||
"""
|
||||
#print "portaladmin (server side):", sessid, operation, data
|
||||
data = utils.to_str(pickle.dumps(data))
|
||||
|
||||
self.callRemote(PortalAdmin,
|
||||
sessid=sessid,
|
||||
operation=operation,
|
||||
data=data).addErrback(self.errback, "PortalAdmin")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -13,6 +13,9 @@ from src.server.models import ServerConfig
|
|||
from src.help.models import HelpEntry
|
||||
from src.utils import create
|
||||
|
||||
# i18n
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
def create_config_values():
|
||||
"""
|
||||
Creates the initial config values.
|
||||
|
|
@ -31,7 +34,7 @@ def create_objects():
|
|||
Creates the #1 player and Limbo room.
|
||||
"""
|
||||
|
||||
print " Creating objects (Player #1 and Limbo room) ..."
|
||||
print _(" Creating objects (Player #1 and Limbo room) ...")
|
||||
|
||||
# Set the initial User's account object's username on the #1 object.
|
||||
# This object is pure django and only holds name, email and password.
|
||||
|
|
@ -55,7 +58,7 @@ def create_objects():
|
|||
typeclass=character_typeclass,
|
||||
user=god_user)
|
||||
god_character.id = 1
|
||||
god_character.db.desc = 'This is User #1.'
|
||||
god_character.db.desc = _('This is User #1.')
|
||||
god_character.locks.add("examine:perm(Immortals);edit:false();delete:false();boot:false();msg:all();puppet:false()")
|
||||
|
||||
god_character.save()
|
||||
|
|
@ -63,12 +66,13 @@ def create_objects():
|
|||
# Limbo is the initial starting room.
|
||||
|
||||
room_typeclass = settings.BASE_ROOM_TYPECLASS
|
||||
limbo_obj = create.create_object(room_typeclass, 'Limbo')
|
||||
limbo_obj = create.create_object(room_typeclass, _('Limbo'))
|
||||
limbo_obj.id = 2
|
||||
string = "Welcome to your new %chEvennia%cn-based game."
|
||||
string = " Welcome to your new {wEvennia{n-based game."
|
||||
string += " From here you are ready to begin development."
|
||||
string += " If you should need help or would like to participate"
|
||||
string += " in community discussions, visit http://evennia.com."
|
||||
string = _(string)
|
||||
limbo_obj.db.desc = string
|
||||
limbo_obj.save()
|
||||
|
||||
|
|
@ -80,7 +84,7 @@ def create_channels():
|
|||
"""
|
||||
Creates some sensible default channels.
|
||||
"""
|
||||
print " Creating default channels ..."
|
||||
print _(" Creating default channels ...")
|
||||
|
||||
# public channel
|
||||
key, aliases, desc, locks = settings.CHANNEL_PUBLIC
|
||||
|
|
@ -103,12 +107,12 @@ def import_MUX_help_files():
|
|||
"""
|
||||
Imports the MUX help files.
|
||||
"""
|
||||
print " Importing MUX help database (devel reference only) ..."
|
||||
print _(" Importing MUX help database (devel reference only) ...")
|
||||
management.call_command('loaddata', '../src/help/mux_help_db.json', verbosity=0)
|
||||
# categorize the MUX help files into its own category.
|
||||
default_category = "MUX"
|
||||
print " Moving imported help db to help category '%s'." \
|
||||
% default_category
|
||||
print _(" Moving imported help db to help category '%(default)s'." \
|
||||
% {'default': default_category})
|
||||
HelpEntry.objects.all_to_category(default_category)
|
||||
|
||||
def create_system_scripts():
|
||||
|
|
@ -118,7 +122,7 @@ def create_system_scripts():
|
|||
"""
|
||||
from src.scripts import scripts
|
||||
|
||||
print " Creating and starting global scripts ..."
|
||||
print _(" Creating and starting global scripts ...")
|
||||
|
||||
# check so that all sessions are alive.
|
||||
script1 = create.create_script(scripts.CheckSessions)
|
||||
|
|
@ -127,7 +131,7 @@ def create_system_scripts():
|
|||
# update the channel handler to make sure it's in sync
|
||||
script3 = create.create_script(scripts.ValidateChannelHandler)
|
||||
if not script1 or not script2 or not script3:
|
||||
print " Error creating system scripts."
|
||||
print _(" Error creating system scripts.")
|
||||
|
||||
def start_game_time():
|
||||
"""
|
||||
|
|
@ -136,7 +140,7 @@ def start_game_time():
|
|||
the total run time of the server as well as its current uptime
|
||||
(the uptime can also be found directly from the server though).
|
||||
"""
|
||||
print " Starting in-game time ..."
|
||||
print _(" Starting in-game time ...")
|
||||
from src.utils import gametime
|
||||
gametime.init_gametime()
|
||||
|
||||
|
|
@ -155,17 +159,17 @@ def create_admin_media_links():
|
|||
dpath = os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
|
||||
apath = os.path.join(settings.ADMIN_MEDIA_ROOT)
|
||||
if os.path.isdir(apath):
|
||||
print " ADMIN_MEDIA_ROOT already exists. Ignored."
|
||||
print _(" ADMIN_MEDIA_ROOT already exists. Ignored.")
|
||||
return
|
||||
if os.name == 'nt':
|
||||
print " Admin-media files copied to ADMIN_MEDIA_ROOT (Windows mode)."
|
||||
print _(" Admin-media files copied to ADMIN_MEDIA_ROOT (Windows mode).")
|
||||
os.mkdir(apath)
|
||||
os.system('xcopy "%s" "%s" /e /q /c' % (dpath, apath))
|
||||
if os.name == 'posix':
|
||||
os.symlink(dpath, apath)
|
||||
print " Admin-media symlinked to ADMIN_MEDIA_ROOT."
|
||||
print _(" Admin-media symlinked to ADMIN_MEDIA_ROOT.")
|
||||
else:
|
||||
print " Admin-media files should be copied manually to ADMIN_MEDIA_ROOT."
|
||||
print _(" Admin-media files should be copied manually to ADMIN_MEDIA_ROOT.")
|
||||
|
||||
def handle_setup(last_step):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ from src.utils.idmapper.models import SharedMemoryModel
|
|||
from src.utils import logger, utils
|
||||
from src.server.manager import ServerConfigManager
|
||||
|
||||
# i18n
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# ServerConfig
|
||||
|
|
@ -82,7 +85,7 @@ class ServerConfig(SharedMemoryModel):
|
|||
"Setter. Allows for self.value = value"
|
||||
if utils.has_parent('django.db.models.base.Model', value):
|
||||
# we have to protect against storing db objects.
|
||||
logger.log_errmsg("ServerConfig cannot store db objects! (%s)" % value)
|
||||
logger.log_errmsg(_("ServerConfig cannot store db objects! (%s)" % value))
|
||||
return
|
||||
self.db_value = pickle.dumps(value)
|
||||
self.save()
|
||||
|
|
|
|||
304
src/server/portal.py
Normal file
304
src/server/portal.py
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
"""
|
||||
This module implements the main Evennia server process, the core of
|
||||
the game engine.
|
||||
|
||||
This module should be started with the 'twistd' executable since it
|
||||
sets up all the networking features. (this is done automatically
|
||||
by game/evennia.py).
|
||||
|
||||
"""
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
if os.name == 'nt':
|
||||
# For Windows batchfile we need an extra path insertion here.
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(
|
||||
os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from twisted.application import internet, service
|
||||
from twisted.internet import protocol, reactor
|
||||
from twisted.web import server, static
|
||||
from django.conf import settings
|
||||
from src.utils.utils import get_evennia_version
|
||||
from src.server.sessionhandler import PORTAL_SESSIONS
|
||||
|
||||
if os.name == 'nt':
|
||||
# For Windows we need to handle pid files manually.
|
||||
PORTAL_PIDFILE = os.path.join(settings.GAME_DIR, 'portal.pid')
|
||||
|
||||
# i18n
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Evennia Portal settings
|
||||
#------------------------------------------------------------
|
||||
|
||||
VERSION = get_evennia_version()
|
||||
|
||||
SERVERNAME = settings.SERVERNAME
|
||||
|
||||
PORTAL_RESTART = os.path.join(settings.GAME_DIR, 'portal.restart')
|
||||
|
||||
TELNET_PORTS = settings.TELNET_PORTS
|
||||
SSL_PORTS = settings.SSL_PORTS
|
||||
SSH_PORTS = settings.SSH_PORTS
|
||||
WEBSERVER_PORTS = settings.WEBSERVER_PORTS
|
||||
|
||||
TELNET_INTERFACES = settings.TELNET_INTERFACES
|
||||
SSL_INTERFACES = settings.SSL_INTERFACES
|
||||
SSH_INTERFACES = settings.SSH_INTERFACES
|
||||
WEBSERVER_INTERFACES = settings.WEBSERVER_INTERFACES
|
||||
|
||||
TELNET_ENABLED = settings.TELNET_ENABLED and TELNET_PORTS and TELNET_INTERFACES
|
||||
SSL_ENABLED = settings.SSL_ENABLED and SSL_PORTS and SSL_INTERFACES
|
||||
SSH_ENABLED = settings.SSH_ENABLED and SSH_PORTS and SSH_INTERFACES
|
||||
WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS and WEBSERVER_INTERFACES
|
||||
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
|
||||
IMC2_ENABLED = settings.IMC2_ENABLED
|
||||
IRC_ENABLED = settings.IRC_ENABLED
|
||||
|
||||
AMP_HOST = settings.AMP_HOST
|
||||
AMP_PORT = settings.AMP_PORT
|
||||
AMP_ENABLED = AMP_HOST and AMP_PORT
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Portal Service object
|
||||
#------------------------------------------------------------
|
||||
class Portal(object):
|
||||
|
||||
"""
|
||||
The main Portal server handler. This object sets up the database and
|
||||
tracks and interlinks all the twisted network services that make up
|
||||
Portal.
|
||||
"""
|
||||
|
||||
def __init__(self, application):
|
||||
"""
|
||||
Setup the server.
|
||||
|
||||
application - an instantiated Twisted application
|
||||
|
||||
"""
|
||||
sys.path.append('.')
|
||||
|
||||
# create a store of services
|
||||
self.services = service.IServiceCollection(application)
|
||||
self.amp_protocol = None # set by amp factory
|
||||
self.sessions = PORTAL_SESSIONS
|
||||
self.sessions.portal = self
|
||||
|
||||
print '\n' + '-'*50
|
||||
|
||||
# Make info output to the terminal.
|
||||
self.terminal_output()
|
||||
|
||||
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 = False
|
||||
|
||||
def terminal_output(self):
|
||||
"""
|
||||
Outputs server startup info to the terminal.
|
||||
"""
|
||||
print _(' %(servername)s Portal (%(version)s) started.') % {'servername': SERVERNAME, 'version': VERSION}
|
||||
if AMP_ENABLED:
|
||||
print " amp (Server): %s" % AMP_PORT
|
||||
if TELNET_ENABLED:
|
||||
ports = ", ".join([str(port) for port in TELNET_PORTS])
|
||||
ifaces = ",".join([" %s" % iface for iface in TELNET_INTERFACES if iface != '0.0.0.0'])
|
||||
print " telnet%s: %s" % (ifaces, ports)
|
||||
if SSH_ENABLED:
|
||||
ports = ", ".join([str(port) for port in SSH_PORTS])
|
||||
ifaces = ",".join([" %s" % iface for iface in SSH_INTERFACES if iface != '0.0.0.0'])
|
||||
print " ssh%s: %s" % (ifaces, ports)
|
||||
if SSL_ENABLED:
|
||||
ports = ", ".join([str(port) for port in SSL_PORTS])
|
||||
ifaces = ",".join([" %s" % iface for iface in SSL_INTERFACES if iface != '0.0.0.0'])
|
||||
print " ssl%s: %s" % (ifaces, ports)
|
||||
if WEBSERVER_ENABLED:
|
||||
clientstring = ""
|
||||
if WEBCLIENT_ENABLED:
|
||||
clientstring = '/client'
|
||||
ports = ", ".join([str(port) for port in WEBSERVER_PORTS])
|
||||
ifaces = ",".join([" %s" % iface for iface in WEBSERVER_INTERFACES if iface != '0.0.0.0'])
|
||||
print " webserver%s%s: %s" % (clientstring, ifaces, ports)
|
||||
|
||||
def set_restart_mode(self, mode=None):
|
||||
"""
|
||||
This manages the flag file that tells the runner if the server should
|
||||
be restarted or is shutting down. Valid modes are True/False and None.
|
||||
If mode is None, no change will be done to the flag file.
|
||||
"""
|
||||
if mode == None:
|
||||
return
|
||||
f = open(PORTAL_RESTART, 'w')
|
||||
print _("writing mode=%(mode)s to %(portal_restart)s") % {'mode': mode, 'portal_restart': PORTAL_RESTART}
|
||||
f.write(str(mode))
|
||||
f.close()
|
||||
|
||||
def shutdown(self, restart=None, _abrupt=False):
|
||||
"""
|
||||
Shuts down the server from inside it.
|
||||
|
||||
restart - True/False sets the flags so the server will be
|
||||
restarted or not. If None, the current flag setting
|
||||
(set at initialization or previous runs) is used.
|
||||
_abrupt - this is set if server is stopped by a kill command,
|
||||
in which case the reactor is dead anyway.
|
||||
|
||||
Note that restarting (regardless of the setting) will not work
|
||||
if the Portal is currently running in daemon mode. In that
|
||||
case it always needs to be restarted manually.
|
||||
"""
|
||||
self.set_restart_mode(restart)
|
||||
if not _abrupt:
|
||||
reactor.callLater(0, reactor.stop)
|
||||
if os.name == 'nt' and os.path.exists(PORTAL_PIDFILE):
|
||||
# for Windows we need to remove pid files manually
|
||||
os.remove(PORTAL_PIDFILE)
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# Start the Portal proxy server and add all active services
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
|
||||
# twistd requires us to define the variable 'application' so it knows
|
||||
# what to execute from.
|
||||
application = service.Application('Portal')
|
||||
|
||||
# The main Portal server program. This sets up the database
|
||||
# and is where we store all the other services.
|
||||
PORTAL = Portal(application)
|
||||
|
||||
if AMP_ENABLED:
|
||||
|
||||
# The AMP protocol handles the communication between
|
||||
# the portal and the mud server. Only reason to ever deactivate
|
||||
# it would be during testing and debugging.
|
||||
|
||||
from src.server import amp
|
||||
|
||||
factory = amp.AmpClientFactory(PORTAL)
|
||||
amp_client = internet.TCPClient(AMP_HOST, AMP_PORT, factory)
|
||||
amp_client.setName('evennia_amp')
|
||||
PORTAL.services.addService(amp_client)
|
||||
|
||||
# We group all the various services under the same twisted app.
|
||||
# These will gradually be started as they are initialized below.
|
||||
|
||||
if TELNET_ENABLED:
|
||||
|
||||
# Start telnet game connections
|
||||
|
||||
from src.server import telnet
|
||||
|
||||
for interface in TELNET_INTERFACES:
|
||||
ifacestr = ""
|
||||
if interface != '0.0.0.0' or len(TELNET_INTERFACES) > 1:
|
||||
ifacestr = "-%s" % interface
|
||||
for port in TELNET_PORTS:
|
||||
pstring = "%s:%s" % (ifacestr, port)
|
||||
factory = protocol.ServerFactory()
|
||||
factory.protocol = telnet.TelnetProtocol
|
||||
factory.sessionhandler = PORTAL_SESSIONS
|
||||
telnet_service = internet.TCPServer(port, factory, interface=interface)
|
||||
telnet_service.setName('EvenniaTelnet%s' % pstring)
|
||||
PORTAL.services.addService(telnet_service)
|
||||
|
||||
if SSL_ENABLED:
|
||||
|
||||
# Start SSL game connection (requires PyOpenSSL).
|
||||
|
||||
from src.server import ssl
|
||||
|
||||
for interface in SSL_INTERFACES:
|
||||
ifacestr = ""
|
||||
if interface != '0.0.0.0' or len(SSL_INTERFACES) > 1:
|
||||
ifacestr = "-%s" % interface
|
||||
for port in SSL_PORTS:
|
||||
pstring = "%s:%s" % (ifacestr, port)
|
||||
factory = protocol.ServerFactory()
|
||||
factory.sessionhandler = PORTAL_SESSIONS
|
||||
factory.protocol = ssl.SSLProtocol
|
||||
ssl_service = internet.SSLServer(port, factory, ssl.getSSLContext(), interface=interface)
|
||||
ssl_service.setName('EvenniaSSL%s' % pstring)
|
||||
PORTAL.services.addService(ssl_service)
|
||||
|
||||
if SSH_ENABLED:
|
||||
|
||||
# Start SSH game connections. Will create a keypair in evennia/game if necessary.
|
||||
|
||||
from src.server import ssh
|
||||
|
||||
for interface in SSH_INTERFACES:
|
||||
ifacestr = ""
|
||||
if interface != '0.0.0.0' or len(SSH_INTERFACES) > 1:
|
||||
ifacestr = "-%s" % interface
|
||||
for port in SSH_PORTS:
|
||||
pstring = "%s:%s" % (ifacestr, port)
|
||||
factory = ssh.makeFactory({'protocolFactory':ssh.SshProtocol,
|
||||
'protocolArgs':(),
|
||||
'sessions':PORTAL_SESSIONS})
|
||||
ssh_service = internet.TCPServer(port, factory, interface=interface)
|
||||
ssh_service.setName('EvenniaSSH%s' % pstring)
|
||||
PORTAL.services.addService(ssh_service)
|
||||
|
||||
if WEBSERVER_ENABLED:
|
||||
|
||||
# Start a django-compatible webserver.
|
||||
|
||||
from twisted.python import threadpool
|
||||
from src.server.webserver import DjangoWebRoot, WSGIWebServer
|
||||
|
||||
# start a thread pool and define the root url (/) as a wsgi resource
|
||||
# recognized by Django
|
||||
threads = threadpool.ThreadPool()
|
||||
web_root = DjangoWebRoot(threads)
|
||||
# point our media resources to url /media
|
||||
web_root.putChild("media", static.File(settings.MEDIA_ROOT))
|
||||
|
||||
if WEBCLIENT_ENABLED:
|
||||
# create ajax client processes at /webclientdata
|
||||
from src.server.webclient import WebClient
|
||||
webclient = WebClient()
|
||||
webclient.sessionhandler = PORTAL_SESSIONS
|
||||
web_root.putChild("webclientdata", webclient)
|
||||
|
||||
web_site = server.Site(web_root, logPath=settings.HTTP_LOG_FILE)
|
||||
|
||||
for interface in WEBSERVER_INTERFACES:
|
||||
ifacestr = ""
|
||||
if interface != '0.0.0.0' or len(WEBSERVER_INTERFACES) > 1:
|
||||
ifacestr = "-%s" % interface
|
||||
for port in WEBSERVER_PORTS:
|
||||
pstring = "%s:%s" % (ifacestr, port)
|
||||
# create the webserver
|
||||
webserver = WSGIWebServer(threads, port, web_site, interface=interface)
|
||||
webserver.setName('EvenniaWebServer%s' % pstring)
|
||||
PORTAL.services.addService(webserver)
|
||||
|
||||
if IRC_ENABLED:
|
||||
|
||||
# IRC channel connections
|
||||
|
||||
from src.comms import irc
|
||||
irc.connect_all()
|
||||
|
||||
if IMC2_ENABLED:
|
||||
|
||||
# IMC2 channel connections
|
||||
|
||||
from src.comms import imc2
|
||||
imc2.connect_all()
|
||||
|
||||
if os.name == 'nt':
|
||||
# Windows only: Set PID file manually
|
||||
f = open(os.path.join(settings.GAME_DIR, 'portal.pid'), 'w')
|
||||
f.write(str(os.getpid()))
|
||||
f.close()
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
"""
|
||||
This module implements the main Evennia server process, the core of
|
||||
the game engine. Don't import this module directly! If you need to
|
||||
access the server processes from code, instead go via the session-
|
||||
handler: src.sessionhandler.SESSIONS.server
|
||||
the game engine.
|
||||
|
||||
This module should be started with the 'twistd' executable since it
|
||||
sets up all the networking features. (this is done automatically
|
||||
|
|
@ -12,6 +10,7 @@ by game/evennia.py).
|
|||
import time
|
||||
import sys
|
||||
import os
|
||||
import signal
|
||||
if os.name == 'nt':
|
||||
# For Windows batchfile we need an extra path insertion here.
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(
|
||||
|
|
@ -22,14 +21,23 @@ from twisted.internet import protocol, reactor, defer
|
|||
from twisted.web import server, static
|
||||
from django.db import connection
|
||||
from django.conf import settings
|
||||
|
||||
from src.scripts.models import ScriptDB
|
||||
from src.server.models import ServerConfig
|
||||
from src.server.sessionhandler import SESSIONS
|
||||
from src.server import initial_setup
|
||||
|
||||
from src.utils.utils import get_evennia_version
|
||||
from src.comms import channelhandler
|
||||
from src.server.sessionhandler import SESSIONS
|
||||
|
||||
if os.name == 'nt':
|
||||
# For Windows we need to handle pid files manually.
|
||||
SERVER_PIDFILE = os.path.join(settings.GAME_DIR, 'server.pid')
|
||||
|
||||
SERVER_RESTART = os.path.join(settings.GAME_DIR, 'server.restart')
|
||||
|
||||
# i18n
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Evennia Server settings
|
||||
|
|
@ -38,23 +46,10 @@ from src.comms import channelhandler
|
|||
SERVERNAME = settings.SERVERNAME
|
||||
VERSION = get_evennia_version()
|
||||
|
||||
TELNET_PORTS = settings.TELNET_PORTS
|
||||
SSL_PORTS = settings.SSL_PORTS
|
||||
SSH_PORTS = settings.SSH_PORTS
|
||||
WEBSERVER_PORTS = settings.WEBSERVER_PORTS
|
||||
AMP_ENABLED = True
|
||||
AMP_HOST = settings.AMP_HOST
|
||||
AMP_PORT = settings.AMP_PORT
|
||||
|
||||
TELNET_INTERFACES = settings.TELNET_INTERFACES
|
||||
SSL_INTERFACES = settings.SSL_INTERFACES
|
||||
SSH_INTERFACES = settings.SSH_INTERFACES
|
||||
WEBSERVER_INTERFACES = settings.WEBSERVER_INTERFACES
|
||||
|
||||
TELNET_ENABLED = settings.TELNET_ENABLED and TELNET_PORTS and TELNET_INTERFACES
|
||||
SSL_ENABLED = settings.SSL_ENABLED and SSL_PORTS and SSL_INTERFACES
|
||||
SSH_ENABLED = settings.SSH_ENABLED and SSH_PORTS and SSH_INTERFACES
|
||||
WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS and WEBSERVER_INTERFACES
|
||||
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
|
||||
IMC2_ENABLED = settings.IMC2_ENABLED
|
||||
IRC_ENABLED = settings.IRC_ENABLED
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Evennia Main Server object
|
||||
|
|
@ -75,10 +70,13 @@ class Evennia(object):
|
|||
|
||||
"""
|
||||
sys.path.append('.')
|
||||
|
||||
|
||||
# create a store of services
|
||||
self.services = service.IServiceCollection(application)
|
||||
|
||||
self.amp_protocol = None # set by amp factory
|
||||
self.sessions = SESSIONS
|
||||
self.sessions.server = self
|
||||
|
||||
print '\n' + '-'*50
|
||||
|
||||
# Database-specific startup optimizations.
|
||||
|
|
@ -87,20 +85,11 @@ class Evennia(object):
|
|||
# Run the initial setup if needed
|
||||
self.run_initial_setup()
|
||||
|
||||
# we have to null this here.
|
||||
SESSIONS.session_count(0)
|
||||
# we link ourself to the sessionhandler so other modules don't have to
|
||||
# re-import the server module itself (which would re-initialize it).
|
||||
SESSIONS.server = self
|
||||
|
||||
self.start_time = time.time()
|
||||
|
||||
# initialize channelhandler
|
||||
channelhandler.CHANNELHANDLER.update()
|
||||
|
||||
# init all global scripts
|
||||
ScriptDB.objects.validate(init_mode=True)
|
||||
|
||||
|
||||
# Make info output to the terminal.
|
||||
self.terminal_output()
|
||||
|
||||
|
|
@ -138,7 +127,7 @@ class Evennia(object):
|
|||
if not last_initial_setup_step:
|
||||
# None is only returned if the config does not exist,
|
||||
# i.e. this is an empty DB that needs populating.
|
||||
print ' Server started for the first time. Setting defaults.'
|
||||
print _(' Server started for the first time. Setting defaults.')
|
||||
initial_setup.handle_setup(0)
|
||||
print '-'*50
|
||||
elif int(last_initial_setup_step) >= 0:
|
||||
|
|
@ -146,51 +135,86 @@ class Evennia(object):
|
|||
# modules and setup will resume from this step, retrying
|
||||
# the last failed module. When all are finished, the step
|
||||
# is set to -1 to show it does not need to be run again.
|
||||
print ' Resuming initial setup from step %s.' % \
|
||||
last_initial_setup_step
|
||||
print _(' Resuming initial setup from step %(last)s.' % \
|
||||
{'last': last_initial_setup_step})
|
||||
initial_setup.handle_setup(int(last_initial_setup_step))
|
||||
print '-'*50
|
||||
|
||||
|
||||
def terminal_output(self):
|
||||
"""
|
||||
Outputs server startup info to the terminal.
|
||||
"""
|
||||
print ' %s (%s) started on port(s):' % (SERVERNAME, VERSION)
|
||||
if TELNET_ENABLED:
|
||||
ports = ", ".join([str(port) for port in TELNET_PORTS])
|
||||
ifaces = ",".join([" %s" % iface for iface in TELNET_INTERFACES if iface != '0.0.0.0'])
|
||||
print " telnet%s: %s" % (ifaces, ports)
|
||||
if SSH_ENABLED:
|
||||
ports = ", ".join([str(port) for port in SSH_PORTS])
|
||||
ifaces = ",".join([" %s" % iface for iface in SSH_INTERFACES if iface != '0.0.0.0'])
|
||||
print " ssh%s: %s" % (ifaces, ports)
|
||||
if SSL_ENABLED:
|
||||
ports = ", ".join([str(port) for port in SSL_PORTS])
|
||||
ifaces = ",".join([" %s" % iface for iface in SSL_INTERFACES if iface != '0.0.0.0'])
|
||||
print " ssl%s: %s" % (ifaces, ports)
|
||||
if WEBSERVER_ENABLED:
|
||||
clientstring = ""
|
||||
if WEBCLIENT_ENABLED:
|
||||
clientstring = '/client'
|
||||
ports = ", ".join([str(port) for port in WEBSERVER_PORTS])
|
||||
ifaces = ",".join([" %s" % iface for iface in WEBSERVER_INTERFACES if iface != '0.0.0.0'])
|
||||
print " webserver%s%s: %s" % (clientstring, ifaces, ports)
|
||||
print _(' %(servername)s Portal (%(version)s) started.') % {'servername': SERVERNAME, 'version': VERSION}
|
||||
print ' amp (Portal): %s' % AMP_PORT
|
||||
|
||||
def shutdown(self, message="{rThe server has been shutdown. Disconnecting.{n", _abrupt=False):
|
||||
def set_restart_mode(self, mode=None):
|
||||
"""
|
||||
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).
|
||||
This manages the flag file that tells the runner if the server is
|
||||
reloading, resetting or shutting down. Valid modes are
|
||||
'reload', 'reset', 'shutdown' and None.
|
||||
If mode is None, no change will be done to the flag file.
|
||||
|
||||
message - message to send to all connected sessions
|
||||
_abrupt - only to be used by internal callback_mechanism.
|
||||
Either way, the active restart setting (Restart=True/False) is
|
||||
returned so the server knows which more it's in.
|
||||
"""
|
||||
if mode == None:
|
||||
if os.path.exists(SERVER_RESTART) and 'True' == open(SERVER_RESTART, 'r').read():
|
||||
mode = 'reload'
|
||||
else:
|
||||
mode = 'shutdown'
|
||||
else:
|
||||
restart = mode in ('reload', 'reset')
|
||||
f = open(SERVER_RESTART, 'w')
|
||||
f.write(str(restart))
|
||||
f.close()
|
||||
return mode
|
||||
|
||||
def shutdown(self, mode=None, _abrupt=False):
|
||||
"""
|
||||
SESSIONS.disconnect_all_sessions(reason=message)
|
||||
Shuts down the server from inside it.
|
||||
|
||||
mode - sets the server restart mode.
|
||||
'reload' - server restarts, no "persistent" scripts are stopped, at_reload hooks called.
|
||||
'reset' - server restarts, non-persistent scripts stopped, at_shutdown hooks called.
|
||||
'shutdown' - like reset, but server will not auto-restart.
|
||||
None - keep currently set flag from flag file.
|
||||
_abrupt - this is set if server is stopped by a kill command,
|
||||
in which case the reactor is dead anyway.
|
||||
"""
|
||||
mode = self.set_restart_mode(mode)
|
||||
|
||||
# call shutdown hooks on all cached objects
|
||||
|
||||
from src.objects.models import ObjectDB
|
||||
from src.players.models import PlayerDB
|
||||
from src.server.models import ServerConfig
|
||||
|
||||
if mode == 'reload':
|
||||
# call restart hooks
|
||||
[(o.typeclass(o), o.at_server_reload()) for o in ObjectDB.get_all_cached_instances()]
|
||||
[(p.typeclass(p), p.at_server_reload()) for p in PlayerDB.get_all_cached_instances()]
|
||||
[(s.typeclass(s), s.pause(), s.at_server_reload()) for s in ScriptDB.get_all_cached_instances()]
|
||||
|
||||
ServerConfig.objects.conf("server_restart_mode", "reload")
|
||||
|
||||
else:
|
||||
if mode == 'reset':
|
||||
# don't call disconnect hooks on reset
|
||||
[(o.typeclass(o), o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()]
|
||||
else: # shutdown
|
||||
[(o.typeclass(o), o.at_disconnect(), o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()]
|
||||
[(p.typeclass(p), p.at_server_shutdown()) for p in PlayerDB.get_all_cached_instances()]
|
||||
[(s.typeclass(s), s.at_server_shutdown()) for s in ScriptDB.get_all_cached_instances()]
|
||||
|
||||
ServerConfig.objects.conf("server_restart_mode", "reset")
|
||||
|
||||
if not _abrupt:
|
||||
reactor.callLater(0, reactor.stop)
|
||||
|
||||
|
||||
if os.name == 'nt' and os.path.exists(SERVER_PIDFILE):
|
||||
# for Windows we need to remove pid files manually
|
||||
os.remove(SERVER_PIDFILE)
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# Start the Evennia game server and add all active services
|
||||
|
|
@ -208,108 +232,24 @@ application = service.Application('Evennia')
|
|||
# and is where we store all the other services.
|
||||
EVENNIA = Evennia(application)
|
||||
|
||||
# We group all the various services under the same twisted app.
|
||||
# These will gradually be started as they are initialized below.
|
||||
# The AMP protocol handles the communication between
|
||||
# the portal and the mud server. Only reason to ever deactivate
|
||||
# it would be during testing and debugging.
|
||||
|
||||
if TELNET_ENABLED:
|
||||
if AMP_ENABLED:
|
||||
|
||||
# Start telnet game connections
|
||||
from src.server import amp
|
||||
|
||||
from src.server import telnet
|
||||
|
||||
for interface in TELNET_INTERFACES:
|
||||
ifacestr = ""
|
||||
if interface != '0.0.0.0' or len(TELNET_INTERFACES) > 1:
|
||||
ifacestr = "-%s" % interface
|
||||
for port in TELNET_PORTS:
|
||||
pstring = "%s:%s" % (ifacestr, port)
|
||||
factory = protocol.ServerFactory()
|
||||
factory.protocol = telnet.TelnetProtocol
|
||||
telnet_service = internet.TCPServer(port, factory, interface=interface)
|
||||
telnet_service.setName('EvenniaTelnet%s' % pstring)
|
||||
EVENNIA.services.addService(telnet_service)
|
||||
|
||||
if SSL_ENABLED:
|
||||
|
||||
# Start SSL game connection (requires PyOpenSSL).
|
||||
|
||||
from src.server import ssl
|
||||
|
||||
for interface in SSL_INTERFACES:
|
||||
ifacestr = ""
|
||||
if interface != '0.0.0.0' or len(SSL_INTERFACES) > 1:
|
||||
ifacestr = "-%s" % interface
|
||||
for port in SSL_PORTS:
|
||||
pstring = "%s:%s" % (ifacestr, port)
|
||||
factory = protocol.ServerFactory()
|
||||
factory.protocol = ssl.SSLProtocol
|
||||
ssl_service = internet.SSLServer(port, factory, ssl.getSSLContext(), interface=interface)
|
||||
ssl_service.setName('EvenniaSSL%s' % pstring)
|
||||
EVENNIA.services.addService(ssl_service)
|
||||
|
||||
if SSH_ENABLED:
|
||||
|
||||
# Start SSH game connections. Will create a keypair in evennia/game if necessary.
|
||||
|
||||
from src.server import ssh
|
||||
|
||||
for interface in SSH_INTERFACES:
|
||||
ifacestr = ""
|
||||
if interface != '0.0.0.0' or len(SSH_INTERFACES) > 1:
|
||||
ifacestr = "-%s" % interface
|
||||
for port in SSH_PORTS:
|
||||
pstring = "%s:%s" % (ifacestr, port)
|
||||
factory = ssh.makeFactory({'protocolFactory':ssh.SshProtocol,
|
||||
'protocolArgs':()})
|
||||
ssh_service = internet.TCPServer(port, factory, interface=interface)
|
||||
ssh_service.setName('EvenniaSSH%s' % pstring)
|
||||
EVENNIA.services.addService(ssh_service)
|
||||
|
||||
if WEBSERVER_ENABLED:
|
||||
|
||||
# Start a django-compatible webserver.
|
||||
|
||||
from twisted.python import threadpool
|
||||
from src.server.webserver import DjangoWebRoot, WSGIWebServer
|
||||
|
||||
# start a thread pool and define the root url (/) as a wsgi resource
|
||||
# recognized by Django
|
||||
threads = threadpool.ThreadPool()
|
||||
web_root = DjangoWebRoot(threads)
|
||||
# point our media resources to url /media
|
||||
web_root.putChild("media", static.File(settings.MEDIA_ROOT))
|
||||
|
||||
if WEBCLIENT_ENABLED:
|
||||
# create ajax client processes at /webclientdata
|
||||
from src.server.webclient import WebClient
|
||||
web_root.putChild("webclientdata", WebClient())
|
||||
|
||||
web_site = server.Site(web_root, logPath=settings.HTTP_LOG_FILE)
|
||||
|
||||
for interface in WEBSERVER_INTERFACES:
|
||||
ifacestr = ""
|
||||
if interface != '0.0.0.0' or len(WEBSERVER_INTERFACES) > 1:
|
||||
ifacestr = "-%s" % interface
|
||||
for port in WEBSERVER_PORTS:
|
||||
pstring = "%s:%s" % (ifacestr, port)
|
||||
# create the webserver
|
||||
webserver = WSGIWebServer(threads, port, web_site, interface=interface)
|
||||
webserver.setName('EvenniaWebServer%s' % pstring)
|
||||
EVENNIA.services.addService(webserver)
|
||||
|
||||
if IRC_ENABLED:
|
||||
|
||||
# IRC channel connections
|
||||
|
||||
from src.comms import irc
|
||||
irc.connect_all()
|
||||
|
||||
if IMC2_ENABLED:
|
||||
|
||||
# IMC2 channel connections
|
||||
|
||||
from src.comms import imc2
|
||||
imc2.connect_all()
|
||||
factory = amp.AmpServerFactory(EVENNIA)
|
||||
amp_service = internet.TCPServer(AMP_PORT, factory)
|
||||
amp_service.setName("EvenniaPortal")
|
||||
EVENNIA.services.addService(amp_service)
|
||||
|
||||
# clear server startup mode
|
||||
ServerConfig.objects.conf("server_starting_mode", delete=True)
|
||||
|
||||
if os.name == 'nt':
|
||||
# Windows only: Set PID file manually
|
||||
f = open(os.path.join(settings.GAME_DIR, 'server.pid'), 'w')
|
||||
f.write(str(os.getpid()))
|
||||
f.close()
|
||||
|
|
|
|||
247
src/server/serversession.py
Normal file
247
src/server/serversession.py
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
"""
|
||||
This defines a the Server's generic session object. This object represents
|
||||
a connection to the outside world but don't know any details about how the
|
||||
connection actually happens (so it's the same for telnet, web, ssh etc).
|
||||
|
||||
It is stored on the Server side (as opposed to protocol-specific sessions which
|
||||
are stored on the Portal side)
|
||||
"""
|
||||
|
||||
import time
|
||||
from datetime import datetime
|
||||
from django.conf import settings
|
||||
from src.scripts.models import ScriptDB
|
||||
from src.comms.models import Channel
|
||||
from src.utils import logger
|
||||
from src.commands import cmdhandler
|
||||
|
||||
IDLE_COMMAND = settings.IDLE_COMMAND
|
||||
|
||||
from src.server.session import Session
|
||||
|
||||
# i18n
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Server Session
|
||||
#------------------------------------------------------------
|
||||
|
||||
class ServerSession(Session):
|
||||
"""
|
||||
This class represents a player's session and is a template for
|
||||
individual protocols to communicate with Evennia.
|
||||
|
||||
Each player gets a session assigned to them whenever they connect
|
||||
to the game server. All communication between game and player goes
|
||||
through their session.
|
||||
|
||||
"""
|
||||
|
||||
def at_sync(self):
|
||||
"""
|
||||
This is called whenever a session has been resynced with the portal.
|
||||
At this point all relevant attributes have already been set and self.player
|
||||
been assigned (if applicable).
|
||||
|
||||
Since this is often called after a server restart we need to set up
|
||||
the session as it was.
|
||||
"""
|
||||
if not self.logged_in:
|
||||
return
|
||||
|
||||
player = self.get_player()
|
||||
character = self.get_character()
|
||||
if player:
|
||||
player.at_init()
|
||||
if character:
|
||||
character.at_init()
|
||||
# start (persistent) scripts on this object
|
||||
ScriptDB.objects.validate(obj=character)
|
||||
|
||||
def session_login(self, player):
|
||||
"""
|
||||
Startup mechanisms that need to run at login. This is called
|
||||
by the login command (which need to have handled authentication
|
||||
already before calling this method)
|
||||
|
||||
player - the connected player
|
||||
"""
|
||||
|
||||
# actually do the login by assigning session data
|
||||
|
||||
self.player = player
|
||||
self.user = player.user
|
||||
self.uid = self.user.id
|
||||
self.uname = self.user.username
|
||||
self.logged_in = True
|
||||
self.conn_time = time.time()
|
||||
|
||||
# Update account's last login time.
|
||||
self.user.last_login = datetime.now()
|
||||
self.user.save()
|
||||
|
||||
# player init
|
||||
player.at_init()
|
||||
|
||||
# Check if this is the first time the *player* logs in
|
||||
if player.db.FIRST_LOGIN:
|
||||
player.at_first_login()
|
||||
del player.db.FIRST_LOGIN
|
||||
player.at_pre_login()
|
||||
|
||||
character = player.character
|
||||
character.at_init()
|
||||
if character:
|
||||
# this player has a character. Check if it's the
|
||||
# first time *this character* logs in
|
||||
if character.db.FIRST_LOGIN:
|
||||
character.at_first_login()
|
||||
del character.db.FIRST_LOGIN
|
||||
# run character login hook
|
||||
character.at_pre_login()
|
||||
|
||||
# this is always called first
|
||||
player.at_init()
|
||||
|
||||
self.log(_('Logged in: %(self)s') % {'self': self})
|
||||
|
||||
# start (persistent) scripts on this object
|
||||
ScriptDB.objects.validate(obj=self.player.character)
|
||||
|
||||
#add session to connected list
|
||||
self.sessionhandler.login(self)
|
||||
|
||||
# post-login hooks
|
||||
player.at_post_login()
|
||||
if character:
|
||||
character.at_post_login()
|
||||
|
||||
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
|
||||
accounts.
|
||||
"""
|
||||
if self.logged_in:
|
||||
player = self.get_player()
|
||||
character = self.get_character()
|
||||
if character:
|
||||
character.at_disconnect()
|
||||
uaccount = player.user
|
||||
uaccount.last_login = datetime.now()
|
||||
uaccount.save()
|
||||
self.logged_in = False
|
||||
self.sessionhandler.disconnect(self)
|
||||
|
||||
def get_player(self):
|
||||
"""
|
||||
Get the player associated with this session
|
||||
"""
|
||||
if self.logged_in:
|
||||
return self.player
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_character(self):
|
||||
"""
|
||||
Returns the in-game character associated with this session.
|
||||
This returns the typeclass of the object.
|
||||
"""
|
||||
player = self.get_player()
|
||||
if player:
|
||||
return player.character
|
||||
return None
|
||||
|
||||
def log(self, message, channel=True):
|
||||
"""
|
||||
Emits session info to the appropriate outputs and info channels.
|
||||
"""
|
||||
if channel:
|
||||
try:
|
||||
cchan = settings.CHANNEL_CONNECTINFO
|
||||
cchan = Channel.objects.get_channel(cchan[0])
|
||||
cchan.msg("[%s]: %s" % (cchan.key, message))
|
||||
except Exception:
|
||||
pass
|
||||
logger.log_infomsg(message)
|
||||
|
||||
def update_session_counters(self, idle=False):
|
||||
"""
|
||||
Hit this when the user enters a command in order to update idle timers
|
||||
and command counters.
|
||||
"""
|
||||
# Store the timestamp of the user's last command.
|
||||
self.cmd_last = time.time()
|
||||
if not idle:
|
||||
# Increment the user's command counter.
|
||||
self.cmd_total += 1
|
||||
# Player-visible idle time, not used in idle timeout calcs.
|
||||
self.cmd_last_visible = time.time()
|
||||
|
||||
def execute_cmd(self, command_string):
|
||||
"""
|
||||
Execute a command string on the server.
|
||||
"""
|
||||
# handle the 'idle' command
|
||||
if str(command_string).strip() == IDLE_COMMAND:
|
||||
self.update_session_counters(idle=True)
|
||||
return
|
||||
|
||||
# all other inputs, including empty inputs
|
||||
character = self.get_character()
|
||||
|
||||
|
||||
if character:
|
||||
character.execute_cmd(command_string)
|
||||
else:
|
||||
if self.logged_in:
|
||||
# there is no character, but we are logged in. Use player instead.
|
||||
self.get_player().execute_cmd(command_string)
|
||||
else:
|
||||
# we are not logged in. Use special unlogged-in call.
|
||||
cmdhandler.cmdhandler(self, command_string, unloggedin=True)
|
||||
self.update_session_counters()
|
||||
|
||||
def data_out(self, msg, data=None):
|
||||
"""
|
||||
Send Evennia -> Player
|
||||
"""
|
||||
self.sessionhandler.data_out(self, msg, data)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.address == other.address
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
String representation of the user session class. We use
|
||||
this a lot in the server logs.
|
||||
"""
|
||||
if self.logged_in:
|
||||
symbol = '#'
|
||||
else:
|
||||
symbol = '?'
|
||||
try:
|
||||
address = ":".join([str(part) for part in self.address])
|
||||
except Exception:
|
||||
address = self.address
|
||||
return "<%s> %s@%s" % (symbol, self.uname, address)
|
||||
|
||||
def __unicode__(self):
|
||||
"""
|
||||
Unicode representation
|
||||
"""
|
||||
return u"%s" % str(self)
|
||||
|
||||
|
||||
# easy-access functions
|
||||
|
||||
def login(self, player):
|
||||
"alias for at_login"
|
||||
self.session_login(player)
|
||||
def disconnect(self):
|
||||
"alias for session_disconnect"
|
||||
self.session_disconnect()
|
||||
def msg(self, string='', data=None):
|
||||
"alias for at_data_out"
|
||||
self.data_out(string, data=data)
|
||||
|
|
@ -1,412 +1,125 @@
|
|||
"""
|
||||
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.
|
||||
This defines a generic session class. All connection instances (both
|
||||
on Portal and Server side) should inherit from this class.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import time
|
||||
from datetime import datetime
|
||||
#from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
#from src.objects.models import ObjectDB
|
||||
from src.comms.models import Channel
|
||||
from src.utils import logger, reloads
|
||||
from src.commands import cmdhandler
|
||||
from src.server.sessionhandler import SESSIONS
|
||||
|
||||
IDLE_TIMEOUT = settings.IDLE_TIMEOUT
|
||||
IDLE_COMMAND = settings.IDLE_COMMAND
|
||||
|
||||
|
||||
|
||||
class IOdata(object):
|
||||
"""
|
||||
A simple storage object that allows for storing
|
||||
new attributes on it at creation.
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
"Give keyword arguments to store as new arguments on the object."
|
||||
self.__dict__.update(**kwargs)
|
||||
|
||||
|
||||
def _login(session, player):
|
||||
"""
|
||||
For logging a player in. Removed this from CmdConnect because ssh
|
||||
wanted to call it for autologin.
|
||||
"""
|
||||
# We are logging in, get/setup the player object controlled by player
|
||||
|
||||
# Check if this is the first time the
|
||||
# *player* connects (should be set by the
|
||||
if player.db.FIRST_LOGIN:
|
||||
player.at_first_login()
|
||||
del player.db.FIRST_LOGIN
|
||||
player.at_pre_login()
|
||||
|
||||
character = player.character
|
||||
if character:
|
||||
# this player has a character. Check if it's the
|
||||
# first time *this character* logs in (this should be
|
||||
# set by the initial create command)
|
||||
if character.db.FIRST_LOGIN:
|
||||
character.at_first_login()
|
||||
del character.db.FIRST_LOGIN
|
||||
# run character login hook
|
||||
character.at_pre_login()
|
||||
|
||||
# actually do the login
|
||||
session.session_login(player)
|
||||
|
||||
# post-login hooks
|
||||
player.at_post_login()
|
||||
if character:
|
||||
character.at_post_login()
|
||||
character.execute_cmd('look')
|
||||
else:
|
||||
player.execute_cmd('look')
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# SessionBase class
|
||||
# Server Session
|
||||
#------------------------------------------------------------
|
||||
|
||||
class SessionBase(object):
|
||||
class Session(object):
|
||||
"""
|
||||
This class represents a player's session and is a template for
|
||||
individual protocols to communicate with Evennia.
|
||||
This class represents a player's session and is a template for
|
||||
both portal- and server-side sessions.
|
||||
|
||||
Each player gets a session assigned to them whenever they connect
|
||||
to the game server. All communication between game and player goes
|
||||
through their session.
|
||||
Each connection will see two session instances created:
|
||||
|
||||
1) A Portal session. This is customized for the respective connection
|
||||
protocols that Evennia supports, like Telnet, SSH etc. The Portal session
|
||||
must call init_session() as part of its initialization. The respective
|
||||
hook methods should be connected to the methods unique for the respective
|
||||
protocol so that there is a unified interface to Evennia.
|
||||
2) A Server session. This is the same for all connected players, regardless
|
||||
of how they connect.
|
||||
|
||||
The Portal and Server have their own respective sessionhandlers. These are synced
|
||||
whenever new connections happen or the Server restarts etc, which means much of the
|
||||
same information must be stored in both places e.g. the portal can re-sync with the
|
||||
server when the server reboots.
|
||||
|
||||
"""
|
||||
|
||||
# use this to uniquely identify the protocol name, e.g. "telnet" or "comet"
|
||||
protocol_key = "BaseProtocol"
|
||||
# names of attributes that should be affected by syncing.
|
||||
_attrs_to_sync = ['protocol_key', 'address', 'suid', 'sessid', 'uid', 'uname',
|
||||
'logged_in', 'cid', 'encoding',
|
||||
'conn_time', 'cmd_last', 'cmd_last_visible', 'cmd_total']
|
||||
|
||||
def session_connect(self, address, suid=None):
|
||||
def init_session(self, protocol_key, address, sessionhandler):
|
||||
"""
|
||||
The setup of the session. An address (usually an IP address) on any form is required.
|
||||
|
||||
This should be called by the protocol at connection time.
|
||||
|
||||
suid = this is a session id. Needed by some transport protocols.
|
||||
Initialize the Session. This should be called by the protocol when
|
||||
a new session is established.
|
||||
protocol_key - telnet, ssh, ssl or web
|
||||
address - client address
|
||||
sessionhandler - reference to the sessionhandler instance
|
||||
"""
|
||||
# This is currently 'telnet', 'ssh', 'ssl' or 'web'
|
||||
self.protocol_key = protocol_key
|
||||
# Protocol address tied to this session
|
||||
self.address = address
|
||||
|
||||
# user setup
|
||||
self.name = None
|
||||
# suid is used by some protocols, it's a hex key.
|
||||
self.suid = None
|
||||
|
||||
# unique id for this session
|
||||
self.sessid = 0 # no sessid yet
|
||||
# database id for the user connected to this session
|
||||
self.uid = None
|
||||
self.suid = suid
|
||||
# user name, for easier tracking of sessions
|
||||
self.uname = None
|
||||
# if user has authenticated already or not
|
||||
self.logged_in = False
|
||||
|
||||
# database id of character/object connected to this player session (if any)
|
||||
self.cid = None
|
||||
self.encoding = "utf-8"
|
||||
|
||||
current_time = time.time()
|
||||
|
||||
# The time the user last issued a command.
|
||||
self.cmd_last = current_time
|
||||
# Player-visible idle time, excluding the IDLE command.
|
||||
self.cmd_last_visible = current_time
|
||||
# The time when the user connected.
|
||||
self.conn_time = current_time
|
||||
# Total number of commands issued.
|
||||
self.cmd_total = 0
|
||||
#self.channels_subscribed = {}
|
||||
SESSIONS.add_unloggedin_session(self)
|
||||
# calling hook
|
||||
self.at_connect()
|
||||
|
||||
def session_login(self, player):
|
||||
"""
|
||||
Startup mechanisms that need to run at login
|
||||
|
||||
player - the connected player
|
||||
"""
|
||||
# Check if this is the first time the *player* logs in
|
||||
if player.db.FIRST_LOGIN:
|
||||
player.at_first_login()
|
||||
del player.db.FIRST_LOGIN
|
||||
player.at_pre_login()
|
||||
|
||||
character = player.character
|
||||
if character:
|
||||
# this player has a character. Check if it's the
|
||||
# first time *this character* logs in
|
||||
if character.db.FIRST_LOGIN:
|
||||
character.at_first_login()
|
||||
del character.db.FIRST_LOGIN
|
||||
# run character login hook
|
||||
character.at_pre_login()
|
||||
|
||||
# actually do the login by assigning session data
|
||||
|
||||
self.player = player
|
||||
self.user = player.user
|
||||
self.uid = self.user.id
|
||||
self.name = self.user.username
|
||||
self.logged_in = True
|
||||
# session time statistics
|
||||
self.conn_time = time.time()
|
||||
self.cmd_last_visible = self.conn_time
|
||||
self.cmd_last = self.conn_time
|
||||
self.cmd_total = 0
|
||||
|
||||
# a back-reference to the relevant sessionhandler this
|
||||
# session is stored in.
|
||||
self.sessionhandler = sessionhandler
|
||||
|
||||
def get_sync_data(self):
|
||||
"""
|
||||
Return all data relevant to sync the session
|
||||
"""
|
||||
sessdata = {}
|
||||
for attrname in self._attrs_to_sync:
|
||||
sessdata[attrname] = self.__dict__.get(attrname, None)
|
||||
return sessdata
|
||||
|
||||
def load_sync_data(self, sessdata):
|
||||
"""
|
||||
Takes a session dictionary, as created by get_sync_data,
|
||||
and loads it into the correct attributes of the session.
|
||||
"""
|
||||
for attrname, value in sessdata.items():
|
||||
self.__dict__[attrname] = value
|
||||
|
||||
# Update account's last login time.
|
||||
self.user.last_login = datetime.now()
|
||||
self.user.save()
|
||||
self.log('Logged in: %s' % self)
|
||||
|
||||
# start (persistent) scripts on this object
|
||||
reloads.reload_scripts(obj=self.player.character)
|
||||
|
||||
#add session to connected list
|
||||
SESSIONS.add_loggedin_session(self)
|
||||
|
||||
#call login hook
|
||||
self.at_login(player)
|
||||
|
||||
# post-login hooks
|
||||
player.at_post_login()
|
||||
if character:
|
||||
character.at_post_login()
|
||||
|
||||
def session_disconnect(self):
|
||||
def at_sync(self):
|
||||
"""
|
||||
Clean up the session, removing it from the game and doing some
|
||||
accounting. This method is used also for non-loggedin
|
||||
accounts.
|
||||
|
||||
Note that this methods does not close the connection - this is protocol-dependent
|
||||
and have to be done right after this function!
|
||||
"""
|
||||
if self.logged_in:
|
||||
player = self.get_player()
|
||||
uaccount = player.user
|
||||
uaccount.last_login = datetime.now()
|
||||
uaccount.save()
|
||||
self.at_disconnect()
|
||||
self.logged_in = False
|
||||
SESSIONS.remove_session(self)
|
||||
|
||||
def session_validate(self):
|
||||
"""
|
||||
Validate the session to make sure they have not been idle for too long
|
||||
"""
|
||||
if IDLE_TIMEOUT > 0 and (time.time() - self.cmd_last) > IDLE_TIMEOUT:
|
||||
self.msg("Idle timeout exceeded, disconnecting.")
|
||||
self.session_disconnect()
|
||||
|
||||
def get_player(self):
|
||||
"""
|
||||
Get the player associated with this session
|
||||
"""
|
||||
if self.logged_in:
|
||||
return self.player
|
||||
else:
|
||||
return None
|
||||
|
||||
# if self.logged_in:
|
||||
# character = ObjectDB.objects.get_object_with_user(self.uid)
|
||||
# if not character:
|
||||
# string = "No player match for session uid: %s" % self.uid
|
||||
# logger.log_errmsg(string)
|
||||
# return None
|
||||
# return character.player
|
||||
# return None
|
||||
|
||||
def get_character(self):
|
||||
"""
|
||||
Returns the in-game character associated with a session.
|
||||
This returns the typeclass of the object.
|
||||
"""
|
||||
player = self.get_player()
|
||||
if player:
|
||||
return player.character
|
||||
return None
|
||||
|
||||
def log(self, message, channel=True):
|
||||
"""
|
||||
Emits session info to the appropriate outputs and info channels.
|
||||
"""
|
||||
if channel:
|
||||
try:
|
||||
cchan = settings.CHANNEL_CONNECTINFO
|
||||
cchan = Channel.objects.get_channel(cchan[0])
|
||||
cchan.msg("[%s]: %s" % (cchan.key, message))
|
||||
except Exception:
|
||||
pass
|
||||
logger.log_infomsg(message)
|
||||
|
||||
def update_session_counters(self, idle=False):
|
||||
"""
|
||||
Hit this when the user enters a command in order to update idle timers
|
||||
and command counters.
|
||||
"""
|
||||
# Store the timestamp of the user's last command.
|
||||
self.cmd_last = time.time()
|
||||
if not idle:
|
||||
# Increment the user's command counter.
|
||||
self.cmd_total += 1
|
||||
# Player-visible idle time, not used in idle timeout calcs.
|
||||
self.cmd_last_visible = time.time()
|
||||
|
||||
def execute_cmd(self, command_string):
|
||||
"""
|
||||
Execute a command string.
|
||||
"""
|
||||
|
||||
# handle the 'idle' command
|
||||
if str(command_string).strip() == IDLE_COMMAND:
|
||||
self.update_session_counters(idle=True)
|
||||
return
|
||||
|
||||
# all other inputs, including empty inputs
|
||||
character = self.get_character()
|
||||
|
||||
if character:
|
||||
# normal operation.
|
||||
character.execute_cmd(command_string)
|
||||
#import cProfile
|
||||
#cProfile.runctx("character.execute_cmd(command_string)",
|
||||
# {"command_string":command_string,"character":character}, {}, "execute_cmd.profile")
|
||||
else:
|
||||
if self.logged_in:
|
||||
# there is no character, but we are logged in. Use player instead.
|
||||
self.get_player().execute_cmd(command_string)
|
||||
else:
|
||||
# we are not logged in. Use special unlogged-in call.
|
||||
cmdhandler.cmdhandler(self, command_string, unloggedin=True)
|
||||
self.update_session_counters()
|
||||
|
||||
def get_data_obj(self, **kwargs):
|
||||
"""
|
||||
Create a data object, storing keyword arguments on itself as arguments.
|
||||
"""
|
||||
return IOdata(**kwargs)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.address == other.address
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
String representation of the user session class. We use
|
||||
this a lot in the server logs.
|
||||
"""
|
||||
if self.logged_in:
|
||||
symbol = '#'
|
||||
else:
|
||||
symbol = '?'
|
||||
return "<%s> %s@%s" % (symbol, self.name, self.address,)
|
||||
|
||||
def __unicode__(self):
|
||||
"""
|
||||
Unicode representation
|
||||
"""
|
||||
return u"%s" % str(self)
|
||||
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Session class - inherit from this
|
||||
#------------------------------------------------------------
|
||||
|
||||
class Session(SessionBase):
|
||||
"""
|
||||
The main class to inherit from. Overload the methods here.
|
||||
"""
|
||||
|
||||
# exchange this for a unique name you can use to identify the
|
||||
# protocol type this session uses
|
||||
protocol_key = "TemplateProtocol"
|
||||
|
||||
#
|
||||
# Hook methods
|
||||
#
|
||||
|
||||
def at_connect(self):
|
||||
"""
|
||||
This method is called by the connection mechanic after
|
||||
connection has been made. The session is added to the
|
||||
sessionhandler and basic accounting has been made at this
|
||||
point.
|
||||
|
||||
This is the place to put e.g. welcome screens specific to the
|
||||
protocol.
|
||||
"""
|
||||
pass
|
||||
|
||||
def at_login(self, player):
|
||||
"""
|
||||
This method is called by the login mechanic whenever the user
|
||||
has finished authenticating. The user has been moved to the
|
||||
right sessionhandler list and basic book keeping has been
|
||||
done at this point (so logged_in=True).
|
||||
Called after a session has been fully synced (including
|
||||
secondary operations such as setting self.player based
|
||||
on uid etc).
|
||||
"""
|
||||
pass
|
||||
|
||||
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
|
||||
# access hooks
|
||||
|
||||
def at_data_in(self, string="", data=None):
|
||||
def disconnect(self, reason=None):
|
||||
"""
|
||||
Player -> Evennia
|
||||
"""
|
||||
pass
|
||||
|
||||
def at_data_out(self, string="", data=None):
|
||||
"""
|
||||
Evennia -> Player
|
||||
|
||||
string - an string of any form to send to the player
|
||||
data - a data structure of any form
|
||||
|
||||
generic hook called from the outside to disconnect this session
|
||||
should be connected to the protocols actual disconnect mechanism.
|
||||
"""
|
||||
pass
|
||||
|
||||
# easy-access functions
|
||||
def login(self, player):
|
||||
"alias for at_login"
|
||||
self.at_login(player)
|
||||
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=data)
|
||||
def data_out(self, msg, data=None):
|
||||
"""
|
||||
generic hook for sending data out through the protocol. Server
|
||||
protocols can use this right away. Portal sessions
|
||||
should overload this to format/handle the outgoing data as needed.
|
||||
"""
|
||||
pass
|
||||
|
||||
def data_in(self, msg, data=None):
|
||||
"""
|
||||
hook for protocols to send incoming data to the engine.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -1,31 +1,72 @@
|
|||
"""
|
||||
This module handles sessions of users connecting
|
||||
to the server.
|
||||
This module defines handlers for storing sessions when handles
|
||||
sessions of users connecting to the server.
|
||||
|
||||
Since Evennia supports several different connection
|
||||
protocols, it is important to have a joint place
|
||||
to store session info. It also makes it easier
|
||||
to dispatch data.
|
||||
|
||||
Whereas server.py handles all setup of the server
|
||||
and database itself, this file handles all that
|
||||
comes after initial startup.
|
||||
|
||||
All new sessions (of whatever protocol) are responsible for
|
||||
registering themselves with this module.
|
||||
There are two similar but separate stores of sessions:
|
||||
ServerSessionHandler - this stores generic game sessions
|
||||
for the game. These sessions has no knowledge about
|
||||
how they are connected to the world.
|
||||
PortalSessionHandler - this stores sessions created by
|
||||
twisted protocols. These are dumb connectors that
|
||||
handle network communication but holds no game info.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
import time
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from src.server.models import ServerConfig
|
||||
from src.utils import utils
|
||||
|
||||
# i18n
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
ALLOW_MULTISESSION = settings.ALLOW_MULTISESSION
|
||||
IDLE_TIMEOUT = settings.IDLE_TIMEOUT
|
||||
|
||||
#------------------------------------------------------------
|
||||
# SessionHandler class
|
||||
#------------------------------------------------------------
|
||||
|
||||
class SessionHandler(object):
|
||||
"""
|
||||
This handler holds a stack of sessions.
|
||||
"""
|
||||
def __init__(self):
|
||||
"""
|
||||
Init the handler.
|
||||
"""
|
||||
self.sessions = {}
|
||||
|
||||
def get_sessions(self, include_unloggedin=False):
|
||||
"""
|
||||
Returns the connected session objects.
|
||||
"""
|
||||
if include_unloggedin:
|
||||
return self.sessions.values()
|
||||
else:
|
||||
return [session for session in self.sessions.values() if session.logged_in]
|
||||
|
||||
def get_session(self, sessid):
|
||||
"""
|
||||
Get session by sessid
|
||||
"""
|
||||
return self.sessions.get(sessid, None)
|
||||
|
||||
def get_all_sync_data(self):
|
||||
"""
|
||||
Create a dictionary of sessdata dicts representing all
|
||||
sessions in store.
|
||||
"""
|
||||
sessdict = {}
|
||||
for sess in self.sessions.values():
|
||||
# copy all relevant data from all sessions
|
||||
sessdict[sess.sessid] = sess.get_sync_data()
|
||||
return sessdict
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Server-SessionHandler class
|
||||
#------------------------------------------------------------
|
||||
|
||||
class ServerSessionHandler(SessionHandler):
|
||||
"""
|
||||
This object holds the stack of sessions active in the game at
|
||||
any time.
|
||||
|
|
@ -38,102 +79,144 @@ class SessionHandler(object):
|
|||
|
||||
"""
|
||||
|
||||
# AMP communication methods
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Init the handler. We track two types of sessions, those
|
||||
who have just connected (unloggedin) and those who have
|
||||
logged in (authenticated).
|
||||
Init the handler.
|
||||
"""
|
||||
self.unloggedin = []
|
||||
self.loggedin = []
|
||||
|
||||
# we keep a link to the server here, for the rest of the game to access.
|
||||
self.sessions = {}
|
||||
self.server = None
|
||||
|
||||
def add_unloggedin_session(self, session):
|
||||
def portal_connect(self, sessid, session):
|
||||
"""
|
||||
Call at first connect. This adds a not-yet authenticated session.
|
||||
"""
|
||||
self.unloggedin.insert(0, session)
|
||||
Called by Portal when a new session has connected.
|
||||
Creates a new, unlogged-in game session.
|
||||
"""
|
||||
self.sessions[sessid] = session
|
||||
session.execute_cmd('look')
|
||||
|
||||
def portal_disconnect(self, sessid):
|
||||
"""
|
||||
Called by Portal when portal reports a closing of a session
|
||||
from the portal side.
|
||||
"""
|
||||
session = self.sessions.get(sessid, None)
|
||||
if session:
|
||||
del self.sessions[session.sessid]
|
||||
self.session_count(-1)
|
||||
|
||||
def portal_session_sync(self, sesslist):
|
||||
"""
|
||||
Syncing all session ids of the portal with the ones of the server. This is instantiated
|
||||
by the portal when reconnecting.
|
||||
|
||||
def add_loggedin_session(self, session):
|
||||
sesslist is a complete list of (sessid, session) pairs, matching the list on the portal.
|
||||
if session was logged in, the amp handler will have logged them in before this point.
|
||||
"""
|
||||
for sess in self.sessions.values():
|
||||
# we delete the old session to make sure to catch eventual lingering references.
|
||||
del sess
|
||||
for sess in sesslist:
|
||||
self.sessions[sess.sessid] = sess
|
||||
sess.at_sync()
|
||||
|
||||
def portal_shutdown(self):
|
||||
"""
|
||||
Called by server when shutting down the portal.
|
||||
"""
|
||||
self.server.amp_protocol.call_remote_PortalAdmin(0,
|
||||
operation='SSHUTD',
|
||||
data="")
|
||||
# server-side access methods
|
||||
|
||||
def disconnect(self, session, reason=""):
|
||||
"""
|
||||
Called from server side to remove session and inform portal
|
||||
of this fact.
|
||||
"""
|
||||
session = self.sessions.get(session.sessid, None)
|
||||
if session:
|
||||
sessid = session.sessid
|
||||
del self.sessions[sessid]
|
||||
# inform portal that session should be closed.
|
||||
self.server.amp_protocol.call_remote_PortalAdmin(sessid,
|
||||
operation='SDISCONN',
|
||||
data=reason)
|
||||
self.session_count(-1)
|
||||
|
||||
|
||||
def login(self, session):
|
||||
"""
|
||||
Log in the previously unloggedin session and the player we by
|
||||
now should know is connected to it. After this point we
|
||||
assume the session to be logged in one way or another.
|
||||
"""
|
||||
# prep the session with player/user info
|
||||
|
||||
|
||||
|
||||
if not ALLOW_MULTISESSION:
|
||||
# disconnect previous sessions.
|
||||
self.disconnect_duplicate_sessions(session)
|
||||
|
||||
# store/move the session to the right list
|
||||
try:
|
||||
self.unloggedin.remove(session)
|
||||
except ValueError:
|
||||
pass
|
||||
self.loggedin.insert(0, session)
|
||||
session.logged_in = True
|
||||
self.session_count(1)
|
||||
# sync the portal to this session
|
||||
sessdata = session.get_sync_data()
|
||||
self.server.amp_protocol.call_remote_PortalAdmin(session.sessid,
|
||||
operation='SLOGIN',
|
||||
data=sessdata)
|
||||
|
||||
def session_sync(self):
|
||||
"""
|
||||
This is called by the server when it reboots. It syncs all session data
|
||||
to the portal.
|
||||
"""
|
||||
sessdata = self.get_all_sync_data()
|
||||
self.server.amp_protocol.call_remote_PortalAdmin(0,
|
||||
'SSYNC',
|
||||
data=sessdata)
|
||||
|
||||
def remove_session(self, session):
|
||||
"""
|
||||
Remove session from the handler
|
||||
"""
|
||||
removed = False
|
||||
try:
|
||||
self.unloggedin.remove(session)
|
||||
except Exception:
|
||||
try:
|
||||
self.loggedin.remove(session)
|
||||
except Exception:
|
||||
return
|
||||
self.session_count(-1)
|
||||
|
||||
def get_sessions(self, include_unloggedin=False):
|
||||
"""
|
||||
Returns the connected session objects.
|
||||
"""
|
||||
if include_unloggedin:
|
||||
return self.loggedin + self.unloggedin
|
||||
else:
|
||||
return self.loggedin
|
||||
|
||||
def disconnect_all_sessions(self, reason="You have been disconnected."):
|
||||
"""
|
||||
Cleanly disconnect all of the connected sessions.
|
||||
"""
|
||||
sessions = self.get_sessions(include_unloggedin=True)
|
||||
for session in sessions:
|
||||
session.at_data_out(reason)
|
||||
session.session_disconnect()
|
||||
|
||||
for session in self.sessions:
|
||||
del session
|
||||
self.session_count(0)
|
||||
# tell portal to disconnect all sessions
|
||||
self.server.amp_protocol.call_remote_PortalAdmin(0,
|
||||
operation='SDISCONNALL',
|
||||
data=reason)
|
||||
|
||||
def disconnect_duplicate_sessions(self, curr_session):
|
||||
"""
|
||||
Disconnects any existing sessions with the same game object.
|
||||
"""
|
||||
reason = "Your account has been logged in from elsewhere. Disconnecting."
|
||||
curr_char = curr_session.get_character()
|
||||
doublet_sessions = [sess for sess in self.get_sessions()
|
||||
if sess.get_character() == curr_char and sess != curr_session]
|
||||
logged_out = 0
|
||||
for session in doublet_sessions:
|
||||
session.msg(reason)
|
||||
self.remove_session(session)
|
||||
logged_out += 1
|
||||
self.session_count(-logged_out)
|
||||
return logged_out
|
||||
doublet_sessions = [sess for sess in self.sessions
|
||||
if sess.logged_in
|
||||
and sess.get_character() == curr_char
|
||||
and sess != curr_session]
|
||||
reason = _("Logged in from elsewhere. Disconnecting.")
|
||||
for sessid in doublet_sessions:
|
||||
self.disconnect(session, reason)
|
||||
self.session_count(-1)
|
||||
|
||||
|
||||
def validate_sessions(self):
|
||||
"""
|
||||
Check all currently connected sessions (logged in and not)
|
||||
and see if any are dead.
|
||||
"""
|
||||
for session in self.get_sessions(include_unloggedin=True):
|
||||
session.session_validate()
|
||||
|
||||
tcurr = time.time()
|
||||
invalid_sessions = [session for session in self.sessions.values()
|
||||
if session.logged_in and IDLE_TIMEOUT > 0
|
||||
and (tcurr - session.cmd_last) > IDLE_TIMEOUT]
|
||||
for session in invalid_sessions:
|
||||
self.disconnect(session, reason=_("Idle timeout exceeded, disconnecting."))
|
||||
self.session_count(-1)
|
||||
|
||||
def session_count(self, num=None):
|
||||
"""
|
||||
Count up/down the number of connected, authenticated users.
|
||||
|
|
@ -160,7 +243,7 @@ class SessionHandler(object):
|
|||
may have more than one session connected if ALLOW_MULTISESSION is True)
|
||||
Only logged-in players are counted here.
|
||||
"""
|
||||
return len(set(sess.uid for sess in self.get_sessions()))
|
||||
return len(set(session.uid for session in self.sessions.values() if session.logged_in))
|
||||
|
||||
def sessions_from_player(self, player):
|
||||
"""
|
||||
|
|
@ -172,7 +255,7 @@ class SessionHandler(object):
|
|||
except User.DoesNotExist:
|
||||
return None
|
||||
uid = uobj.id
|
||||
return [session for session in self.loggedin if session.uid == uid]
|
||||
return [session for session in self.sessions.values() if session.logged_in and session.uid == uid]
|
||||
|
||||
def sessions_from_character(self, character):
|
||||
"""
|
||||
|
|
@ -183,20 +266,129 @@ class SessionHandler(object):
|
|||
return self.sessions_from_player(player)
|
||||
return None
|
||||
|
||||
def session_from_suid(self, suid):
|
||||
"""
|
||||
Given a session id, retrieve the session (this is primarily
|
||||
intended to be called by web clients)
|
||||
"""
|
||||
return [sess for sess in self.get_sessions(include_unloggedin=True) if sess.suid and sess.suid == suid]
|
||||
|
||||
def announce_all(self, message):
|
||||
"""
|
||||
Send message to all connected sessions
|
||||
"""
|
||||
for sess in self.get_sessions(include_unloggedin=True):
|
||||
sess.msg(message)
|
||||
for sess in self.sessions.values():
|
||||
self.data_out(sess, message)
|
||||
|
||||
SESSIONS = SessionHandler()
|
||||
def data_out(self, session, string="", data=""):
|
||||
"""
|
||||
Sending data Server -> Portal
|
||||
"""
|
||||
self.server.amp_protocol.call_remote_MsgServer2Portal(sessid=session.sessid,
|
||||
msg=string,
|
||||
data=data)
|
||||
def data_in(self, sessid, string="", data=""):
|
||||
"""
|
||||
Data Portal -> Server
|
||||
"""
|
||||
session = self.sessions.get(sessid, None)
|
||||
if session:
|
||||
session.execute_cmd(string)
|
||||
|
||||
# ignore 'data' argument for now; this is otherwise the place
|
||||
# to put custom effects on the server due to data input, e.g.
|
||||
# from a custom client.
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Portal-SessionHandler class
|
||||
#------------------------------------------------------------
|
||||
|
||||
class PortalSessionHandler(SessionHandler):
|
||||
"""
|
||||
This object holds the sessions connected to the portal at any time.
|
||||
It is synced with the server's equivalent SessionHandler over the AMP
|
||||
connection.
|
||||
|
||||
Sessions register with the handler using the connect() method. This
|
||||
will assign a new unique sessionid to the session and send that sessid
|
||||
to the server using the AMP connection.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Init the handler
|
||||
"""
|
||||
self.portal = None
|
||||
self.sessions = {}
|
||||
self.latest_sessid = 0
|
||||
|
||||
def connect(self, session):
|
||||
"""
|
||||
Called by protocol at first connect. This adds a not-yet authenticated session
|
||||
using an ever-increasing counter for sessid.
|
||||
"""
|
||||
self.latest_sessid += 1
|
||||
sessid = self.latest_sessid
|
||||
session.sessid = sessid
|
||||
sessdata = session.get_sync_data()
|
||||
self.sessions[sessid] = session
|
||||
# sync with server-side
|
||||
self.portal.amp_protocol.call_remote_ServerAdmin(sessid,
|
||||
operation="PCONN",
|
||||
data=sessdata)
|
||||
def disconnect(self, session):
|
||||
"""
|
||||
Called from portal side when the connection is closed from the portal side.
|
||||
"""
|
||||
sessid = session.sessid
|
||||
self.portal.amp_protocol.call_remote_ServerAdmin(sessid,
|
||||
operation="PDISCONN")
|
||||
|
||||
def server_disconnect(self, sessid, reason=""):
|
||||
"""
|
||||
Called by server to force a disconnect by sessid
|
||||
"""
|
||||
session = self.sessions.get(sessid, None)
|
||||
if session:
|
||||
session.disconnect(reason)
|
||||
del session
|
||||
|
||||
def server_disconnect_all(self, reason=""):
|
||||
"""
|
||||
Called by server when forcing a clean disconnect for everyone.
|
||||
"""
|
||||
for session in self.sessions.values():
|
||||
session.disconnect(reason)
|
||||
del session
|
||||
|
||||
def session_from_suid(self, suid):
|
||||
"""
|
||||
Given a session id, retrieve the session (this is primarily
|
||||
intended to be called by web clients)
|
||||
"""
|
||||
return [sess for sess in self.get_sessions(include_unloggedin=True)
|
||||
if hasattr(sess, 'suid') and sess.suid == suid]
|
||||
|
||||
def data_in(self, session, string="", data=""):
|
||||
"""
|
||||
Called by portal sessions for relaying data coming
|
||||
in from the protocol to the server. data is
|
||||
serialized before passed on.
|
||||
"""
|
||||
self.portal.amp_protocol.call_remote_MsgPortal2Server(session.sessid,
|
||||
msg=string,
|
||||
data=data)
|
||||
def announce_all(self, message):
|
||||
"""
|
||||
Send message to all connection sessions
|
||||
"""
|
||||
for session in self.sessions.values():
|
||||
session.data_out(message)
|
||||
|
||||
def data_out(self, sessid, string="", data=""):
|
||||
"""
|
||||
Called by server for having the portal relay messages and data
|
||||
to the correct session protocol.
|
||||
"""
|
||||
session = self.sessions.get(sessid, None)
|
||||
if session:
|
||||
session.data_out(string, data=data)
|
||||
|
||||
SESSIONS = ServerSessionHandler()
|
||||
PORTAL_SESSIONS = PortalSessionHandler()
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ This depends on a generic session module that implements
|
|||
the actual login procedure of the game, tracks
|
||||
sessions etc.
|
||||
|
||||
Using standard ssh client,
|
||||
|
||||
"""
|
||||
import os
|
||||
|
||||
|
|
@ -25,7 +27,9 @@ from django.conf import settings
|
|||
from src.server import session
|
||||
from src.players.models import PlayerDB
|
||||
from src.utils import ansi, utils, logger
|
||||
#from src.commands.default.unloggedin import _login
|
||||
|
||||
# i18n
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
ENCODINGS = settings.ENCODINGS
|
||||
|
||||
|
|
@ -40,13 +44,13 @@ class SshProtocol(Manhole, session.Session):
|
|||
them. All communication between game and player goes through
|
||||
here.
|
||||
"""
|
||||
def __init__(self, player):
|
||||
def __init__(self, starttuple):
|
||||
"""
|
||||
For setting up the player. If player is not None then we'll
|
||||
login automatically.
|
||||
"""
|
||||
self.player = player
|
||||
|
||||
self.authenticated_player = starttuple[0]
|
||||
self.cfactory = starttuple[1] # obs may not be called self.factory, it gets overwritten!
|
||||
|
||||
def terminalSize(self, width, height):
|
||||
"""
|
||||
|
|
@ -60,10 +64,14 @@ class SshProtocol(Manhole, session.Session):
|
|||
self.height = height
|
||||
|
||||
# initialize the session
|
||||
self.session_connect(self.getClientAddress())
|
||||
if self.player is not None:
|
||||
self.session_login(self.player)
|
||||
self.execute_cmd('look')
|
||||
client_address = self.getClientAddress()
|
||||
self.init_session("ssh", client_address, self.cfactory.sessionhandler)
|
||||
|
||||
# since we might have authenticated already, we might set this here.
|
||||
if self.authenticated_player:
|
||||
self.logged_in = True
|
||||
self.uid = self.authenticated_player.user.id
|
||||
self.sessionhandler.connect(self)
|
||||
|
||||
def connectionMade(self):
|
||||
"""
|
||||
|
|
@ -74,8 +82,9 @@ class SshProtocol(Manhole, session.Session):
|
|||
self.keyHandlers[CTRL_C] = self.handle_INT
|
||||
self.keyHandlers[CTRL_D] = self.handle_EOF
|
||||
self.keyHandlers[CTRL_L] = self.handle_FF
|
||||
self.keyHandlers[CTRL_BACKSLASH] = self.handle_QUIT
|
||||
|
||||
self.keyHandlers[CTRL_BACKSLASH] = self.handle_QUIT
|
||||
|
||||
# initalize
|
||||
|
||||
def handle_INT(self):
|
||||
"""
|
||||
|
|
@ -116,32 +125,22 @@ class SshProtocol(Manhole, session.Session):
|
|||
self.terminal.loseConnection()
|
||||
|
||||
|
||||
def connectionLost(self, reason=None, step=1):
|
||||
def connectionLost(self, reason=None):
|
||||
"""
|
||||
This is executed when the connection is lost for
|
||||
whatever reason.
|
||||
whatever reason. It can also be called directly,
|
||||
from the disconnect method.
|
||||
|
||||
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.
|
||||
"""
|
||||
insults.TerminalProtocol.connectionLost(self, reason)
|
||||
if step == 1:
|
||||
self.session_disconnect()
|
||||
else:
|
||||
self.terminal.loseConnection()
|
||||
|
||||
self.sessionhandler.disconnect(self)
|
||||
self.terminal.loseConnection()
|
||||
|
||||
def getClientAddress(self):
|
||||
"""
|
||||
Returns the client's address and port in a tuple. For example
|
||||
('127.0.0.1', 41917)
|
||||
"""
|
||||
|
||||
return self.terminal.transport.getPeer()
|
||||
|
||||
|
||||
|
|
@ -152,7 +151,7 @@ class SshProtocol(Manhole, session.Session):
|
|||
command for the purpose of the MUD. So we take the user input
|
||||
and pass it on to the game engine.
|
||||
"""
|
||||
self.at_data_in(string)
|
||||
self.sessionhandler.data_in(self, string)
|
||||
|
||||
def lineSend(self, string):
|
||||
"""
|
||||
|
|
@ -166,35 +165,18 @@ class SshProtocol(Manhole, session.Session):
|
|||
self.terminal.write(line) #this is the telnet-specific method for sending
|
||||
self.terminal.nextLine()
|
||||
|
||||
|
||||
# session-general method hooks
|
||||
|
||||
def at_connect(self):
|
||||
"""
|
||||
Show the banner screen.
|
||||
"""
|
||||
self.telnet_markup = True
|
||||
# show connection screen
|
||||
|
||||
def at_login(self, player):
|
||||
"""
|
||||
Called after authentication. self.logged_in=True at this point.
|
||||
"""
|
||||
if player.has_attribute('telnet_markup'):
|
||||
self.telnet_markup = player.get_attribute("telnet_markup")
|
||||
else:
|
||||
self.telnet_markup = True
|
||||
|
||||
def at_disconnect(self, reason="Connection closed. Goodbye for now."):
|
||||
def disconnect(self, reason="Connection closed. Goodbye for now."):
|
||||
"""
|
||||
Disconnect from server
|
||||
"""
|
||||
char = self.get_character()
|
||||
if char:
|
||||
char.at_disconnect()
|
||||
self.at_data_out(reason)
|
||||
self.connectionLost(step=2)
|
||||
if reason:
|
||||
self.data_out(reason)
|
||||
self.connectionLost(reason)
|
||||
|
||||
def at_data_out(self, string, data=None):
|
||||
def data_out(self, string, data=None):
|
||||
"""
|
||||
Data Evennia -> Player access hook. 'data' argument is a dict parsed for string settings.
|
||||
"""
|
||||
|
|
@ -203,31 +185,18 @@ class SshProtocol(Manhole, session.Session):
|
|||
except Exception, e:
|
||||
self.lineSend(str(e))
|
||||
return
|
||||
nomarkup = not self.telnet_markup
|
||||
raw = False
|
||||
nomarkup = False
|
||||
raw = False
|
||||
if type(data) == dict:
|
||||
# check if we want escape codes to go through unparsed.
|
||||
raw = data.get("raw", self.telnet_markup)
|
||||
raw = data.get("raw", False)
|
||||
# check if we want to remove all markup
|
||||
nomarkup = data.get("nomarkup", not self.telnet_markup)
|
||||
nomarkup = data.get("nomarkup", False)
|
||||
if raw:
|
||||
self.lineSend(string)
|
||||
else:
|
||||
self.lineSend(ansi.parse_ansi(string, strip_ansi=nomarkup))
|
||||
|
||||
def at_data_in(self, string, data=None):
|
||||
"""
|
||||
Line from Player -> Evennia. 'data' argument is not used.
|
||||
|
||||
"""
|
||||
try:
|
||||
string = utils.to_unicode(string, encoding=self.encoding)
|
||||
self.execute_cmd(string)
|
||||
return
|
||||
except Exception, e:
|
||||
logger.log_errmsg(str(e))
|
||||
|
||||
|
||||
|
||||
class ExtraInfoAuthServer(SSHUserAuthServer):
|
||||
def auth_password(self, packet):
|
||||
|
|
@ -251,15 +220,19 @@ class PlayerDBPasswordChecker(object):
|
|||
"""
|
||||
credentialInterfaces = (credentials.IUsernamePassword,)
|
||||
|
||||
def __init__(self, factory):
|
||||
self.factory = factory
|
||||
super(PlayerDBPasswordChecker, self).__init__()
|
||||
|
||||
def requestAvatarId(self, c):
|
||||
"Generic credentials"
|
||||
up = credentials.IUsernamePassword(c, None)
|
||||
username = up.username
|
||||
password = up.password
|
||||
player = PlayerDB.objects.get_player_from_name(username)
|
||||
res = None
|
||||
res = (None, self.factory)
|
||||
if player and player.user.check_password(password):
|
||||
res = player
|
||||
res = (player, self.factory)
|
||||
return defer.succeed(res)
|
||||
|
||||
class PassAvatarIdTerminalRealm(TerminalRealm):
|
||||
|
|
@ -322,7 +295,7 @@ def getKeyPair(pubkeyfile, privkeyfile):
|
|||
|
||||
if not (os.path.exists(pubkeyfile) and os.path.exists(privkeyfile)):
|
||||
# No keypair exists. Generate a new RSA keypair
|
||||
print " Generating SSH RSA keypair ...",
|
||||
print _(" Generating SSH RSA keypair ..."),
|
||||
from Crypto.PublicKey import RSA
|
||||
|
||||
KEY_LENGTH = 1024
|
||||
|
|
@ -359,6 +332,7 @@ def makeFactory(configdict):
|
|||
rlm.transportFactory = TerminalSessionTransport_getPeer
|
||||
rlm.chainedProtocolFactory = chainProtocolFactory
|
||||
factory = ConchFactory(Portal(rlm))
|
||||
factory.sessionhandler = configdict['sessions']
|
||||
|
||||
try:
|
||||
# create/get RSA keypair
|
||||
|
|
@ -366,12 +340,12 @@ def makeFactory(configdict):
|
|||
factory.publicKeys = {'ssh-rsa': publicKey}
|
||||
factory.privateKeys = {'ssh-rsa': privateKey}
|
||||
except Exception, e:
|
||||
print " getKeyPair error: %s\n WARNING: Evennia could not auto-generate SSH keypair. Using conch default keys instead." % e
|
||||
print " If this error persists, create game/%s and game/%s yourself using third-party tools." % (pubkeyfile, privkeyfile)
|
||||
print _(" getKeyPair error: %(e)s\n WARNING: Evennia could not auto-generate SSH keypair. Using conch default keys instead.") % {'e': e}
|
||||
print _(" If this error persists, create game/%(pub)s and game/%(priv)s yourself using third-party tools.") % {'pub': pubkeyfile, 'priv': privkeyfile}
|
||||
|
||||
factory.services = factory.services.copy()
|
||||
factory.services['ssh-userauth'] = ExtraInfoAuthServer
|
||||
|
||||
factory.portal.registerChecker(PlayerDBPasswordChecker())
|
||||
factory.portal.registerChecker(PlayerDBPasswordChecker(factory))
|
||||
|
||||
return factory
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ from twisted.internet import ssl as twisted_ssl
|
|||
try:
|
||||
import OpenSSL
|
||||
except ImportError:
|
||||
print " SSL_ENABLED requires PyOpenSSL."
|
||||
sys.exit()
|
||||
print _(" SSL_ENABLED requires PyOpenSSL.")
|
||||
sys.exit(5)
|
||||
|
||||
from src.server.telnet import TelnetProtocol
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ def verify_SSL_key_and_cert(keyfile, certfile):
|
|||
from Crypto.PublicKey import RSA
|
||||
from twisted.conch.ssh.keys import Key
|
||||
|
||||
print " Creating SSL key and certificate ... ",
|
||||
print _(" Creating SSL key and certificate ... "),
|
||||
|
||||
try:
|
||||
# create the RSA key and store it.
|
||||
|
|
@ -42,9 +42,9 @@ def verify_SSL_key_and_cert(keyfile, certfile):
|
|||
keyString = rsaKey.toString(type="OPENSSH")
|
||||
file(keyfile, 'w+b').write(keyString)
|
||||
except Exception,e:
|
||||
print "rsaKey error: %s\n WARNING: Evennia could not auto-generate SSL private key." % e
|
||||
print "If this error persists, create game/%s yourself using third-party tools." % keyfile
|
||||
sys.exit()
|
||||
print _("rsaKey error: %(e)s\n WARNING: Evennia could not auto-generate SSL private key.") % {'e': e}
|
||||
print _("If this error persists, create game/%(keyfile)s yourself using third-party tools.") % {'keyfile': keyfile}
|
||||
sys.exit(5)
|
||||
|
||||
# try to create the certificate
|
||||
CERT_EXPIRE = 365 * 20 # twenty years validity
|
||||
|
|
@ -56,12 +56,12 @@ def verify_SSL_key_and_cert(keyfile, certfile):
|
|||
err = subprocess.call(exestring)#, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
except OSError, e:
|
||||
print " %s\n" % e
|
||||
print " Evennia's SSL context factory could not automatically create an SSL certificate game/%s." % certfile
|
||||
print " A private key 'ssl.key' was already created. Please create %s manually using the commands valid " % certfile
|
||||
print " for your operating system."
|
||||
print " Example (linux, using the openssl program): "
|
||||
print " %s" % exestring
|
||||
sys.exit()
|
||||
print _(" Evennia's SSL context factory could not automatically create an SSL certificate game/%(cert)s.") % {'cert': certfile}
|
||||
print _(" A private key 'ssl.key' was already created. Please create %(cert)s manually using the commands valid") % {'cert': certfile}
|
||||
print _(" for your operating system.")
|
||||
print _(" Example (linux, using the openssl program): ")
|
||||
print " %s" % exestring
|
||||
sys.exit(5)
|
||||
print "done."
|
||||
|
||||
def getSSLContext():
|
||||
|
|
|
|||
|
|
@ -8,130 +8,73 @@ sessions etc.
|
|||
"""
|
||||
|
||||
from twisted.conch.telnet import StatefulTelnetProtocol
|
||||
from django.conf import settings
|
||||
from src.server import session
|
||||
from src.utils import ansi, utils, logger
|
||||
from src.server.session import Session
|
||||
from src.utils import utils, ansi
|
||||
|
||||
ENCODINGS = settings.ENCODINGS
|
||||
|
||||
class TelnetProtocol(StatefulTelnetProtocol, session.Session):
|
||||
class TelnetProtocol(StatefulTelnetProtocol, Session):
|
||||
"""
|
||||
Each player connecting over telnet (ie using most traditional mud
|
||||
clients) gets a telnet protocol instance assigned to them. All
|
||||
communication between game and player goes through here.
|
||||
"""
|
||||
|
||||
# telnet-specific hooks
|
||||
|
||||
def connectionMade(self):
|
||||
"""
|
||||
This is called when the connection is first
|
||||
established.
|
||||
"""
|
||||
# initialize the session
|
||||
self.session_connect(self.getClientAddress())
|
||||
client_address = self.transport.client
|
||||
self.init_session("telnet", client_address, self.factory.sessionhandler)
|
||||
# add us to sessionhandler
|
||||
self.sessionhandler.connect(self)
|
||||
|
||||
def connectionLost(self, reason=None, step=1):
|
||||
def connectionLost(self, reason):
|
||||
"""
|
||||
This is executed when the connection is lost for
|
||||
whatever reason.
|
||||
whatever reason. It can also be called directly, from
|
||||
the disconnect method
|
||||
"""
|
||||
self.sessionhandler.disconnect(self)
|
||||
self.transport.loseConnection()
|
||||
|
||||
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.
|
||||
"""
|
||||
if step == 1:
|
||||
self.session_disconnect()
|
||||
else:
|
||||
self.transport.loseConnection()
|
||||
|
||||
def getClientAddress(self):
|
||||
"""
|
||||
Returns the client's address and port in a tuple. For example
|
||||
('127.0.0.1', 41917)
|
||||
"""
|
||||
return self.transport.client
|
||||
|
||||
def lineReceived(self, string):
|
||||
"""
|
||||
Communication Player -> Evennia. Any line return indicates a
|
||||
command for the purpose of the MUD. So we take the user input
|
||||
and pass it on to the game engine.
|
||||
Telnet method called when data is coming in over the telnet
|
||||
connection. We pass it on to the game engine directly.
|
||||
"""
|
||||
self.at_data_in(string)
|
||||
self.sessionhandler.data_in(self, string)
|
||||
|
||||
def lineSend(self, string):
|
||||
"""
|
||||
Communication Evennia -> Player
|
||||
Any string sent should already have been
|
||||
properly formatted and processed
|
||||
before reaching this point.
|
||||
# Session hooks
|
||||
|
||||
def disconnect(self, reason=None):
|
||||
"""
|
||||
self.sendLine(string) #this is the telnet-specific method for sending
|
||||
generic hook for the engine to call in order to
|
||||
disconnect this protocol.
|
||||
"""
|
||||
if reason:
|
||||
self.data_out(reason)
|
||||
self.connectionLost(reason)
|
||||
|
||||
# session-general method hooks
|
||||
|
||||
def at_connect(self):
|
||||
def data_out(self, string, data=None):
|
||||
"""
|
||||
Show the banner screen.
|
||||
generic hook method for engine to call in order to send data
|
||||
through the telnet connection.
|
||||
Data Evennia -> Player. 'data' argument is not used
|
||||
"""
|
||||
self.telnet_markup = True
|
||||
# show connection screen
|
||||
self.execute_cmd('look')
|
||||
|
||||
def at_login(self, player):
|
||||
"""
|
||||
Called after authentication. self.logged_in=True at this point.
|
||||
"""
|
||||
if player.has_attribute('telnet_markup'):
|
||||
self.telnet_markup = player.get_attribute("telnet_markup")
|
||||
else:
|
||||
self.telnet_markup = True
|
||||
|
||||
def at_disconnect(self, reason="Connection closed. Goodbye for now."):
|
||||
"""
|
||||
Disconnect from server
|
||||
"""
|
||||
char = self.get_character()
|
||||
if char:
|
||||
char.at_disconnect()
|
||||
self.at_data_out(reason)
|
||||
self.connectionLost(step=2)
|
||||
|
||||
def at_data_out(self, string, data=None):
|
||||
"""
|
||||
Data Evennia -> Player access hook. 'data' argument is a dict parsed for string settings.
|
||||
"""
|
||||
try:
|
||||
string = utils.to_str(string, encoding=self.encoding)
|
||||
except Exception, e:
|
||||
self.lineSend(str(e))
|
||||
return
|
||||
nomarkup = not self.telnet_markup
|
||||
raw = False
|
||||
if type(data) == dict:
|
||||
try:
|
||||
string = utils.to_str(string, encoding=self.encoding)
|
||||
except Exception, e:
|
||||
self.sendLine(str(e))
|
||||
return
|
||||
nomarkup = False
|
||||
raw = False
|
||||
if type(data) == dict:
|
||||
# check if we want escape codes to go through unparsed.
|
||||
raw = data.get("raw", self.telnet_markup)
|
||||
raw = data.get("raw", False)
|
||||
# check if we want to remove all markup
|
||||
nomarkup = data.get("nomarkup", not self.telnet_markup)
|
||||
if raw:
|
||||
self.lineSend(string)
|
||||
else:
|
||||
self.lineSend(ansi.parse_ansi(string, strip_ansi=nomarkup))
|
||||
|
||||
def at_data_in(self, string, data=None):
|
||||
"""
|
||||
Line from Player -> Evennia. 'data' argument is not used.
|
||||
|
||||
"""
|
||||
try:
|
||||
string = utils.to_unicode(string, encoding=self.encoding)
|
||||
self.execute_cmd(string)
|
||||
return
|
||||
except Exception, e:
|
||||
logger.log_errmsg(str(e))
|
||||
nomarkup = data.get("nomarkup", False)
|
||||
if raw:
|
||||
self.sendLine(string)
|
||||
else:
|
||||
self.sendLine(ansi.parse_ansi(string, strip_ansi=nomarkup))
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ from django.conf import settings
|
|||
from src.utils import utils, logger, ansi
|
||||
from src.utils.text2html import parse_html
|
||||
from src.server import session
|
||||
from src.server.sessionhandler import SESSIONS
|
||||
|
||||
SERVERNAME = settings.SERVERNAME
|
||||
ENCODINGS = settings.ENCODINGS
|
||||
|
|
@ -55,7 +54,7 @@ def jsonify(obj):
|
|||
|
||||
class WebClient(resource.Resource):
|
||||
"""
|
||||
An ajax/comet long-polling transport protocol for
|
||||
An ajax/comet long-polling transport
|
||||
"""
|
||||
isLeaf = True
|
||||
allowedMethods = ('POST',)
|
||||
|
|
@ -95,24 +94,16 @@ class WebClient(resource.Resource):
|
|||
dataentries.append(jsonify({'msg':string, 'data':data}))
|
||||
self.databuffer[suid] = dataentries
|
||||
|
||||
def disconnect(self, suid, step=1):
|
||||
def client_disconnect(self, suid):
|
||||
"""
|
||||
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()
|
||||
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]
|
||||
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):
|
||||
"""
|
||||
|
|
@ -133,7 +124,9 @@ class WebClient(resource.Resource):
|
|||
|
||||
sess = WebClientSession()
|
||||
sess.client = self
|
||||
sess.session_connect(remote_addr, suid)
|
||||
sess.init_session("comet", remote_addr, self.sessionhandler)
|
||||
sess.suid = suid
|
||||
sess.sessionhandler.connect(sess)
|
||||
return jsonify({'msg':host_string, 'suid':suid})
|
||||
|
||||
def mode_input(self, request):
|
||||
|
|
@ -144,11 +137,12 @@ class WebClient(resource.Resource):
|
|||
suid = request.args.get('suid', ['0'])[0]
|
||||
if suid == '0':
|
||||
return ''
|
||||
sess = SESSIONS.session_from_suid(suid)
|
||||
sess = self.sessionhandler.session_from_suid(suid)
|
||||
if sess:
|
||||
sess = sess[0]
|
||||
string = request.args.get('msg', [''])[0]
|
||||
data = request.args.get('data', [None])[0]
|
||||
sess[0].at_data_in(string, data)
|
||||
sess.sessionhandler.data_in(sess, string, data)
|
||||
return ''
|
||||
|
||||
def mode_receive(self, request):
|
||||
|
|
@ -179,7 +173,7 @@ class WebClient(resource.Resource):
|
|||
"""
|
||||
suid = request.args.get('suid', ['0'])[0]
|
||||
if suid == '0':
|
||||
self.disconnect(suid)
|
||||
self.client_disconnect(suid)
|
||||
return ''
|
||||
|
||||
def render_POST(self, request):
|
||||
|
|
@ -217,35 +211,16 @@ class WebClientSession(session.Session):
|
|||
"""
|
||||
This represents a session running in a webclient.
|
||||
"""
|
||||
|
||||
def at_connect(self):
|
||||
"""
|
||||
Show the banner screen.
|
||||
"""
|
||||
# show screen
|
||||
self.telnet_markup = True
|
||||
self.execute_cmd('look')
|
||||
|
||||
|
||||
def at_login(self, player):
|
||||
"""
|
||||
Called after authentication. self.logged_in=True at this point.
|
||||
"""
|
||||
if player.has_attribute('telnet_markup'):
|
||||
self.telnet_markup = player.get_attribute("telnet_markup")
|
||||
|
||||
def at_disconnect(self, reason=None):
|
||||
def disconnect(self, reason=None):
|
||||
"""
|
||||
Disconnect from server
|
||||
"""
|
||||
if reason:
|
||||
self.lineSend(self.suid, reason)
|
||||
char = self.get_character()
|
||||
if char:
|
||||
char.at_disconnect()
|
||||
self.client.disconnect(self.suid, step=2)
|
||||
self.client.client_disconnect(self.suid)
|
||||
|
||||
def at_data_out(self, string='', data=None):
|
||||
def data_out(self, string='', data=None):
|
||||
"""
|
||||
Data Evennia -> Player access hook.
|
||||
|
||||
|
|
@ -261,13 +236,13 @@ class WebClientSession(session.Session):
|
|||
try:
|
||||
string = utils.to_str(string, encoding=self.encoding)
|
||||
|
||||
nomarkup = not self.telnet_markup
|
||||
nomarkup = False
|
||||
raw = False
|
||||
if type(data) == dict:
|
||||
# check if we want escape codes to go through unparsed.
|
||||
raw = data.get("raw", self.telnet_markup)
|
||||
raw = data.get("raw", False)
|
||||
# check if we want to remove all markup
|
||||
nomarkup = data.get("nomarkup", not self.telnet_markup)
|
||||
nomarkup = data.get("nomarkup", False)
|
||||
if raw:
|
||||
self.client.lineSend(self.suid, string)
|
||||
else:
|
||||
|
|
@ -275,21 +250,3 @@ class WebClientSession(session.Session):
|
|||
return
|
||||
except Exception, e:
|
||||
logger.log_trace()
|
||||
|
||||
def at_data_in(self, string, data=None):
|
||||
"""
|
||||
Input from Player -> Evennia (called by client protocol).
|
||||
Use of 'data' is up to the client - server implementation.
|
||||
"""
|
||||
|
||||
# treat data?
|
||||
if data:
|
||||
pass
|
||||
|
||||
# the string part is identical to telnet
|
||||
try:
|
||||
string = utils.to_unicode(string, encoding=self.encoding)
|
||||
self.execute_cmd(string)
|
||||
return
|
||||
except Exception, e:
|
||||
logger.log_trace()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue