More whitespace cleanup.

This commit is contained in:
Griatch 2012-03-30 23:57:04 +02:00
parent c0322c9eae
commit 45c5be8468
43 changed files with 1116 additions and 1131 deletions

View file

@ -1,6 +1,6 @@
#
# This sets up how models are displayed
# in the web admin interface.
# This sets up how models are displayed
# in the web admin interface.
#
from django.contrib import admin
@ -12,7 +12,7 @@ class ServerConfigAdmin(admin.ModelAdmin):
list_display_links = ('db_key',)
ordering = ['db_key', 'db_value']
search_fields = ['db_key']
save_as = True
save_on_top = True
list_select_related = True
save_as = True
save_on_top = True
list_select_related = True
admin.site.register(ServerConfig, ServerConfigAdmin)

View file

@ -4,11 +4,11 @@ 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
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
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
@ -32,15 +32,15 @@ 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")
# communication bits
# communication bits
PCONN = chr(1) # portal session connect
PDISCONN = chr(2) # portal session disconnect
PSYNC = chr(3) # portal session sync
SLOGIN = chr(4) # server session login
SDISCONN = chr(5) # server session disconnect
SDISCONN = chr(5) # server session disconnect
SDISCONNALL = chr(6) # server session disconnect all
SSHUTD = chr(7) # server shutdown
SSHUTD = chr(7) # server shutdown
SSYNC = chr(8) # server session sync
# i18n
@ -54,7 +54,7 @@ def get_restart_mode(restart_file):
if os.path.exists(restart_file):
flag = open(restart_file, 'r').read()
return flag == "True"
return False
return False
class AmpServerFactory(protocol.ServerFactory):
"""
@ -66,8 +66,8 @@ class AmpServerFactory(protocol.ServerFactory):
server: The Evennia server service instance
protocol: The protocol the factory creates instances of.
"""
self.server = server
self.protocol = AMPProtocol
self.server = server
self.protocol = AMPProtocol
def buildProtocol(self, addr):
"""
@ -91,7 +91,7 @@ class AmpClientFactory(protocol.ReconnectingClientFactory):
maxDelay = 1
def __init__(self, portal):
self.portal = portal
self.portal = portal
self.protocol = AMPProtocol
def startedConnecting(self, connector):
@ -100,10 +100,10 @@ class AmpClientFactory(protocol.ReconnectingClientFactory):
"""
pass
#print 'AMP started to connect:', connector
def buildProtocol(self, addr):
"""
Creates an AMPProtocol instance when connecting to the server.
Creates an AMPProtocol instance when connecting to the server.
"""
#print "Portal connected to Evennia server at %s." % addr
self.resetDelay()
@ -114,7 +114,7 @@ class AmpClientFactory(protocol.ReconnectingClientFactory):
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)
@ -128,7 +128,7 @@ class AmpClientFactory(protocol.ReconnectingClientFactory):
class MsgPortal2Server(amp.Command):
"""
"""
Message portal -> server
"""
arguments = [('sessid', amp.Integer()),
@ -138,7 +138,7 @@ class MsgPortal2Server(amp.Command):
response = []
class MsgServer2Portal(amp.Command):
"""
"""
Message server -> portal
"""
arguments = [('sessid', amp.Integer()),
@ -155,7 +155,7 @@ class OOBPortal2Server(amp.Command):
('data', amp.String())]
errors = [(Exception, "EXCEPTION")]
response = []
class OOBServer2Portal(amp.Command):
"""
OOB data server -> portal
@ -164,13 +164,13 @@ class OOBServer2Portal(amp.Command):
('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
operations on the server, such as when a new
session connects or resyncs
"""
arguments = [('sessid', amp.Integer()),
@ -178,13 +178,13 @@ class ServerAdmin(amp.Command):
('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.
operations on the portal.
"""
arguments = [('sessid', amp.Integer()),
('operation', amp.String()),
@ -209,27 +209,27 @@ class AMPProtocol(amp.AMP):
subclasses that specify the datatypes of the input/output of these methods.
"""
# helper 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.
server side.
"""
if hasattr(self.factory, "portal"):
sessdata = self.factory.portal.sessions.get_all_sync_data()
#print sessdata
self.call_remote_ServerAdmin(0,
PSYNC,
self.call_remote_ServerAdmin(0,
PSYNC,
data=sessdata)
if get_restart_mode(SERVER_RESTART):
msg = _(" ... Server restarted.")
self.factory.portal.sessions.announce_all(msg)
self.factory.portal.sessions.at_server_connection()
# Error handling
# Error handling
def errback(self, e, info):
"error handler, to avoid dropping connections on server tracebacks."
@ -240,8 +240,8 @@ class AMPProtocol(amp.AMP):
# Message definition + helper methods to call/create each message type
# Portal -> Server Msg
def amp_msg_portal2server(self, sessid, msg, data):
def amp_msg_portal2server(self, sessid, msg, data):
"""
Relays message to server. This method is executed on the Server.
"""
@ -253,14 +253,14 @@ class AMPProtocol(amp.AMP):
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=dumps(data)).addErrback(self.errback, "MsgPortal2Server")
# Server -> Portal message
# Server -> Portal message
def amp_msg_server2portal(self, sessid, msg, data):
"""
@ -281,11 +281,11 @@ class AMPProtocol(amp.AMP):
msg=to_str(msg),
data=dumps(data)).addErrback(self.errback, "OOBServer2Portal")
# OOB Portal -> Server
# OOB Portal -> Server
# Portal -> Server Msg
def amp_oob_portal2server(self, sessid, data):
def amp_oob_portal2server(self, sessid, data):
"""
Relays out-of-band data to server. This method is executed on the Server.
"""
@ -297,13 +297,13 @@ class AMPProtocol(amp.AMP):
def call_remote_OOBPortal2Server(self, sessid, data=""):
"""
Access method called by the Portal and executed on the Portal.
"""
"""
#print "oob portal->server (portal side):", sessid, data
self.callRemote(OOBPortal2Server,
sessid=sessid,
sessid=sessid,
data=dumps(data)).addErrback(self.errback, "OOBPortal2Server")
# Server -> Portal message
# Server -> Portal message
def amp_oob_server2portal(self, sessid, data):
"""
@ -318,13 +318,13 @@ class AMPProtocol(amp.AMP):
"""
Access method called by the Server and executed on the Server.
"""
#print "oob server->portal (server side):", sessid, data
#print "oob server->portal (server side):", sessid, data
self.callRemote(OOBServer2Portal,
sessid=sessid,
sessid=sessid,
data=dumps(data)).addErrback(self.errback, "OOBServer2Portal")
# Server administration from the Portal side
# Server administration from the Portal side
def amp_server_admin(self, sessid, operation, data):
"""
This allows the portal to perform admin
@ -334,12 +334,12 @@ class AMPProtocol(amp.AMP):
data = loads(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)
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)
@ -348,24 +348,24 @@ class AMPProtocol(amp.AMP):
self.factory.server.sessions.portal_connect(sessid, sess)
elif operation == PDISCONN: #'portal_session_disconnect'
# session closed from portal side
# 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)
# 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():
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)
sesslist.append(sess)
sesslist.append(sess)
# replace sessions on server
server_sessionhandler.portal_session_sync(sesslist)
server_sessionhandler.portal_session_sync(sesslist)
# after sync is complete we force-validate all scripts (this starts everything)
init_mode = ServerConfig.objects.conf("server_restart_mode", default=None)
ScriptDB.objects.validate(init_mode=init_mode)
@ -373,7 +373,7 @@ class AMPProtocol(amp.AMP):
else:
raise Exception(_("operation %(op)s not recognized.") % {'op': operation})
return {}
ServerAdmin.responder(amp_server_admin)
@ -393,7 +393,7 @@ class AMPProtocol(amp.AMP):
def amp_portal_admin(self, sessid, operation, data):
"""
This allows the server to perform admin
This allows the server to perform admin
operations on the portal. This is executed on the Portal.
"""
data = loads(data)
@ -401,7 +401,7 @@ class AMPProtocol(amp.AMP):
#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 = self.factory.portal.sessions.get_session(sessid)
sess.load_sync_data(data)
elif operation == SDISCONN: #'server_session_disconnect'
@ -415,14 +415,14 @@ class AMPProtocol(amp.AMP):
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).
# 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
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]
@ -449,10 +449,3 @@ class AMPProtocol(amp.AMP):
sessid=sessid,
operation=operation,
data=data).addErrback(self.errback, "PortalAdmin")

View file

@ -11,7 +11,7 @@ from django.core import management
from django.conf import settings
from src.server.models import ServerConfig
from src.help.models import HelpEntry
from src.utils import create
from src.utils import create
# i18n
from django.utils.translation import ugettext as _
@ -19,7 +19,7 @@ from django.utils.translation import ugettext as _
def create_config_values():
"""
Creates the initial config values.
"""
"""
ServerConfig.objects.conf("site_name", settings.SERVERNAME)
ServerConfig.objects.conf("idle_timeout", settings.IDLE_TIMEOUT)
@ -33,28 +33,28 @@ def create_objects():
"""
Creates the #1 player 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.
# This object is pure django and only holds name, email and password.
god_user = get_god_user()
# Create a Player 'user profile' object to hold eventual
# mud-specific settings for the bog standard User object. This is
# accessed by user.get_profile() and can also store attributes.
# It also holds mud permissions, but for a superuser these
# have no effect anyhow.
# have no effect anyhow.
character_typeclass = settings.BASE_CHARACTER_TYPECLASS
# Create the Player object as well as the in-game god-character
# for user #1. We can't set location and home yet since nothing
# exists. Also, all properties (name, email, password, is_superuser)
# is inherited from the user so we don't specify it again here.
# is inherited from the user so we don't specify it again here.
god_character = create.create_player(god_user.username, None, None,
user=god_user,
god_character = create.create_player(god_user.username, None, None,
user=god_user,
create_character=True,
character_typeclass=character_typeclass)
@ -66,7 +66,7 @@ def create_objects():
god_character.locks.add("examine:perm(Immortals);edit:false();delete:false();boot:false();msg:all();puppet:false()")
god_character.save()
# Limbo is the default "nowhere" starting room
room_typeclass = settings.BASE_ROOM_TYPECLASS
@ -83,11 +83,11 @@ def create_objects():
# Now that Limbo exists, try to set the user up in Limbo (unless
# the creation hooks already fixed this).
if not god_character.location:
if not god_character.location:
god_character.location = limbo_obj
if not god_character.home:
god_character.home = limbo_obj
def create_channels():
"""
Creates some sensible default channels.
@ -97,7 +97,7 @@ def create_channels():
# public channel
key, aliases, desc, locks = settings.CHANNEL_PUBLIC
pchan = create.create_channel(key, aliases, desc, locks=locks)
# mudinfo channel
# mudinfo channel
key, aliases, desc, locks = settings.CHANNEL_MUDINFO
ichan = create.create_channel(key, aliases, desc, locks=locks)
# connectinfo channel
@ -110,29 +110,29 @@ def create_channels():
PlayerChannelConnection.objects.create_connection(goduser, pchan)
PlayerChannelConnection.objects.create_connection(goduser, ichan)
PlayerChannelConnection.objects.create_connection(goduser, cchan)
def import_MUX_help_files():
"""
Imports the MUX help files.
"""
"""
print _(" Importing MUX help database (devel reference only) ...")
management.call_command('loaddata', '../src/help/mux_help_db.json', verbosity=0)
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 '%(default)s'." \
% {'default': default_category})
HelpEntry.objects.all_to_category(default_category)
def create_system_scripts():
"""
Setup the system repeat scripts. They are automatically started
by the create_script function.
by the create_script function.
"""
from src.scripts import scripts
print _(" Creating and starting global scripts ...")
# check so that all sessions are alive.
# check so that all sessions are alive.
script1 = create.create_script(scripts.CheckSessions)
# validate all scripts in script table.
script2 = create.create_script(scripts.ValidateScripts)
@ -140,7 +140,7 @@ def create_system_scripts():
script3 = create.create_script(scripts.ValidateChannelHandler)
if not script1 or not script2 or not script3:
print _(" Error creating system scripts.")
def start_game_time():
"""
This starts a persistent script that keeps track of the
@ -155,7 +155,7 @@ def start_game_time():
def create_admin_media_links():
"""
This traverses to src/web/media and tries to create a symbolic
link to the django media files from within the MEDIA_ROOT.
link to the django media files from within the MEDIA_ROOT.
These are files we normally don't
want to mess with (use templates to customize the admin
look). Linking is needed since the Twisted webserver otherwise has no
@ -168,8 +168,8 @@ def create_admin_media_links():
apath = os.path.join(settings.ADMIN_MEDIA_ROOT)
if os.path.isdir(apath):
print _(" ADMIN_MEDIA_ROOT already exists. Ignored.")
return
if os.name == 'nt':
return
if os.name == 'nt':
print _(" Admin-media files copied to ADMIN_MEDIA_ROOT (Windows mode).")
os.mkdir(apath)
os.system('xcopy "%s" "%s" /e /q /c' % (dpath, apath))
@ -188,34 +188,34 @@ def at_initial_setup():
"""
modname = settings.AT_INITIAL_SETUP_HOOK_MODULE
if not modname:
return
try:
return
try:
mod = __import__(modname, fromlist=[None])
except ImportError, ValueError:
return
return
print _(" Running at_initial_setup() hook.")
if mod.__dict__.get("at_initial_setup", None):
mod.at_initial_setup()
mod.at_initial_setup()
def handle_setup(last_step):
"""
Main logic for the module. It allows for restarting
the initialization at any point if one of the modules
should crash.
the initialization at any point if one of the modules
should crash.
"""
if last_step < 0:
# this means we don't need to handle setup since
# it already ran sucessfully once.
# it already ran sucessfully once.
return
elif last_step == None:
# config doesn't exist yet. First start of server
last_step = 0
# setting up the list of functions to run
# setting up the list of functions to run
setup_queue = [
create_config_values,
create_objects,
create_config_values,
create_objects,
create_channels,
create_system_scripts,
start_game_time,
@ -224,17 +224,17 @@ def handle_setup(last_step):
at_initial_setup]
if not settings.IMPORT_MUX_HELP:
# skip importing of the MUX helpfiles, they are
# skip importing of the MUX helpfiles, they are
# not interesting except for developers.
del setup_queue[-2]
#print " Initial setup: %s steps." % (len(setup_queue))
#print " Initial setup: %s steps." % (len(setup_queue))
# step through queue, from last completed function
for num, setup_func in enumerate(setup_queue[last_step:]):
for num, setup_func in enumerate(setup_queue[last_step:]):
# run the setup function. Note that if there is a
# traceback we let it stop the system so the config
# step is not saved.
# step is not saved.
#print "%s..." % num
try:
@ -255,10 +255,10 @@ def handle_setup(last_step):
chan.delete()
for conn in PlayerChannelConnection.objects.all():
conn.delete()
raise
ServerConfig.objects.conf("last_initial_setup_step", last_step + num + 1)
raise
ServerConfig.objects.conf("last_initial_setup_step", last_step + num + 1)
# We got through the entire list. Set last_step to -1 so we don't
# have to run this again.
ServerConfig.objects.conf("last_initial_setup_step", -1)
ServerConfig.objects.conf("last_initial_setup_step", -1)

View file

@ -5,17 +5,17 @@ from django.db import models
class ServerConfigManager(models.Manager):
"""
This ServerConfigManager implements methods for searching
This ServerConfigManager implements methods for searching
and manipulating ServerConfigs directly from the database.
These methods will all return database objects
These methods will all return database objects
(or QuerySets) directly.
ServerConfigs are used to store certain persistent settings for the
ServerConfigs are used to store certain persistent settings for the
server at run-time.
Evennia-specific:
conf
conf
"""
def conf(self, key=None, value=None, delete=False, default=None):
@ -27,13 +27,13 @@ class ServerConfigManager(models.Manager):
elif delete == True:
for conf in self.filter(db_key=key):
conf.delete()
elif value != None:
elif value != None:
conf = self.filter(db_key=key)
if conf:
conf = conf[0]
else:
conf = self.model(db_key=key)
conf.value = value # this will pickle
conf = self.model(db_key=key)
conf.value = value # this will pickle
else:
conf = self.filter(db_key=key)
if not conf:

View file

@ -12,52 +12,52 @@ effect of MCCP unless you have extremely heavy traffic or sits on a
terribly slow connection.
This protocol is implemented by the telnet protocol importing
mccp_compress and calling it from its write methods.
mccp_compress and calling it from its write methods.
"""
import zlib
import zlib
# negotiations for v1 and v2 of the protocol
MCCP = chr(86)
FLUSH = zlib.Z_SYNC_FLUSH
def mccp_compress(protocol, data):
"Handles zlib compression, if applicable"
"Handles zlib compression, if applicable"
if hasattr(protocol, 'zlib'):
return protocol.zlib.compress(data) + protocol.zlib.flush(FLUSH)
return data
return data
class Mccp(object):
"""
Implements the MCCP protocol. Add this to a
Implements the MCCP protocol. Add this to a
variable on the telnet protocol to set it up.
"""
def __init__(self, protocol):
"""
initialize MCCP by storing protocol on
ourselves and calling the client to see if
it supports MCCP. Sets callbacks to
start zlib compression in that case.
initialize MCCP by storing protocol on
ourselves and calling the client to see if
it supports MCCP. Sets callbacks to
start zlib compression in that case.
"""
self.protocol = protocol
self.protocol.protocol_flags['MCCP'] = False
self.protocol.protocol_flags['MCCP'] = False
# ask if client will mccp, connect callbacks to handle answer
self.protocol.will(MCCP).addCallbacks(self.do_mccp, self.no_mccp)
def no_mccp(self, option):
"""
If client doesn't support mccp, don't do anything.
"""
"""
if hasattr(self.protocol, 'zlib'):
del self.protocol.zlib
self.protocol.protocol_flags['MCCP'] = False
self.protocol.protocol_flags['MCCP'] = False
def do_mccp(self, option):
"""
The client supports MCCP. Set things up by
creating a zlib compression stream.
"""
self.protocol.protocol_flags['MCCP'] = True
The client supports MCCP. Set things up by
creating a zlib compression stream.
"""
self.protocol.protocol_flags['MCCP'] = True
self.protocol.requestNegotiation(MCCP, '')
self.protocol.zlib = zlib.compressobj(9)

View file

@ -4,8 +4,8 @@ Server Configuration flags
This holds persistent server configuration flags.
Config values should usually be set through the
manager's conf() method.
Config values should usually be set through the
manager's conf() method.
"""
try:
@ -26,10 +26,10 @@ from django.utils.translation import ugettext as _
# ServerConfig
#
#------------------------------------------------------------
class ServerConfig(SharedMemoryModel):
"""
On-the fly storage of global settings.
On-the fly storage of global settings.
Properties defined on ServerConfig:
key - main identifier
@ -50,15 +50,15 @@ class ServerConfig(SharedMemoryModel):
db_value = models.TextField(blank=True)
objects = ServerConfigManager()
# Wrapper properties to easily set database fields. These are
# @property decorators that allows to access these fields using
# normal python operations (without having to remember to save()
# etc). So e.g. a property 'attr' has a get/set/del decorator
# defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self
# defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self
# is the object in question).
# key property (wraps db_key)
#@property
def key_get(self):
@ -86,12 +86,12 @@ class ServerConfig(SharedMemoryModel):
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))
return
return
self.db_value = pickle.dumps(value)
self.save()
#@value.deleter
def value_del(self):
"Deleter. Allows for del self.value. Deletes entry."
"Deleter. Allows for del self.value. Deletes entry."
self.delete()
value = property(value_get, value_set, value_del)
@ -100,8 +100,8 @@ class ServerConfig(SharedMemoryModel):
verbose_name = "Server Config value"
verbose_name_plural = "Server Config values"
#
# ServerConfig other methods
#
# ServerConfig other methods
#
def __unicode__(self):

View file

@ -28,14 +28,14 @@ regex_varval = re.compile(r"%s(.*?)%s(.*?)[%s]" % (MSDP_VAR, MSDP_VAL, ENDING))
class Msdp(object):
"""
Implements the MSDP protocol.
Implements the MSDP protocol.
"""
def __init__(self, protocol):
"""
Initiates by storing the protocol
on itself and trying to determine
if the client supports MSDP.
if the client supports MSDP.
"""
self.protocol = protocol
self.protocol.protocol_FLAGS['MSDP'] = False
@ -45,39 +45,39 @@ class Msdp(object):
def no_msdp(self, option):
"No msdp"
pass
def do_msdp(self, option):
"""
Called when client confirms that it can do MSDP.
Called when client confirms that it can do MSDP.
"""
self.protocol.protocol_flags['MSDP'] = True
self.protocol.protocol_flags['MSDP'] = True
def func_to_msdp(self, cmdname, data):
"""
handle return data from cmdname by converting it to
a proper msdp structure. data can either be a single value (will be
converted to a string), a list (will be converted to an MSDP_ARRAY),
or a dictionary (will be converted to MSDP_TABLE).
handle return data from cmdname by converting it to
a proper msdp structure. data can either be a single value (will be
converted to a string), a list (will be converted to an MSDP_ARRAY),
or a dictionary (will be converted to MSDP_TABLE).
OBS - this supports nested tables and even arrays nested
inside tables, as opposed to the receive method. Arrays
OBS - this supports nested tables and even arrays nested
inside tables, as opposed to the receive method. Arrays
cannot hold tables by definition (the table must be named
with MSDP_VAR, and an array can only contain MSDP_VALs).
with MSDP_VAR, and an array can only contain MSDP_VALs).
"""
def make_table(name, datadict, string):
def make_table(name, datadict, string):
"build a table that may be nested with other tables or arrays."
string += MSDP_VAR + name + MSDP_VAL + MSDP_TABLE_OPEN
for key, val in datadict.items():
for key, val in datadict.items():
if type(val) == type({}):
string += make_table(key, val, string)
elif hasattr(val, '__iter__'):
string += make_array(key, val, string)
elif hasattr(val, '__iter__'):
string += make_array(key, val, string)
else:
string += MSDP_VAR + key + MSDP_VAL + val
string += MSDP_TABLE_CLOSE
return string
string += MSDP_TABLE_CLOSE
return string
def make_array(name, string, datalist):
"build a simple array. Arrays may not nest tables by definition."
@ -85,23 +85,23 @@ class Msdp(object):
for val in datalist:
string += MSDP_VAL + val
string += MSDP_ARRAY_CLOSE
return string
return string
if type(data) == type({}):
if type(data) == type({}):
msdp_string = make_table(cmdname, data, "")
elif hasattr(data, '__iter__'):
msdp_string = make_array(cmdname, data, "")
else:
msdp_string = MSDP_VAR + cmdname + MSDP_VAL + data
return msdp_string
return msdp_string
def msdp_to_func(self, data):
"""
Handle a client's requested negotiation, converting
it into a function mapping
OBS-this does not support receiving nested tables
from the client at this point!
OBS-this does not support receiving nested tables
from the client at this point!
"""
tables = {}
arrays = {}
@ -112,23 +112,23 @@ class Msdp(object):
for array in regex_array.findall(data):
arrays[array[0]] = dict(regex_varval(array[1]))
variables = dict(regex._varval(regex_array.sub("", regex_table.sub("", data))))
# MSDP Commands
# Some given MSDP (varname, value) pairs can also be treated as command + argument.
# MSDP Commands
# Some given MSDP (varname, value) pairs can also be treated as command + argument.
# Generic msdp command map. The argument will be sent to the given command.
# See http://tintin.sourceforge.net/msdp/ for definitions of each command.
# These are client->server commands.
# See http://tintin.sourceforge.net/msdp/ for definitions of each command.
# These are client->server commands.
def msdp_cmd_list(self, arg):
"""
The List command allows for retrieving various info about the server/client
"""
"""
if arg == 'COMMANDS':
return self.func_to_msdp(arg, MSDP_COMMANDS.keys())
elif arg == 'LISTS':
return self.func_to_msdp(arg, ("COMMANDS", "LISTS",
"CONFIGURABLE_VARIABLES",
return self.func_to_msdp(arg, ("COMMANDS", "LISTS",
"CONFIGURABLE_VARIABLES",
"REPORTED_VARIABLES", "SENDABLE_VARIABLES"))
elif arg == 'CONFIGURABLE_VARIABLES':
return self.func_to_msdp(arg, ("CLIENT_NAME", "CLIENT_VERSION", "PLUGIN_ID"))
@ -149,7 +149,7 @@ class Msdp(object):
try:
MSDP_REPORTABLE[arg](report=True)
except Exception:
logger.log_trace()
logger.log_trace()
def msdp_cmd_unreport(self, arg):
"""
@ -159,7 +159,7 @@ class Msdp(object):
MSDP_REPORTABLE[arg](eport=False)
except Exception:
logger.log_trace()
def msdp_cmd_reset(self, arg):
"""
The reset command resets a variable to its initial state.
@ -167,12 +167,12 @@ class Msdp(object):
try:
MSDP_REPORTABLE[arg](reset=True)
except Exception:
logger.log_trace()
logger.log_trace()
def msdp_cmd_send(self, arg):
"""
Request the server to send a particular variable
to the client.
to the client.
arg - this is a list of variables the client wants.
"""
@ -181,8 +181,8 @@ class Msdp(object):
try:
ret.append(MSDP_REPORTABLE[arg](send=True))
except Exception:
logger.log_trace()
return ret
logger.log_trace()
return ret
MSDP_COMMANDS = {
"LIST": self.msdp_list,
@ -192,57 +192,57 @@ class Msdp(object):
"UNREPORT":"mspd_unreport"
}
# MSDP_MAP is a standard suggestions for making it easy to create generic guis.
# MSDP_MAP is a standard suggestions for making it easy to create generic guis.
# this maps MSDP command names to Evennia commands found in OOB_FUNC_MODULE. It
# is up to these commands to return data on proper form.
# is up to these commands to return data on proper form.
MSDP_REPORTABLE = {
# General
"CHARACTER_NAME": "get_character_name",
"SERVER_ID": "get_server_id",
"SERVER_TIME": "get_server_time",
# Character
# Character
"AFFECTS": "char_affects",
"ALIGNMENT": "char_alignment",
"EXPERIENCE": "char_experience",
"EXPERIENCE_MAX": "char_experience_max",
"EXPERIENCE_TNL": "char_experience_tnl",
"HEALTH": "char_health",
"HEALTH_MAX": "char_health_max",
"LEVEL": "char_level",
"HEALTH": "char_health",
"HEALTH_MAX": "char_health_max",
"LEVEL": "char_level",
"RACE": "char_race",
"CLASS": "char_class",
"MANA": "char_mana",
"CLASS": "char_class",
"MANA": "char_mana",
"MANA_MAX": "char_mana_max",
"WIMPY": "char_wimpy",
"PRACTICE": "char_practice",
"MONEY": "char_money",
"WIMPY": "char_wimpy",
"PRACTICE": "char_practice",
"MONEY": "char_money",
"MOVEMENT": "char_movement",
"MOVEMENT_MAX": "char_movement_max",
"HITROLL": "char_hitroll",
"DAMROLL": "char_damroll",
"HITROLL": "char_hitroll",
"DAMROLL": "char_damroll",
"AC": "char_ac",
"STR": "char_str",
"INT": "char_int",
"STR": "char_str",
"INT": "char_int",
"WIS": "char_wis",
"DEX": "char_dex",
"CON": "char_con",
# Combat
"DEX": "char_dex",
"CON": "char_con",
# Combat
"OPPONENT_HEALTH": "opponent_health",
"OPPONENT_HEALTH_MAX":"opponent_health_max",
"OPPONENT_LEVEL": "opponent_level",
"OPPONENT_NAME": "opponent_name",
# World
# World
"AREA_NAME": "area_name",
"ROOM_EXITS": "area_room_exits",
"ROOM_NAME": "room_name",
"ROOM_VNUM": "room_dbref",
"ROOM_NAME": "room_name",
"ROOM_VNUM": "room_dbref",
"WORLD_TIME": "world_time",
# Configurable variables
"CLIENT_ID": "client_id",
# Configurable variables
"CLIENT_ID": "client_id",
"CLIENT_VERSION": "client_version",
"PLUGIN_ID": "plugin_id",
"ANSI_COLORS": "ansi_colours",
@ -250,13 +250,13 @@ class Msdp(object):
"UTF_8": "utf_8",
"SOUND": "sound",
"MXP": "mxp",
# GUI variables
# GUI variables
"BUTTON_1": "button1",
"BUTTON_2": "button2",
"BUTTON_3": "button3",
"BUTTON_4": "button4",
"BUTTON_5": "button5",
"BUTTON_5": "button5",
"GAUGE_1": "gauge1",
"GAUGE_2": "gauge2",
"GAUGE_3": "gauge3",

View file

@ -4,9 +4,9 @@ MSSP - Mud Server Status Protocol
This implements the MSSP telnet protocol as per
http://tintin.sourceforge.net/mssp/. MSSP allows web portals and
listings to have their crawlers find the mud and automatically
extract relevant information about it, such as genre, how many
active players and so on.
listings to have their crawlers find the mud and automatically
extract relevant information about it, such as genre, how many
active players and so on.
Most of these settings are de
@ -19,23 +19,23 @@ MSSP_VAR = chr(1)
MSSP_VAL = chr(2)
# try to get the customized mssp info, if it exists.
MSSPTable_CUSTOM = utils.variable_from_module(settings.MSSP_META_MODULE, "MSSPTable", default={})
# try to get the customized mssp info, if it exists.
MSSPTable_CUSTOM = utils.variable_from_module(settings.MSSP_META_MODULE, "MSSPTable", default={})
class Mssp(object):
"""
Implements the MSSP protocol. Add this to a
variable on the telnet protocol to set it up.
Implements the MSSP protocol. Add this to a
variable on the telnet protocol to set it up.
"""
def __init__(self, protocol):
"""
initialize MSSP by storing protocol on ourselves
and calling the client to see if it supports
MSSP.
MSSP.
"""
self.protocol = protocol
self.protocol.will(MSSP).addCallbacks(self.do_mssp, self.no_mssp)
def get_player_count(self):
"Get number of logged-in players"
return str(self.protocol.sessionhandler.count_loggedin())
@ -52,31 +52,31 @@ class Mssp(object):
def do_mssp(self, option):
"""
Negotiate all the information.
Negotiate all the information.
"""
self.mssp_table = {
# Required fields
# Required fields
"NAME": "Evennia",
"PLAYERS": self.get_player_count,
"UPTIME" : self.get_uptime,
"PLAYERS": self.get_player_count,
"UPTIME" : self.get_uptime,
# Generic
"CRAWL DELAY": "-1",
"HOSTNAME": "", # current or new hostname
"HOSTNAME": "", # current or new hostname
"PORT": ["4000"], # most important port should be last in list
"CODEBASE": "Evennia",
"CONTACT": "", # email for contacting the mud
"CREATED": "", # year MUD was created
"ICON": "", # url to icon 32x32 or larger; <32kb.
"ICON": "", # url to icon 32x32 or larger; <32kb.
"IP": "", # current or new IP address
"LANGUAGE": "", # name of language used, e.g. English
"LOCATION": "", # full English name of server country
"MINIMUM AGE": "0", # set to 0 if not applicable
"MINIMUM AGE": "0", # set to 0 if not applicable
"WEBSITE": "www.evennia.com",
# Categorisation
@ -88,14 +88,14 @@ class Mssp(object):
# Roleplaying, Simulation, Social or Strategy
"STATUS": "Open Beta", # Alpha, Closed Beta, Open Beta, Live
"GAMESYSTEM": "Custom", # D&D, d20 System, World of Darkness, etc. Use Custom if homebrew
"INTERMUD": "IMC2", # evennia supports IMC2.
"SUBGENRE": "None", # LASG, Medieval Fantasy, World War II, Frankenstein,
"INTERMUD": "IMC2", # evennia supports IMC2.
"SUBGENRE": "None", # LASG, Medieval Fantasy, World War II, Frankenstein,
# Cyberpunk, Dragonlance, etc. Or None if not available.
# World
"AREAS": "0",
"HELPFILES": "0",
"AREAS": "0",
"HELPFILES": "0",
"MOBILES": "0",
"OBJECTS": "0",
"ROOMS": "0", # use 0 if room-less
@ -128,7 +128,7 @@ class Mssp(object):
"HIRING BUILDERS": "0",
"HIRING CODERS": "0",
# Extended variables
# Extended variables
# World

View file

@ -1,6 +1,6 @@
"""
This module implements the main Evennia server process, the core of
the game engine.
the game engine.
This module should be started with the 'twistd' executable since it
sets up all the networking features. (this is done automatically
@ -30,7 +30,7 @@ if os.name == 'nt':
from django.utils.translation import ugettext as _
#------------------------------------------------------------
# Evennia Portal settings
# Evennia Portal settings
#------------------------------------------------------------
VERSION = get_evennia_version()
@ -53,15 +53,15 @@ 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
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
AMP_HOST = settings.AMP_HOST
AMP_PORT = settings.AMP_PORT
AMP_ENABLED = AMP_HOST and AMP_PORT
AMP_ENABLED = AMP_HOST and AMP_PORT
#------------------------------------------------------------
# Portal Service object
# Portal Service object
#------------------------------------------------------------
class Portal(object):
@ -69,17 +69,17 @@ 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.
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
@ -88,25 +88,25 @@ class Portal(object):
print '\n' + '-'*50
# Make info output to the terminal.
# Make info output to the terminal.
self.terminal_output()
print '-'*50
print '-'*50
# set a callback if the server is killed abruptly,
# 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}
print _(' %(servername)s Portal (%(version)s) started.') % {'servername': SERVERNAME, 'version': VERSION}
if AMP_ENABLED:
print " amp (Server): %s" % AMP_PORT
if TELNET_ENABLED:
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)
@ -129,11 +129,11 @@ class Portal(object):
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.
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
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))
@ -141,13 +141,13 @@ class Portal(object):
def shutdown(self, restart=None, _abrupt=False):
"""
Shuts down the server from inside it.
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.
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
@ -157,7 +157,7 @@ class Portal(object):
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
# for Windows we need to remove pid files manually
os.remove(PORTAL_PIDFILE)
#------------------------------------------------------------
@ -170,15 +170,15 @@ class Portal(object):
# what to execute from.
application = service.Application('Portal')
# The main Portal server program. This sets up the database
# 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:
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.
# it would be during testing and debugging.
from src.server import amp
@ -188,7 +188,7 @@ if AMP_ENABLED:
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.
# These will gradually be started as they are initialized below.
if TELNET_ENABLED:
@ -200,7 +200,7 @@ if TELNET_ENABLED:
ifacestr = ""
if interface != '0.0.0.0' or len(TELNET_INTERFACES) > 1:
ifacestr = "-%s" % interface
for port in TELNET_PORTS:
for port in TELNET_PORTS:
pstring = "%s:%s" % (ifacestr, port)
factory = protocol.ServerFactory()
factory.protocol = telnet.TelnetProtocol
@ -219,7 +219,7 @@ if SSL_ENABLED:
ifacestr = ""
if interface != '0.0.0.0' or len(SSL_INTERFACES) > 1:
ifacestr = "-%s" % interface
for port in SSL_PORTS:
for port in SSL_PORTS:
pstring = "%s:%s" % (ifacestr, port)
factory = protocol.ServerFactory()
factory.sessionhandler = PORTAL_SESSIONS
@ -231,7 +231,7 @@ if SSL_ENABLED:
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:
@ -242,7 +242,7 @@ if SSH_ENABLED:
pstring = "%s:%s" % (ifacestr, port)
factory = ssh.makeFactory({'protocolFactory':ssh.SshProtocol,
'protocolArgs':(),
'sessions':PORTAL_SESSIONS})
'sessions':PORTAL_SESSIONS})
ssh_service = internet.TCPServer(port, factory, interface=interface)
ssh_service.setName('EvenniaSSH%s' % pstring)
PORTAL.services.addService(ssh_service)
@ -254,14 +254,14 @@ if WEBSERVER_ENABLED:
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
# 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))
# point our media resources to url /media
web_root.putChild("media", static.File(settings.MEDIA_ROOT))
if WEBCLIENT_ENABLED:
if WEBCLIENT_ENABLED:
# create ajax client processes at /webclientdata
from src.server.webclient import WebClient
webclient = WebClient()

View file

@ -1,6 +1,6 @@
"""
This module implements the main Evennia server process, the core of
the game engine.
the game engine.
This module should be started with the 'twistd' executable since it
sets up all the networking features. (this is done automatically
@ -19,7 +19,7 @@ if os.name == 'nt':
from twisted.application import internet, service
from twisted.internet import protocol, reactor, defer
from twisted.web import server, static
import django
import django
from django.db import connection
from django.conf import settings
@ -38,20 +38,20 @@ if os.name == 'nt':
# a file with a flag telling the server to restart after shutdown or not.
SERVER_RESTART = os.path.join(settings.GAME_DIR, 'server.restart')
# module containing hook methods
# module containing hook methods
SERVER_HOOK_MODULE = mod_import(settings.AT_SERVER_STARTSTOP_MODULE)
# i18n
from django.utils.translation import ugettext as _
#------------------------------------------------------------
# Evennia Server settings
# Evennia Server settings
#------------------------------------------------------------
SERVERNAME = settings.SERVERNAME
VERSION = get_evennia_version()
AMP_ENABLED = True
AMP_ENABLED = True
AMP_HOST = settings.AMP_HOST
AMP_PORT = settings.AMP_PORT
@ -61,7 +61,7 @@ IRC_ENABLED = settings.IRC_ENABLED
RSS_ENABLED = settings.RSS_ENABLED
#------------------------------------------------------------
# Evennia Main Server object
# Evennia Main Server object
#------------------------------------------------------------
class Evennia(object):
@ -69,15 +69,15 @@ class Evennia(object):
The main Evennia server handler. This object sets up the database and
tracks and interlinks all the twisted network services that make up
evennia.
"""
"""
def __init__(self, application):
"""
Setup the server.
Setup the server.
application - an instantiated Twisted application
"""
"""
sys.path.append('.')
# create a store of services
@ -85,44 +85,44 @@ class Evennia(object):
self.amp_protocol = None # set by amp factory
self.sessions = SESSIONS
self.sessions.server = self
print '\n' + '-'*50
# Database-specific startup optimizations.
self.sqlite3_prep()
# Run the initial setup if needed
# Run the initial setup if needed
self.run_initial_setup()
self.start_time = time.time()
# initialize channelhandler
channelhandler.CHANNELHANDLER.update()
# Make info output to the terminal.
# Make info output to the terminal.
self.terminal_output()
print '-'*50
print '-'*50
# set a callback if the server is killed abruptly,
# set a callback if the server is killed abruptly,
# by Ctrl-C, reboot etc.
reactor.addSystemEventTrigger('before', 'shutdown', self.shutdown, _abrupt=True)
self.game_running = True
self.run_init_hooks()
# Server startup methods
def sqlite3_prep(self):
"""
Optimize some SQLite stuff at startup since we
can't save it to the database.
"""
"""
if ((".".join(str(i) for i in django.VERSION) < "1.2" and settings.DATABASE_ENGINE == "sqlite3")
or (hasattr(settings, 'DATABASES')
and settings.DATABASES.get("default", {}).get('ENGINE', None)
== 'django.db.backends.sqlite3')):
or (hasattr(settings, 'DATABASES')
and settings.DATABASES.get("default", {}).get('ENGINE', None)
== 'django.db.backends.sqlite3')):
cursor = connection.cursor()
cursor.execute("PRAGMA cache_size=10000")
cursor.execute("PRAGMA synchronous=OFF")
@ -147,7 +147,7 @@ class Evennia(object):
# 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 %(last)s.' % \
{'last': last_initial_setup_step})
{'last': last_initial_setup_step})
initial_setup.handle_setup(int(last_initial_setup_step))
print '-'*50
@ -164,25 +164,25 @@ class Evennia(object):
# call server hook.
if SERVER_HOOK_MODULE:
SERVER_HOOK_MODULE.at_server_start()
SERVER_HOOK_MODULE.at_server_start()
def terminal_output(self):
"""
Outputs server startup info to the terminal.
"""
print _(' %(servername)s Server (%(version)s) started.') % {'servername': SERVERNAME, 'version': VERSION}
print _(' %(servername)s Server (%(version)s) started.') % {'servername': SERVERNAME, 'version': VERSION}
print ' amp (Portal): %s' % AMP_PORT
def set_restart_mode(self, mode=None):
"""
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.
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.
Either way, the active restart setting (Restart=True/False) is
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'
@ -197,17 +197,17 @@ class Evennia(object):
def shutdown(self, mode=None, _abrupt=False):
"""
Shuts down the server from inside it.
Shuts down the server from inside it.
mode - sets the server restart mode.
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.
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.
in which case the reactor is dead anyway.
"""
mode = self.set_restart_mode(mode)
mode = self.set_restart_mode(mode)
# call shutdown hooks on all cached objects
@ -217,7 +217,7 @@ class Evennia(object):
if mode == 'reload':
# call restart hooks
[(o.typeclass, o.at_server_reload()) for o in ObjectDB.get_all_cached_instances()]
[(o.typeclass, o.at_server_reload()) for o in ObjectDB.get_all_cached_instances()]
[(p.typeclass, p.at_server_reload()) for p in PlayerDB.get_all_cached_instances()]
[(s.typeclass, s.pause(), s.at_server_reload()) for s in ScriptDB.get_all_cached_instances()]
@ -226,23 +226,23 @@ class Evennia(object):
else:
if mode == 'reset':
# don't call disconnect hooks on reset
[(o.typeclass, o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()]
[(o.typeclass, o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()]
else: # shutdown
[(o.typeclass, o.at_disconnect(), o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()]
[(o.typeclass, o.at_disconnect(), o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()]
[(p.typeclass, p.at_server_shutdown()) for p in PlayerDB.get_all_cached_instances()]
[(s.typeclass, s.at_server_shutdown()) for s in ScriptDB.get_all_cached_instances()]
[(s.typeclass, s.at_server_shutdown()) for s in ScriptDB.get_all_cached_instances()]
ServerConfig.objects.conf("server_restart_mode", "reset")
if not _abrupt:
if SERVER_HOOK_MODULE:
SERVER_HOOK_MODULE.at_server_stop()
reactor.callLater(0, reactor.stop)
if os.name == 'nt' and os.path.exists(SERVER_PIDFILE):
# for Windows we need to remove pid files manually
# for Windows we need to remove pid files manually
os.remove(SERVER_PIDFILE)
#------------------------------------------------------------
#
# Start the Evennia game server and add all active services
@ -250,21 +250,21 @@ class Evennia(object):
#------------------------------------------------------------
# Tell the system the server is starting up; some things are not available yet
ServerConfig.objects.conf("server_starting_mode", True)
ServerConfig.objects.conf("server_starting_mode", True)
# twistd requires us to define the variable 'application' so it knows
# what to execute from.
application = service.Application('Evennia')
# The main evennia server program. This sets up the database
# The main evennia server program. This sets up the database
# and is where we store all the other services.
EVENNIA = Evennia(application)
# 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.
# it would be during testing and debugging.
if AMP_ENABLED:
if AMP_ENABLED:
from src.server import amp
@ -278,7 +278,7 @@ if IRC_ENABLED:
# IRC channel connections
from src.comms import irc
from src.comms import irc
irc.connect_all()
if IMC2_ENABLED:
@ -289,10 +289,10 @@ if IMC2_ENABLED:
imc2.connect_all()
if RSS_ENABLED:
# RSS feed channel connections
# RSS feed channel connections
from src.comms import rss
rss.connect_all()
rss.connect_all()
# clear server startup mode
ServerConfig.objects.conf("server_starting_mode", delete=True)

View file

@ -1,13 +1,13 @@
"""
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
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
import time
from datetime import datetime
from django.conf import settings
from src.scripts.models import ScriptDB
@ -16,7 +16,7 @@ from src.utils import logger, utils
from src.commands import cmdhandler, cmdsethandler
from src.server.session import Session
IDLE_COMMAND = settings.IDLE_COMMAND
IDLE_COMMAND = settings.IDLE_COMMAND
# load optional out-of-band function module
OOB_FUNC_MODULE = settings.OOB_FUNC_MODULE
@ -33,14 +33,14 @@ from django.utils.translation import ugettext as _
class ServerSession(Session):
"""
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
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.
@ -48,13 +48,13 @@ class ServerSession(Session):
been assigned (if applicable).
Since this is often called after a server restart we need to set up
the session as it was.
the session as it was.
"""
if not self.logged_in:
# assign the unloggedin-command set.
self.cmdset = cmdsethandler.CmdSetHandler(self)
self.cmdset_storage = [settings.CMDSET_UNLOGGEDIN]
self.cmdset.update(init_mode=True)
self.cmdset.update(init_mode=True)
self.cmdset.update(init_mode=True)
return
@ -80,29 +80,29 @@ class ServerSession(Session):
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()
self.user.last_login = datetime.now()
self.user.save()
# player init
#print "at_init() - player"
player.at_init()
# Check if this is the first time the *player* logs in
# 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()
player.at_pre_login()
character = player.character
if character:
if character:
# this player has a character. Check if it's the
# first time *this character* logs in
character.at_init()
if character.db.FIRST_LOGIN:
character.at_first_login()
del character.db.FIRST_LOGIN
del character.db.FIRST_LOGIN
# run character login hook
character.at_pre_login()
@ -110,12 +110,12 @@ class ServerSession(Session):
# 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()
# post-login hooks
player.at_post_login()
if character:
character.at_post_login()
@ -125,15 +125,15 @@ class ServerSession(Session):
accounting. This method is used also for non-loggedin
accounts.
"""
if self.logged_in:
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
uaccount.save()
self.logged_in = False
self.sessionhandler.disconnect(self)
def get_player(self):
@ -143,8 +143,8 @@ class ServerSession(Session):
if self.logged_in:
return self.player
else:
return None
return None
def get_character(self):
"""
Returns the in-game character associated with this session.
@ -153,12 +153,12 @@ class ServerSession(Session):
player = self.get_player()
if player:
return player.character
return None
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
@ -171,7 +171,7 @@ class ServerSession(Session):
def update_session_counters(self, idle=False):
"""
Hit this when the user enters a command in order to update idle timers
and command counters.
and command counters.
"""
# Store the timestamp of the user's last command.
self.cmd_last = time.time()
@ -187,24 +187,24 @@ class ServerSession(Session):
"""
# handle the 'idle' command
if str(command_string).strip() == IDLE_COMMAND:
self.update_session_counters(idle=True)
return
self.update_session_counters(idle=True)
return
# all other inputs, including empty inputs
character = self.get_character()
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 the session directly
self.get_player().execute_cmd(command_string)
else:
# we are not logged in. Use the session directly
# (it uses the settings.UNLOGGEDIN cmdset)
cmdhandler.cmdhandler(self, command_string)
self.update_session_counters()
self.update_session_counters()
def data_out(self, msg, data=None):
"""
@ -218,25 +218,25 @@ class ServerSession(Session):
This receives out-of-band data from the Portal.
This method parses the data input (a dict) and uses
it to launch correct methods from those plugged into
the system.
it to launch correct methods from those plugged into
the system.
data = {funcname: ( [args], {kwargs]),
funcname: ( [args], {kwargs}), ...}
example:
example:
data = {"get_hp": ([], {}),
"update_counter", (["counter1"], {"now":True}) }
"""
print "server: "
outdata = {}
entity = self.get_character()
if not entity:
entity = self.get_player()
if not entity:
entity = self
entity = self
for funcname, argtuple in data.items():
# loop through the data, calling available functions.
@ -262,19 +262,19 @@ class ServerSession(Session):
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.
"""
symbol = ""
if self.logged_in and hasattr(self, "player") and self.player:
symbol = "(#%s)" % self.player.id
if self.logged_in and hasattr(self, "player") and self.player:
symbol = "(#%s)" % self.player.id
try:
address = ":".join([str(part) for part in self.address])
address = ":".join([str(part) for part in self.address])
except Exception:
address = self.address
address = self.address
return "%s%s@%s" % (self.uname, symbol, address)
def __unicode__(self):
@ -298,7 +298,7 @@ class ServerSession(Session):
# Dummy API hooks for use a non-loggedin operation
def at_cmdset_get(self):
"dummy hook all objects with cmdsets need to have"
pass
@ -306,11 +306,11 @@ class ServerSession(Session):
# Mock db/ndb properties for allowing easy storage on the session
# (note that no databse is involved at all here. session.db.attr =
# value just saves a normal property in memory, just like ndb).
#@property
def ndb_get(self):
"""
A non-persistent store (ndb: NonDataBase). Everything stored
A non-persistent store (ndb: NonDataBase). Everything stored
to this is guaranteed to be cleared when a server is shutdown.
Syntax is same as for the _get_db_holder() method and
property, e.g. obj.ndb.attr = value etc.
@ -321,14 +321,14 @@ class ServerSession(Session):
class NdbHolder(object):
"Holder for storing non-persistent attributes."
def all(self):
return [val for val in self.__dict__.keys()
if not val.startswith['_']]
return [val for val in self.__dict__.keys()
if not val.startswith['_']]
def __getattribute__(self, key):
# return None if no matching attribute was found.
# return None if no matching attribute was found.
try:
return object.__getattribute__(self, key)
except AttributeError:
return None
return None
self._ndb_holder = NdbHolder()
return self._ndb_holder
#@ndb.setter
@ -348,4 +348,4 @@ class ServerSession(Session):
# at this stage, so we just present a uniform API)
def access(self, *args, **kwargs):
"Dummy method."
return True
return True

View file

@ -1,10 +1,10 @@
"""
This defines a generic session class. All connection instances (both
on Portal and Server side) should inherit from this class.
This defines a generic session class. All connection instances (both
on Portal and Server side) should inherit from this class.
"""
import time
import time
#------------------------------------------------------------
# Server Session
@ -21,27 +21,27 @@ class Session(object):
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.
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.
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.
server when the server reboots.
"""
# 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',
'server_data']
_attrs_to_sync = ['protocol_key', 'address', 'suid', 'sessid', 'uid', 'uname',
'logged_in', 'cid', 'encoding',
'conn_time', 'cmd_last', 'cmd_last_visible', 'cmd_total',
'server_data']
def init_session(self, protocol_key, address, sessionhandler):
"""
Initialize the Session. This should be called by the protocol when
a new session is established.
a new session is established.
protocol_key - telnet, ssh, ssl or web
address - client address
sessionhandler - reference to the sessionhandler instance
@ -50,24 +50,24 @@ class Session(object):
self.protocol_key = protocol_key
# Protocol address tied to this session
self.address = address
# suid is used by some protocols, it's a hex key.
self.suid = None
# unique id for this session
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
# user name, for easier tracking of sessions
self.uname = None
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.cid = None
self.encoding = "utf-8"
# session time statistics
# session time statistics
self.conn_time = time.time()
self.cmd_last_visible = self.conn_time
self.cmd_last = self.conn_time
@ -76,10 +76,10 @@ class Session(object):
self.protocol_flags = {}
self.server_data = {}
# a back-reference to the relevant sessionhandler this
# session is stored in.
# 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
@ -93,15 +93,15 @@ class Session(object):
"""
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
self.__dict__[attrname] = value
def at_sync(self):
"""
Called after a session has been fully synced (including
secondary operations such as setting self.player based
on uid etc).
secondary operations such as setting self.player based
on uid etc).
"""
pass
@ -112,13 +112,13 @@ class Session(object):
generic hook called from the outside to disconnect this session
should be connected to the protocols actual disconnect mechanism.
"""
pass
pass
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.
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
@ -126,8 +126,8 @@ class Session(object):
"""
hook for protocols to send incoming data to the engine.
"""
pass
pass
def oob_data_out(self, data):
"""
for Portal, this receives out-of-band data from Server across the AMP.
@ -141,7 +141,7 @@ class Session(object):
"""
for Portal, this sends out-of-band requests to Server over the AMP.
for Server, this receives data from Portal.
data is a dictionary
"""
pass

View file

@ -1,33 +1,33 @@
"""
This module defines handlers for storing sessions when handles
sessions of users connecting to the server.
This module defines handlers for storing sessions when handles
sessions of users connecting to the server.
There are two similar but separate stores of sessions:
ServerSessionHandler - this stores generic game sessions
ServerSessionHandler - this stores generic game sessions
for the game. These sessions has no knowledge about
how they are connected to the world.
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
from src.utils import utils
from src.commands.cmdhandler import CMD_LOGINSTART
# AMP signals
# AMP signals
PCONN = chr(1) # portal session connect
PDISCONN = chr(2) # portal session disconnect
PSYNC = chr(3) # portal session sync
SLOGIN = chr(4) # server session login
SDISCONN = chr(5) # server session disconnect
SDISCONN = chr(5) # server session disconnect
SDISCONNALL = chr(6) # server session disconnect all
SSHUTD = chr(7) # server shutdown
SSHUTD = chr(7) # server shutdown
SSYNC = chr(8) # server session sync
# i18n
@ -43,7 +43,7 @@ class SessionHandler(object):
"""
def __init__(self):
"""
Init the handler.
Init the handler.
"""
self.sessions = {}
@ -64,13 +64,13 @@ class SessionHandler(object):
def get_all_sync_data(self):
"""
Create a dictionary of sessdata dicts representing all
sessions in store.
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()
sessdict[sess.sessid] = sess.get_sync_data()
return sessdict
#------------------------------------------------------------
@ -80,13 +80,13 @@ class SessionHandler(object):
class ServerSessionHandler(SessionHandler):
"""
This object holds the stack of sessions active in the game at
any time.
any time.
A session register with the handler in two steps, first by
registering itself with the connect() method. This indicates an
non-authenticated session. Whenever the session is authenticated
the session together with the related player is sent to the login()
method.
method.
"""
@ -94,15 +94,15 @@ class ServerSessionHandler(SessionHandler):
def __init__(self):
"""
Init the handler.
Init the handler.
"""
self.sessions = {}
self.server = None
self.server = None
self.server_data = {"servername":settings.SERVERNAME}
def portal_connect(self, sessid, session):
"""
Called by Portal when a new session has connected.
Called by Portal when a new session has connected.
Creates a new, unlogged-in game session.
"""
self.sessions[sessid] = session
@ -122,10 +122,10 @@ class ServerSessionHandler(SessionHandler):
"""
Syncing all session ids of the portal with the ones of the server. This is instantiated
by the portal when reconnecting.
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
@ -136,15 +136,15 @@ class ServerSessionHandler(SessionHandler):
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
data="")
# server-side access methods
def disconnect(self, session, reason=""):
"""
Called from server side to remove session and inform portal
Called from server side to remove session and inform portal
of this fact.
"""
session = self.sessions.get(session.sessid, None)
@ -157,7 +157,7 @@ class ServerSessionHandler(SessionHandler):
data=reason)
self.session_count(-1)
def login(self, session):
"""
Log in the previously unloggedin session and the player we by
@ -165,22 +165,22 @@ class ServerSessionHandler(SessionHandler):
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)
session.logged_in = True
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.
to the portal.
"""
sessdata = self.get_all_sync_data()
self.server.amp_protocol.call_remote_PortalAdmin(0,
@ -192,7 +192,7 @@ class ServerSessionHandler(SessionHandler):
"""
Cleanly disconnect all of the connected sessions.
"""
for session in self.sessions:
del session
self.session_count(0)
@ -203,42 +203,42 @@ class ServerSessionHandler(SessionHandler):
def disconnect_duplicate_sessions(self, curr_session, reason = _("Logged in from elsewhere. Disconnecting.") ):
"""
Disconnects any existing sessions with the same game object.
Disconnects any existing sessions with the same game object.
"""
curr_char = curr_session.get_character()
doublet_sessions = [sess for sess in self.sessions
if sess.logged_in
if sess.logged_in
and sess.get_character() == curr_char
and sess != curr_session]
for sessid in doublet_sessions:
self.disconnect(session, reason)
self.disconnect(session, reason)
self.session_count(-1)
def validate_sessions(self):
"""
Check all currently connected sessions (logged in and not)
Check all currently connected sessions (logged in and not)
and see if any are dead.
"""
tcurr = time.time()
reason= _("Idle timeout exceeded, disconnecting.")
for session in (session for session in self.sessions.values()
if session.logged_in and IDLE_TIMEOUT > 0
for session in (session for session in self.sessions.values()
if session.logged_in and IDLE_TIMEOUT > 0
and (tcurr - session.cmd_last) > IDLE_TIMEOUT):
self.disconnect(session, reason=reason)
self.session_count(-1)
def session_count(self, num=None):
"""
Count up/down the number of connected, authenticated users.
Count up/down the number of connected, authenticated users.
If num is None, the current number of sessions is returned.
num can be a positive or negative value to be added to the current count.
If 0, the counter will be reset to 0.
num can be a positive or negative value to be added to the current count.
If 0, the counter will be reset to 0.
"""
if num == None:
# show the current value. This also syncs it.
return int(ServerConfig.objects.conf('nr_sessions', default=0))
# show the current value. This also syncs it.
return int(ServerConfig.objects.conf('nr_sessions', default=0))
elif num == 0:
# reset value to 0
ServerConfig.objects.conf('nr_sessions', 0)
@ -255,7 +255,7 @@ class ServerSessionHandler(SessionHandler):
Only logged-in players are counted here.
"""
return len(set(session.uid for session in self.sessions.values() if session.logged_in))
def sessions_from_player(self, player):
"""
Given a player, return any matching sessions.
@ -275,7 +275,7 @@ class ServerSessionHandler(SessionHandler):
player = character.player
if player:
return self.sessions_from_player(player)
return None
return None
def announce_all(self, message):
@ -295,15 +295,15 @@ class ServerSessionHandler(SessionHandler):
def data_in(self, sessid, string="", data=""):
"""
Data Portal -> Server
"""
session = self.sessions.get(sessid, None)
if session:
"""
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.
# from a custom client.
def oob_data_in(self, sessid, data):
"""
OOB (Out-of-band) Data Portal -> Server
@ -327,11 +327,11 @@ 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.
connection.
Sessions register with the handler using the connect() method. This
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.
to the server using the AMP connection.
"""
@ -339,7 +339,7 @@ class PortalSessionHandler(SessionHandler):
"""
Init the handler
"""
self.portal = None
self.portal = None
self.sessions = {}
self.latest_sessid = 0
self.uptime = time.time()
@ -351,19 +351,19 @@ class PortalSessionHandler(SessionHandler):
Server. At this point, the AMP connection is already
established.
"""
self.connection_time = time.time()
self.connection_time = time.time()
def connect(self, session):
"""
Called by protocol at first connect. This adds a not-yet authenticated session
using an ever-increasing counter for sessid.
"""
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
# sync with server-side
self.portal.amp_protocol.call_remote_ServerAdmin(sessid,
operation=PCONN,
data=sessdata)
@ -374,23 +374,23 @@ class PortalSessionHandler(SessionHandler):
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
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():
for session in self.sessions.values():
session.disconnect(reason)
del session
del session
def count_loggedin(self, include_unloggedin=False):
@ -398,22 +398,22 @@ class PortalSessionHandler(SessionHandler):
Count loggedin connections, alternatively count all connections.
"""
return len(self.get_sessions(include_unloggedin=include_unloggedin))
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)
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.
"""
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)
@ -426,12 +426,12 @@ class PortalSessionHandler(SessionHandler):
def data_out(self, sessid, string="", data=""):
"""
Called by server for having the portal relay messages and data
to the correct session protocol.
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)
session.data_out(string, data=data)
def oob_data_in(self, session, data):
"""

View file

@ -6,7 +6,7 @@ This depends on a generic session module that implements
the actual login procedure of the game, tracks
sessions etc.
Using standard ssh client,
Using standard ssh client,
"""
import os
@ -65,11 +65,11 @@ class SshProtocol(Manhole, session.Session):
# initialize the session
client_address = self.getClientAddress()
self.init_session("ssh", client_address, self.cfactory.sessionhandler)
self.init_session("ssh", client_address, self.cfactory.sessionhandler)
# since we might have authenticated already, we might set this here.
# since we might have authenticated already, we might set this here.
if self.authenticated_player:
self.logged_in = True
self.logged_in = True
self.uid = self.authenticated_player.user.id
self.sessionhandler.connect(self)
@ -82,8 +82,8 @@ 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):
@ -128,8 +128,8 @@ class SshProtocol(Manhole, session.Session):
def connectionLost(self, reason=None):
"""
This is executed when the connection is lost for
whatever reason. It can also be called directly,
from the disconnect method.
whatever reason. It can also be called directly,
from the disconnect method.
"""
insults.TerminalProtocol.connectionLost(self, reason)
@ -186,7 +186,7 @@ class SshProtocol(Manhole, session.Session):
self.lineSend(str(e))
return
nomarkup = False
raw = False
raw = False
if type(data) == dict:
# check if we want escape codes to go through unparsed.
raw = data.get("raw", False)
@ -230,7 +230,7 @@ class PlayerDBPasswordChecker(object):
username = up.username
password = up.password
player = PlayerDB.objects.get_player_from_name(username)
res = (None, self.factory)
res = (None, self.factory)
if player and player.user.check_password(password):
res = (player, self.factory)
return defer.succeed(res)
@ -290,13 +290,13 @@ class TerminalSessionTransport_getPeer:
def getKeyPair(pubkeyfile, privkeyfile):
"""
This function looks for RSA keypair files in the current directory. If they
do not exist, the keypair is created.
do not exist, the keypair is created.
"""
if not (os.path.exists(pubkeyfile) and os.path.exists(privkeyfile)):
# No keypair exists. Generate a new RSA keypair
print _(" Generating SSH RSA keypair ..."),
from Crypto.PublicKey import RSA
from Crypto.PublicKey import RSA
KEY_LENGTH = 1024
rsaKey = Key(RSA.generate(KEY_LENGTH))

View file

@ -1,6 +1,6 @@
"""
This is a simple context factory for auto-creating
SSL keys and certificates.
This is a simple context factory for auto-creating
SSL keys and certificates.
"""
import os, sys
@ -16,21 +16,21 @@ from src.server.telnet import TelnetProtocol
class SSLProtocol(TelnetProtocol):
"""
Communication is the same as telnet, except data transfer
is done with encryption.
is done with encryption.
"""
pass
def verify_SSL_key_and_cert(keyfile, certfile):
"""
This function looks for RSA key and certificate in the current
directory. If files ssl.key and ssl.cert does not exist, they
directory. If files ssl.key and ssl.cert does not exist, they
are created.
"""
if not (os.path.exists(keyfile) and os.path.exists(certfile)):
# key/cert does not exist. Create.
# key/cert does not exist. Create.
import subprocess
from Crypto.PublicKey import RSA
from Crypto.PublicKey import RSA
from twisted.conch.ssh.keys import Key
print _(" Creating SSL key and certificate ... "),
@ -39,16 +39,16 @@ def verify_SSL_key_and_cert(keyfile, certfile):
# create the RSA key and store it.
KEY_LENGTH = 1024
rsaKey = Key(RSA.generate(KEY_LENGTH))
keyString = rsaKey.toString(type="OPENSSH")
keyString = rsaKey.toString(type="OPENSSH")
file(keyfile, 'w+b').write(keyString)
except Exception,e:
except Exception,e:
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
# default:
CERT_EXPIRE = 365 * 20 # twenty years validity
# default:
#openssl req -new -x509 -key ssl.key -out ssl.cert -days 7300
exestring = "openssl req -new -x509 -key %s -out %s -days %s" % (keyfile, certfile, CERT_EXPIRE)
#print "exestring:", exestring
@ -58,9 +58,9 @@ def verify_SSL_key_and_cert(keyfile, certfile):
print " %s\n" % e
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 _(" for your operating system.")
print _(" Example (linux, using the openssl program): ")
print " %s" % exestring
print " %s" % exestring
sys.exit(5)
print "done."

View file

@ -2,8 +2,8 @@
This module implements the telnet protocol.
This depends on a generic session module that implements
the actual login procedure of the game, tracks
sessions etc.
the actual login procedure of the game, tracks
sessions etc.
"""
@ -11,44 +11,44 @@ from twisted.conch.telnet import Telnet, StatefulTelnetProtocol, IAC, LINEMODE,
from src.server.session import Session
from src.server import ttype, mssp
from src.server.mccp import Mccp, mccp_compress, MCCP
from src.utils import utils, ansi
from src.utils import utils, ansi
class TelnetProtocol(Telnet, 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.
"""
"""
def connectionMade(self):
"""
This is called when the connection is first
established.
"""
This is called when the connection is first
established.
"""
# initialize the session
client_address = self.transport.client
client_address = self.transport.client
self.init_session("telnet", client_address, self.factory.sessionhandler)
# negotiate mccp (data compression)
self.mccp = Mccp(self)
self.mccp = Mccp(self)
# negotiate ttype (client info)
self.ttype = ttype.Ttype(self)
# negotiate mssp (crawler communication)
self.mssp = mssp.Mssp(self)
# add this new connection to sessionhandler so
# the Server becomes aware of it.
self.sessionhandler.connect(self)
def enableRemote(self, option):
# add this new connection to sessionhandler so
# the Server becomes aware of it.
self.sessionhandler.connect(self)
def enableRemote(self, option):
"""
This sets up the options we allow for this protocol.
"""
return (option == LINEMODE or
option == ttype.TTYPE or
option == MCCP or
option == MCCP or
option == mssp.MSSP)
def enableLocal(self, option):
@ -60,18 +60,18 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
def disableLocal(self, option):
if option == MCCP:
self.mccp.no_mccp(option)
return True
return True
else:
return super(TelnetProtocol, self).disableLocal(option)
def connectionLost(self, reason):
"""
This is executed when the connection is lost for
This is executed when the connection is lost for
whatever reason. It can also be called directly, from
the disconnect method
"""
self.sessionhandler.disconnect(self)
"""
self.sessionhandler.disconnect(self)
self.transport.loseConnection()
def dataReceived(self, data):
@ -83,7 +83,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
# print "dataRcv:", data,
# try:
# for b in data:
# print ord(b),
# print ord(b),
# print ""
# except Exception, e:
# print str(e) + ":", str(data)
@ -91,19 +91,19 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
if data and data[0] == IAC:
try:
super(TelnetProtocol, self).dataReceived(data)
return
return
except Exception:
pass
StatefulTelnetProtocol.dataReceived(self, data)
def _write(self, data):
"hook overloading the one used in plain telnet"
#print "_write (%s): %s" % (self.state, " ".join(str(ord(c)) for c in data))
#print "_write (%s): %s" % (self.state, " ".join(str(ord(c)) for c in data))
data = data.replace('\n', '\r\n')
super(TelnetProtocol, self)._write(mccp_compress(self, data))
def sendLine(self, line):
"hook overloading the one used by linereceiver"
"hook overloading the one used by linereceiver"
#print "sendLine (%s):\n%s" % (self.state, line)
#escape IAC in line mode, and correctly add \r\n
line += self.delimiter
@ -112,17 +112,17 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
def lineReceived(self, string):
"""
Telnet method called when data is coming in over the telnet
Telnet method called when data is coming in over the telnet
connection. We pass it on to the game engine directly.
"""
"""
self.sessionhandler.data_in(self, string)
# Session hooks
# Session hooks
def disconnect(self, reason=None):
"""
generic hook for the engine to call in order to
generic hook for the engine to call in order to
disconnect this protocol.
"""
if reason:
@ -131,25 +131,25 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
def data_out(self, string, data=None):
"""
generic hook method for engine to call in order to send data
through the telnet connection.
Data Evennia -> Player.
generic hook method for engine to call in order to send data
through the telnet connection.
Data Evennia -> Player.
data argument may contain a dict with output flags.
"""
try:
string = utils.to_str(string, encoding=self.encoding)
except Exception, e:
self.sendLine(str(e))
return
try:
string = utils.to_str(string, encoding=self.encoding)
except Exception, e:
self.sendLine(str(e))
return
ttype = self.protocol_flags.get('TTYPE', {})
nomarkup = not (ttype.get('256 COLORS') or ttype.get('ANSI') or not ttype.get("init_done"))
raw = False
if type(data) == dict:
if type(data) == dict:
# check if we want escape codes to go through unparsed.
raw = data.get("raw", False)
# check if we want to remove all markup (TTYPE override)
nomarkup = data.get("nomarkup", False)
if raw:
self.sendLine(string)
if raw:
self.sendLine(string)
else:
self.sendLine(ansi.parse_ansi(string, strip_ansi=nomarkup, xterm256=ttype.get('256 COLORS')))

View file

@ -17,18 +17,18 @@ IS = chr(0)
SEND = chr(1)
# terminal capabilities and their codes
MTTS = [(128,'PROXY'),
(64, 'SCREEN READER'),
(32, 'OSC COLOR PALETTE'),
(16, 'MOUSE TRACKING'),
(8, '256 COLORS'),
MTTS = [(128,'PROXY'),
(64, 'SCREEN READER'),
(32, 'OSC COLOR PALETTE'),
(16, 'MOUSE TRACKING'),
(8, '256 COLORS'),
(4, 'UTF-8'),
(2, 'VT100'),
(1, 'ANSI')]
class Ttype(object):
"""
Handles ttype negotiations. Called and initiated by the
Handles ttype negotiations. Called and initiated by the
telnet protocol.
"""
def __init__(self, protocol):
@ -39,26 +39,26 @@ class Ttype(object):
the ttype_step indicates how far in the data retrieval we've
gotten.
"""
self.ttype_step = 0
self.ttype_step = 0
self.protocol = protocol
self.protocol.protocol_flags['TTYPE'] = {"init_done":False}
# setup protocol to handle ttype initialization and negotiation
self.protocol.negotiationMap[TTYPE] = self.do_ttype
self.protocol.negotiationMap[TTYPE] = self.do_ttype
# ask if client will ttype, connect callback if it does.
self.protocol.will(TTYPE).addCallbacks(self.do_ttype, self.no_ttype)
def no_ttype(self, option):
"""
Callback if ttype is not supported by client.
Callback if ttype is not supported by client.
"""
self.protocol.protocol_flags['TTYPE'] = False
self.protocol.protocol_flags['TTYPE'] = False
def do_ttype(self, option):
"""
Handles negotiation of the ttype protocol once the
client has confirmed that it supports the ttype
protocol.
Handles negotiation of the ttype protocol once the
client has confirmed that it supports the ttype
protocol.
The negotiation proceeds in several steps, each returning a
certain piece of information about the client. All data is
@ -66,15 +66,15 @@ class Ttype(object):
"""
if self.protocol.protocol_flags['TTYPE']['init_done']:
return
return
self.ttype_step += 1
if self.ttype_step == 1:
# set up info storage and initialize subnegotiation
if self.ttype_step == 1:
# set up info storage and initialize subnegotiation
self.protocol.requestNegotiation(TTYPE, SEND)
else:
# receive data
# receive data
option = "".join(option).lstrip(IS)
if self.ttype_step == 2:
self.protocol.protocol_flags['TTYPE']['CLIENTNAME'] = option
@ -82,17 +82,16 @@ class Ttype(object):
elif self.ttype_step == 3:
self.protocol.protocol_flags['TTYPE']['TERM'] = option
self.protocol.requestNegotiation(TTYPE, SEND)
elif self.ttype_step == 4:
elif self.ttype_step == 4:
option = int(option.strip('MTTS '))
self.protocol.protocol_flags['TTYPE']['MTTS'] = option
for codenum, standard in MTTS:
self.protocol.protocol_flags['TTYPE']['MTTS'] = option
for codenum, standard in MTTS:
if option == 0:
break
break
status = option % codenum < option
self.protocol.protocol_flags['TTYPE'][standard] = status
if status:
if status:
option = option % codenum
self.protocol.protocol_flags['TTYPE']['init_done'] = True
#print "ttype results:", self.protocol.protocol_flags['TTYPE']

View file

@ -4,17 +4,17 @@ Web client server resource.
The Evennia web client consists of two components running
on twisted and django. They are both a part of the Evennia
website url tree (so the testing website might be located
on http://localhost:8000/, whereas the webclient can be
found on http://localhost:8000/webclient.)
on http://localhost:8000/, whereas the webclient can be
found on http://localhost:8000/webclient.)
/webclient - this url is handled through django's template
system and serves the html page for the client
itself along with its javascript chat program.
/webclientdata - this url is called by the ajax chat using
POST requests (long-polling when necessary)
The WebClient resource in this module will
handle these requests and act as a gateway
to sessions connected over the webclient.
The WebClient resource in this module will
handle these requests and act as a gateway
to sessions connected over the webclient.
"""
import time
from hashlib import md5
@ -25,7 +25,7 @@ from twisted.internet import defer, reactor
from django.utils import simplejson
from django.utils.functional import Promise
from django.utils.encoding import force_unicode
from django.conf import settings
from django.conf import settings
from src.utils import utils, logger, ansi
from src.utils.text2html import parse_html
from src.server import session
@ -34,7 +34,7 @@ SERVERNAME = settings.SERVERNAME
ENCODINGS = settings.ENCODINGS
# defining a simple json encoder for returning
# django data to the client. Might need to
# django data to the client. Might need to
# extend this if one wants to send more
# complex database objects too.
@ -51,36 +51,36 @@ def jsonify(obj):
# WebClient resource - this is called by the ajax client
# using POST requests to /webclientdata.
#
class WebClient(resource.Resource):
"""
An ajax/comet long-polling transport
An ajax/comet long-polling transport
"""
isLeaf = True
isLeaf = True
allowedMethods = ('POST',)
def __init__(self):
self.requests = {}
self.databuffer = {}
def getChild(self, path, request):
"""
This is the place to put dynamic content.
"""
return self
return self
def _responseFailed(self, failure, suid, request):
"callback if a request is lost/timed out"
"callback if a request is lost/timed out"
try:
self.requests.get(suid, []).remove(request)
except ValueError:
pass
pass
def lineSend(self, suid, string, data=None):
"""
This adds the data to the buffer and/or sends it to
the client as soon as possible.
"""
"""
requests = self.requests.get(suid, None)
if requests:
request = requests.pop(0)
@ -88,22 +88,22 @@ class WebClient(resource.Resource):
request.write(jsonify({'msg':string, 'data':data}))
request.finish()
self.requests[suid] = requests
else:
else:
# no waiting request. Store data in buffer
dataentries = self.databuffer.get(suid, [])
dataentries.append(jsonify({'msg':string, 'data':data}))
self.databuffer[suid] = dataentries
def client_disconnect(self, suid):
"""
Disconnect session with given 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]
del self.databuffer[suid]
def mode_init(self, request):
"""
@ -120,10 +120,10 @@ class WebClient(resource.Resource):
# creating a unique id hash string
suid = md5(str(time.time())).hexdigest()
self.requests[suid] = []
self.databuffer[suid] = []
self.databuffer[suid] = []
sess = WebClientSession()
sess.client = self
sess.client = self
sess.init_session("comet", remote_addr, self.sessionhandler)
sess.suid = suid
sess.sessionhandler.connect(sess)
@ -154,22 +154,22 @@ class WebClient(resource.Resource):
available.
"""
suid = request.args.get('suid', ['0'])[0]
if suid == '0':
if suid == '0':
return ''
dataentries = self.databuffer.get(suid, [])
if dataentries:
return dataentries.pop(0)
reqlist = self.requests.get(suid, [])
request.notifyFinish().addErrback(self._responseFailed, suid, request)
reqlist.append(request)
reqlist.append(request)
self.requests[suid] = reqlist
return server.NOT_DONE_YET
def mode_close(self, request):
"""
This is called by render_POST when the client is signalling
that it is about to be closed.
that it is about to be closed.
"""
suid = request.args.get('suid', ['0'])[0]
if suid == '0':
@ -184,8 +184,8 @@ class WebClient(resource.Resource):
initializing or sending/receving data through the request. It
uses a long-polling mechanism to avoid sending data unless
there is actual data available.
"""
dmode = request.args.get('mode', [None])[0]
"""
dmode = request.args.get('mode', [None])[0]
if dmode == 'init':
# startup. Setup the server.
return self.mode_init(request)
@ -201,11 +201,11 @@ class WebClient(resource.Resource):
else:
# this should not happen if client sends valid data.
return ''
#
# A session type handling communication over the
# web client interface.
#
# A session type handling communication over the
# web client interface.
#
class WebClientSession(session.Session):
"""
@ -215,38 +215,38 @@ class WebClientSession(session.Session):
def disconnect(self, reason=None):
"""
Disconnect from server
"""
"""
if reason:
self.client.lineSend(self.suid, reason)
self.client.client_disconnect(self.suid)
def data_out(self, string='', data=None):
"""
Data Evennia -> Player access hook.
Data Evennia -> Player access hook.
data argument may be used depending on
the client-server implementation.
the client-server implementation.
"""
if data:
# treat data?
pass
# string handling is similar to telnet
try:
string = utils.to_str(string, encoding=self.encoding)
string = utils.to_str(string, encoding=self.encoding)
nomarkup = False
raw = False
raw = False
if type(data) == dict:
# check if we want escape codes to go through unparsed.
raw = data.get("raw", False)
# check if we want to remove all markup
nomarkup = data.get("nomarkup", False)
# check if we want to remove all markup
nomarkup = data.get("nomarkup", False)
if raw:
self.client.lineSend(self.suid, string)
else:
self.client.lineSend(self.suid, parse_html(ansi.parse_ansi(string, strip_ansi=nomarkup)))
return
except Exception, e:
return
except Exception, e:
logger.log_trace()

View file

@ -14,7 +14,7 @@ a great example/aid on how to do this.)
from twisted.web import resource
from twisted.python import threadpool
from twisted.internet import reactor
from twisted.application import service, internet
from twisted.application import service, internet
from twisted.web.wsgi import WSGIResource
from django.core.handlers.wsgi import WSGIHandler
@ -26,19 +26,19 @@ from django.core.handlers.wsgi import WSGIHandler
class DjangoWebRoot(resource.Resource):
"""
This creates a web root (/) that Django
understands by tweaking the way the
child instancee are recognized.
understands by tweaking the way the
child instancee are recognized.
"""
def __init__(self, pool):
"""
Setup the django+twisted resource
"""
resource.Resource.__init__(self)
resource.Resource.__init__(self)
self.wsgi_resource = WSGIResource(reactor, pool , WSGIHandler())
def getChild(self, path, request):
"""
To make things work we nudge the
To make things work we nudge the
url tree to make this the root.
"""
path0 = request.prepath.pop(0)
@ -62,9 +62,9 @@ class WSGIWebServer(internet.TCPServer):
internet.TCPServer.__init__(self, *args, **kwargs)
def startService(self):
"Start the pool after the service"
internet.TCPServer.startService(self)
self.pool.start()
def stopService(self):
internet.TCPServer.startService(self)
self.pool.start()
def stopService(self):
"Safely stop the pool after service stop."
internet.TCPServer.stopService(self)
internet.TCPServer.stopService(self)
self.pool.stop()