This commit is contained in:
Griatch 2013-06-03 17:09:14 +02:00
commit 965e236d9a
30 changed files with 979 additions and 634 deletions

View file

@ -403,6 +403,7 @@ def error_check_python_modules():
deprstring = "settings.%s should be renamed to %s. If defaults are used, their path/classname must be updated (see src/settings_default.py)." deprstring = "settings.%s should be renamed to %s. If defaults are used, their path/classname must be updated (see src/settings_default.py)."
if hasattr(settings, "CMDSET_DEFAULT"): raise DeprecationWarning(deprstring % ("CMDSET_DEFAULT", "CMDSET_CHARACTER")) if hasattr(settings, "CMDSET_DEFAULT"): raise DeprecationWarning(deprstring % ("CMDSET_DEFAULT", "CMDSET_CHARACTER"))
if hasattr(settings, "CMDSET_OOC"): raise DeprecationWarning(deprstring % ("CMDSET_OOC", "CMDSET_PLAYER")) if hasattr(settings, "CMDSET_OOC"): raise DeprecationWarning(deprstring % ("CMDSET_OOC", "CMDSET_PLAYER"))
if settings.WEBSERVER_ENABLED and not isinstance(settings.WEBSERVER_PORTS[0], tuple): raise DeprecationWarning("settings.WEBSERVER_PORTS must be on the form [(proxyport, serverport), ...]")
from src.commands import cmdsethandler from src.commands import cmdsethandler
if not cmdsethandler.import_cmdset(settings.CMDSET_UNLOGGEDIN, None): print "Warning: CMDSET_UNLOGGED failed to load!" if not cmdsethandler.import_cmdset(settings.CMDSET_UNLOGGEDIN, None): print "Warning: CMDSET_UNLOGGED failed to load!"

View file

@ -50,7 +50,7 @@ from django.conf import settings
# Setup access of the evennia server itself # Setup access of the evennia server itself
SERVER_PY_FILE = os.path.join(settings.SRC_DIR, 'server/server.py') SERVER_PY_FILE = os.path.join(settings.SRC_DIR, 'server/server.py')
PORTAL_PY_FILE = os.path.join(settings.SRC_DIR, 'server/portal.py') PORTAL_PY_FILE = os.path.join(settings.SRC_DIR, 'server/portal/portal.py')
# Get logfile names # Get logfile names
SERVER_LOGFILE = settings.SERVER_LOG_FILE SERVER_LOGFILE = settings.SERVER_LOG_FILE

View file

@ -80,7 +80,7 @@ class ObjectDBAdmin(admin.ModelAdmin):
) )
#deactivated temporarily, they cause empty objects to be created in admin #deactivated temporarily, they cause empty objects to be created in admin
inlines = [AliasInline]#, ObjAttributeInline] #inlines = [AliasInline, ObjAttributeInline]
# Custom modification to give two different forms wether adding or not. # Custom modification to give two different forms wether adding or not.
@ -111,6 +111,7 @@ class ObjectDBAdmin(admin.ModelAdmin):
return super(ObjectDBAdmin, self).get_form(request, obj, **defaults) return super(ObjectDBAdmin, self).get_form(request, obj, **defaults)
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
obj.save()
if not change: if not change:
# adding a new object # adding a new object
obj = obj.typeclass obj = obj.typeclass

View file

@ -17,11 +17,13 @@ transparently through the decorating TypeClass.
import traceback import traceback
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
from django.db.models.signals import post_init, pre_delete
from src.utils.idmapper.models import SharedMemoryModel from src.utils.idmapper.models import SharedMemoryModel
from src.typeclasses.models import Attribute, TypedObject, TypeNick, TypeNickHandler from src.typeclasses.models import Attribute, TypedObject, TypeNick, TypeNickHandler
from src.server.caches import get_field_cache, set_field_cache, del_field_cache from src.server.caches import get_field_cache, set_field_cache, del_field_cache
from src.server.caches import get_prop_cache, set_prop_cache, del_prop_cache from src.server.caches import get_prop_cache, set_prop_cache, del_prop_cache
from src.server.caches import attr_post_init, attr_pre_delete
from src.typeclasses.typeclass import TypeClass from src.typeclasses.typeclass import TypeClass
from src.players.models import PlayerNick from src.players.models import PlayerNick
from src.objects.manager import ObjectManager from src.objects.manager import ObjectManager
@ -53,6 +55,7 @@ _HERE = _("here")
# #
#------------------------------------------------------------ #------------------------------------------------------------
class ObjAttribute(Attribute): class ObjAttribute(Attribute):
"Attributes for ObjectDB objects." "Attributes for ObjectDB objects."
db_obj = models.ForeignKey("ObjectDB") db_obj = models.ForeignKey("ObjectDB")
@ -62,6 +65,10 @@ class ObjAttribute(Attribute):
verbose_name = "Object Attribute" verbose_name = "Object Attribute"
verbose_name_plural = "Object Attributes" verbose_name_plural = "Object Attributes"
# attach the cache handlers
post_init.connect(attr_post_init, sender=ObjAttribute, dispatch_uid="objattrcache")
pre_delete.connect(attr_pre_delete, sender=ObjAttribute, dispatch_uid="objattrcache")
#------------------------------------------------------------ #------------------------------------------------------------
# #
# Alias # Alias
@ -241,7 +248,7 @@ class ObjectDB(TypedObject):
"Deleter. Allows for del self.aliases" "Deleter. Allows for del self.aliases"
for alias in Alias.objects.filter(db_obj=self): for alias in Alias.objects.filter(db_obj=self):
alias.delete() alias.delete()
del_prop_cache(self, "_aliases") #del_prop_cache(self, "_aliases")
aliases = property(__aliases_get, __aliases_set, __aliases_del) aliases = property(__aliases_get, __aliases_set, __aliases_del)
# player property (wraps db_player) # player property (wraps db_player)
@ -299,27 +306,16 @@ class ObjectDB(TypedObject):
del_field_cache(self, "sessid") del_field_cache(self, "sessid")
sessid = property(__sessid_get, __sessid_set, __sessid_del) sessid = property(__sessid_get, __sessid_set, __sessid_del)
# location property (wraps db_location) def _db_location_handler(self, new_value, old_value=None):
#@property "This handles changes to the db_location field."
def __location_get(self): print "db_location_handler:", new_value, old_value
"Getter. Allows for value = self.location."
loc = get_field_cache(self, "location")
if loc:
return _GA(loc, "typeclass")
return None
#@location.setter
def __location_set(self, location):
"Setter. Allows for self.location = location"
try: try:
old_loc = _GA(self, "location") old_loc = old_value
if ObjectDB.objects.dbref(location): # new_value can be dbref, typeclass or dbmodel
# dbref search if ObjectDB.objects.dbref(new_value, reqhash=False):
loc = ObjectDB.objects.dbref_search(location) loc = ObjectDB.objects.dbref_search(new_value)
loc = loc and _GA(loc, "dbobj") # this should not fail if new_value is valid.
elif location and type(location) != ObjectDB: loc = _GA(loc, "dbobj")
loc = _GA(location, "dbobj")
else:
loc = location
# recursive location check # recursive location check
def is_loc_loop(loc, depth=0): def is_loc_loop(loc, depth=0):
@ -333,32 +329,85 @@ class ObjectDB(TypedObject):
except RuntimeWarning: pass except RuntimeWarning: pass
# set the location # set the location
set_field_cache(self, "location", loc) _SA(self, "db_location", loc)
# update the contents of each location # update the contents of each location
if old_loc: if old_loc:
_GA(_GA(old_loc, "dbobj"), "contents_update")() _GA(_GA(old_loc, "dbobj"), "contents_update")(self, remove=True)
if loc: if loc:
_GA(loc, "contents_update")() _GA(loc, "contents_update")(self)
except RuntimeError: except RuntimeError:
string = "Cannot set location, " string = "Cannot set location, "
string += "%s.location = %s would create a location-loop." % (self.key, loc) string += "%s.location = %s would create a location-loop." % (self.key, new_value)
_GA(self, "msg")(_(string)) _GA(self, "msg")(_(string))
logger.log_trace(string) logger.log_trace(string)
raise RuntimeError(string) raise RuntimeError(string)
except Exception, e: except Exception, e:
string = "Cannot set location (%s): " % str(e) string = "Cannot set location (%s): " % str(e)
string += "%s is not a valid location." % location string += "%s is not a valid location." % new_value
_GA(self, "msg")(_(string)) _GA(self, "msg")(_(string))
logger.log_trace(string) logger.log_trace(string)
raise Exception(string) raise Exception(string)
#@location.deleter
def __location_del(self): ## location property (wraps db_location)
"Deleter. Allows for del self.location" ##@property
_GA(self, "location").contents_update() #def __location_get(self):
_SA(self, "db_location", None) # "Getter. Allows for value = self.location."
_GA(self, "save")() # loc = get_field_cache(self, "location")
del_field_cache(self, "location") # if loc:
location = property(__location_get, __location_set, __location_del) # return _GA(loc, "typeclass")
# return None
##@location.setter
#def __location_set(self, location):
# "Setter. Allows for self.location = location"
# try:
# old_loc = _GA(self, "location")
# if ObjectDB.objects.dbref(location):
# # dbref search
# loc = ObjectDB.objects.dbref_search(location)
# loc = loc and _GA(loc, "dbobj")
# elif location and type(location) != ObjectDB:
# loc = _GA(location, "dbobj")
# else:
# loc = location
# # recursive location check
# def is_loc_loop(loc, depth=0):
# "Recursively traverse the target location to make sure we are not in it."
# if depth > 10: return
# elif loc == self: raise RuntimeError
# elif loc == None: raise RuntimeWarning # just to quickly get out
# return is_loc_loop(_GA(loc, "db_location"), depth+1)
# # check so we don't create a location loop - if so, RuntimeError will be raised.
# try: is_loc_loop(loc)
# except RuntimeWarning: pass
# # set the location
# set_field_cache(self, "location", loc)
# # update the contents of each location
# if old_loc:
# _GA(_GA(old_loc, "dbobj"), "contents_update")()
# if loc:
# _GA(loc, "contents_update")()
# except RuntimeError:
# string = "Cannot set location, "
# string += "%s.location = %s would create a location-loop." % (self.key, loc)
# _GA(self, "msg")(_(string))
# logger.log_trace(string)
# raise RuntimeError(string)
# except Exception, e:
# string = "Cannot set location (%s): " % str(e)
# string += "%s is not a valid location." % location
# _GA(self, "msg")(_(string))
# logger.log_trace(string)
# raise Exception(string)
##@location.deleter
#def __location_del(self):
# "Deleter. Allows for del self.location"
# _GA(self, "location").contents_update()
# _SA(self, "db_location", None)
# _GA(self, "save")()
# del_field_cache(self, "location")
#location = property(__location_get, __location_set, __location_del)
# home property (wraps db_home) # home property (wraps db_home)
#@property #@property
@ -515,19 +564,26 @@ class ObjectDB(TypedObject):
exclude = make_iter(exclude) exclude = make_iter(exclude)
if cont == None: if cont == None:
cont = _GA(self, "contents_update")() cont = _GA(self, "contents_update")()
return [obj for obj in cont if obj not in exclude] return [obj for obj in cont.values() if obj not in exclude]
contents = property(contents_get) contents = property(contents_get)
def contents_update(self): def contents_update(self, obj=None, remove=False):
""" """
Updates the contents property of the object with a new Updates the contents property of the object
object Called by
self.location_set.
obj - add - object to add to content list
remove (true/false) - remove obj from content list remove object to remove from content list
""" """
cont = ObjectDB.objects.get_contents(self) cont = get_prop_cache(self, "_contents")
if not cont:
cont = {}
if obj:
if remove:
cont.pop(self.dbid, None)
else:
cont[self.dbid] = obj
else:
cont = dict((o.dbid, o) for o in ObjectDB.objects.get_contents(self))
set_prop_cache(self, "_contents", cont) set_prop_cache(self, "_contents", cont)
return cont return cont

View file

@ -27,9 +27,12 @@ from django.conf import settings
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.encoding import smart_str from django.utils.encoding import smart_str
from django.db.models.signals import post_init, pre_delete
from src.server.caches import get_field_cache, set_field_cache, del_field_cache from src.server.caches import get_field_cache, set_field_cache, del_field_cache
from src.server.caches import get_prop_cache, set_prop_cache, del_prop_cache from src.server.caches import get_prop_cache, set_prop_cache, del_prop_cache
from src.server.caches import attr_post_init, attr_pre_delete
from src.players import manager from src.players import manager
from src.scripts.models import ScriptDB from src.scripts.models import ScriptDB
from src.typeclasses.models import Attribute, TypedObject, TypeNick, TypeNickHandler from src.typeclasses.models import Attribute, TypedObject, TypeNick, TypeNickHandler
@ -74,6 +77,9 @@ class PlayerAttribute(Attribute):
"Define Django meta options" "Define Django meta options"
verbose_name = "Player Attribute" verbose_name = "Player Attribute"
post_init.connect(attr_post_init, sender=PlayerAttribute, dispatch_uid="playerattrcache")
pre_delete.connect(attr_pre_delete, sender=PlayerAttribute, dispatch_uid="playerattrcache")
#------------------------------------------------------------ #------------------------------------------------------------
# #
# Player Nicks # Player Nicks

View file

@ -26,6 +26,9 @@ Common examples of uses of Scripts:
""" """
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.db.models.signals import post_init, pre_delete
from src.server.caches import attr_post_init, attr_pre_delete
from src.typeclasses.models import Attribute, TypedObject from src.typeclasses.models import Attribute, TypedObject
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from src.scripts.manager import ScriptManager from src.scripts.manager import ScriptManager
@ -47,6 +50,9 @@ class ScriptAttribute(Attribute):
verbose_name = "Script Attribute" verbose_name = "Script Attribute"
verbose_name_plural = "Script Attributes" verbose_name_plural = "Script Attributes"
# attach cache handlers for attribute lookup
post_init.connect(attr_post_init, sender=ScriptAttribute, dispatch_uid="scriptattrcache")
pre_delete.connect(attr_pre_delete, sender=ScriptAttribute, dispatch_uid="scriptattrcache")
#------------------------------------------------------------ #------------------------------------------------------------
# #

View file

@ -3,26 +3,42 @@ Central caching module.
""" """
from sys import getsizeof
from collections import defaultdict from collections import defaultdict
from django.conf import settings from django.dispatch import Signal
from django.core.cache import get_cache
_ENABLE_LOCAL_CACHES = settings.GAME_CACHE_TYPE #from django.db.models.signals import pre_save, pre_delete, post_init
from src.server.models import ServerConfig
from src.utils.utils import uses_database, to_str
_GA = object.__getattribute__ _GA = object.__getattribute__
_SA = object.__setattr__ _SA = object.__setattr__
_DA = object.__delattr__ _DA = object.__delattr__
# OOB hooks (OOB not yet functional, don't use yet) #
_OOB_FIELD_UPDATE_HOOKS = defaultdict(dict) # Open handles to the caches
_OOB_PROP_UPDATE_HOOKS = defaultdict(dict) #
_OOB_ATTR_UPDATE_HOOKS = defaultdict(dict)
_OOB_NDB_UPDATE_HOOKS = defaultdict(dict)
_OOB_CUSTOM_UPDATE_HOOKS = defaultdict(dict)
_OOB_HANDLER = None # set by oob handler when it initializes _FIELD_CACHE = get_cache("field_cache")
_ATTR_CACHE = get_cache("attr_cache")
#_PROP_CACHE = get_cache("prop_cache")
_PROP_CACHE = defaultdict(dict)
def hashid(obj): # make sure caches are empty at startup
_FIELD_CACHE.clear()
_ATTR_CACHE.clear()
#_PROP_CACHE.clear()
#------------------------------------------------------------
# Cache key hash generation
#------------------------------------------------------------
if uses_database("mysql") and ServerConfig.objects.get_mysql_db_version() < '5.6.4':
# mysql <5.6.4 don't support millisecond precision
_DATESTRING = "%Y:%m:%d-%H:%M:%S:000000"
else:
_DATESTRING = "%Y:%m:%d-%H:%M:%S:%f"
def hashid(obj, suffix=""):
""" """
Returns a per-class unique that combines the object's Returns a per-class unique that combines the object's
class name with its idnum and creation time. This makes this id unique also class name with its idnum and creation time. This makes this id unique also
@ -35,12 +51,12 @@ def hashid(obj):
hid = _GA(obj, "_hashid") hid = _GA(obj, "_hashid")
except AttributeError: except AttributeError:
try: try:
date, idnum = _GA(obj, "db_date_created"), _GA(obj, "id") date, idnum = _GA(obj, "db_date_created").strftime(_DATESTRING), _GA(obj, "id")
except AttributeError: except AttributeError:
try: try:
# maybe a typeclass, try to go to dbobj # maybe a typeclass, try to go to dbobj
obj = _GA(obj, "dbobj") obj = _GA(obj, "dbobj")
date, idnum = _GA(obj, "db_date_created"), _GA(obj, "id") date, idnum = _GA(obj, "db_date_created").strftime(_DATESTRING), _GA(obj, "id")
except AttributeError: except AttributeError:
# this happens if hashing something like ndb. We have to # this happens if hashing something like ndb. We have to
# rely on memory adressing in this case. # rely on memory adressing in this case.
@ -48,289 +64,441 @@ def hashid(obj):
if not idnum or not date: if not idnum or not date:
# this will happen if setting properties on an object which is not yet saved # this will happen if setting properties on an object which is not yet saved
return None return None
# build the hashid
hid = "%s-%s-#%s" % (_GA(obj, "__class__"), date, idnum) hid = "%s-%s-#%s" % (_GA(obj, "__class__"), date, idnum)
hid = hid.replace(" ", "") # we have to remove the class-name's space, for memcached's sake
# we cache the object part of the hashid to avoid too many object lookups
_SA(obj, "_hashid", hid) _SA(obj, "_hashid", hid)
return hid # build the complete hashid
hid = "%s%s" % (hid, suffix)
return to_str(hid)
# oob helper functions
def register_oob_update_hook(obj,name, entity="field"): #------------------------------------------------------------
# Cache callback handlers
#------------------------------------------------------------
#------------------------------------------------------------
# Field cache - makes sure to cache all database fields when
# they are saved, no matter from where.
#------------------------------------------------------------
# callback to pre_save signal (connected in src.server.server)
def field_pre_save(sender, instance=None, update_fields=None, raw=False, **kwargs):
""" """
Register hook function to be called when field/property/db/ndb is updated. Called at the beginning of the save operation. The save method
Given function will be called with function(obj, entityname, newvalue, *args, **kwargs) must be called with the update_fields keyword in order to
entity - one of "field", "property", "db", "ndb" or "custom"
""" """
hid = hashid(obj) if raw:
return
if update_fields:
# this is a list of strings at this point. We want field objects
update_fields = (instance._meta.get_field_by_name(field)[0] for field in update_fields)
else:
# meta.fields are already field objects
update_fields = instance._meta.fields
for field in update_fields:
fieldname = field.name
new_value = field.value_from_object(instance)
handlername = "_%s_handler" % fieldname
try:
handler = _GA(instance, handlername)
except AttributeError:
handler = None
hid = hashid(instance, "-%s" % fieldname)
if callable(handler):
old_value = _FIELD_CACHE.get(hid) if hid else None
# the handler may modify the stored value in various ways
# don't catch exceptions, the handler must work!
new_value = handler(new_value, old_value=old_value)
# we re-assign this to the field, save() will pick it up from there
_SA(instance, fieldname, new_value)
if hid:
# update cache
_FIELD_CACHE.set(hid, new_value)
# access method
def flush_field_cache():
"Clear the field cache"
_FIELD_CACHE.clear()
#------------------------------------------------------------
# Attr cache - caching the attribute objects related to a given object to
# avoid lookups more than necessary (this makes Attributes en par in speed
# to any property).
#------------------------------------------------------------
# connected to post_init signal (connected in respective Attribute model)
def attr_post_init(sender, instance=None, **kwargs):
"Called when attribute is created or retrieved in connection with obj."
#print "attr_post_init:", instance, instance.db_obj, instance.db_key
hid = hashid(_GA(instance, "db_obj"), "-%s" % _GA(instance, "db_key"))
if hid: if hid:
if entity == "field": _ATTR_CACHE.set(hid, sender)
global _OOB_FIELD_UPDATE_HOOKS
_OOB_FIELD_UPDATE_HOOKS[hid][name] = True
return
elif entity == "property":
global _OOB_PROP_UPDATE_HOOKS
_OOB_PROP_UPDATE_HOOKS[hid][name] = True
elif entity == "db":
global _OOB_ATTR_UPDATE_HOOKS
_OOB_ATTR_UPDATE_HOOKS[hid][name] = True
elif entity == "ndb":
global _OOB_NDB_UPDATE_HOOKS
_OOB_NDB_UPDATE_HOOKS[hid][name] = True
elif entity == "custom":
global _OOB_CUSTOM_UPDATE_HOOKS
_OOB_CUSTOM_UPDATE_HOOKS[hid][name] = True
else:
return None
def unregister_oob_update_hook(obj, name, entity="property"): # connected to pre_delete signal (connected in respective Attribute model)
""" def attr_pre_delete(sender, instance=None, **kwargs):
Un-register a report hook "Called when attribute is deleted (del_attribute)"
""" #print "attr_pre_delete:", instance, instance.db_obj, instance.db_key
hid = hashid(obj) hid = hashid(_GA(instance, "db_obj"), "-%s" % _GA(instance, "db_key"))
if hid: if hid:
global _OOB_FIELD_UPDATE_HOOKS,_OOB_PROP_UPDATE_HOOKS, _OOB_ATTR_UPDATE_HOOKS #print "attr_pre_delete:", _GA(instance, "db_key")
global _OOB_CUSTOM_UPDATE_HOOKS, _OOB_NDB_UPDATE_HOOKS _ATTR_CACHE.delete(hid)
if entity == "field" and name in _OOB_FIELD_UPDATE_HOOKS:
del _OOB_FIELD_UPDATE_HOOKS[hid][name]
elif entity == "property" and name in _OOB_PROP_UPDATE_HOOKS:
del _OOB_PROP_UPDATE_HOOKS[hid][name]
elif entity == "db" and name in _OOB_ATTR_UPDATE_HOOKS:
del _OOB_ATTR_UPDATE_HOOKS[hid][name]
elif entity == "ndb" and name in _OOB_NDB_UPDATE_HOOKS:
del _OOB_NDB_UPDATE_HOOKS[hid][name]
elif entity == "custom" and name in _OOB_CUSTOM_UPDATE_HOOKS:
del _OOB_CUSTOM_UPDATE_HOOKS[hid][name]
else:
return None
def call_ndb_hooks(obj, attrname, value): # access methods
"""
No caching is done of ndb here, but def get_attr_cache(obj, attrname):
we use this as a way to call OOB hooks. "Called by get_attribute"
""" hid = hashid(obj, "-%s" % attrname)
hid = hashid(obj) _ATTR_CACHE.delete(hid)
return hid and _ATTR_CACHE.get(hid, None) or None
def set_attr_cache(attrobj):
"Set the attr cache manually; this can be used to update"
attr_post_init(None, instance=attrobj)
def flush_attr_cache():
"Clear attribute cache"
_ATTR_CACHE.clear()
#------------------------------------------------------------
# Property cache - this is a generic cache for properties stored on models.
#------------------------------------------------------------
# access methods
def get_prop_cache(obj, propname):
"retrieve data from cache"
hid = hashid(obj, "-%s" % propname)
if hid: if hid:
oob_hook = _OOB_NDB_UPDATE_HOOKS[hid].get(attrname) #print "get_prop_cache", hid, propname, _PROP_CACHE.get(hid, None)
if oob_hook: return _PROP_CACHE[hid].get(propname, None)
oob_hook[0](obj.typeclass, attrname, value, *oob_hook[1], **oob_hook[2])
def call_custom_hooks(obj, attrname, value): def set_prop_cache(obj, propname, propvalue):
""" "Set property cache"
Custom handler for developers adding their own oob hooks, e.g. to hid = hashid(obj, "-%s" % propname)
custom typeclass properties.
"""
hid = hashid(obj)
if hid: if hid:
oob_hook = _OOB_CUSTOM_UPDATE_HOOKS[hid].get(attrname) #print "set_prop_cache", propname, propvalue
if oob_hook: _PROP_CACHE[hid][propname] = propvalue
oob_hook[0](obj.typeclass, attrname, value, *oob_hook[1], **oob_hook[2]) #_PROP_CACHE.set(hid, propvalue)
def del_prop_cache(obj, propname):
"Delete element from property cache"
hid = hashid(obj, "-%s" % propname)
if hid and propname in _PROP_CACHE[hid]:
del _PROP_CACHE[hid][propname]
#_PROP_CACHE.delete(hid)
if _ENABLE_LOCAL_CACHES: def flush_prop_cache():
"Clear property cache"
# Cache stores global _PROP_CACHE
_ATTR_CACHE = defaultdict(dict)
_FIELD_CACHE = defaultdict(dict)
_PROP_CACHE = defaultdict(dict) _PROP_CACHE = defaultdict(dict)
#_PROP_CACHE.clear()
def get_cache_sizes(): #_ENABLE_LOCAL_CACHES = settings.GAME_CACHE_TYPE
""" ## oob helper functions
Get cache sizes, expressed in number of objects and memory size in MB # OOB hooks (OOB not yet functional, don't use yet)
""" #_OOB_FIELD_UPDATE_HOOKS = defaultdict(dict)
global _ATTR_CACHE, _FIELD_CACHE, _PROP_CACHE #_OOB_PROP_UPDATE_HOOKS = defaultdict(dict)
#_OOB_ATTR_UPDATE_HOOKS = defaultdict(dict)
#_OOB_NDB_UPDATE_HOOKS = defaultdict(dict)
#_OOB_CUSTOM_UPDATE_HOOKS = defaultdict(dict)
#
#_OOB_HANDLER = None # set by oob handler when it initializes
#def register_oob_update_hook(obj,name, entity="field"):
# """
# Register hook function to be called when field/property/db/ndb is updated.
# Given function will be called with function(obj, entityname, newvalue, *args, **kwargs)
# entity - one of "field", "property", "db", "ndb" or "custom"
# """
# hid = hashid(obj)
# if hid:
# if entity == "field":
# global _OOB_FIELD_UPDATE_HOOKS
# _OOB_FIELD_UPDATE_HOOKS[hid][name] = True
# return
# elif entity == "property":
# global _OOB_PROP_UPDATE_HOOKS
# _OOB_PROP_UPDATE_HOOKS[hid][name] = True
# elif entity == "db":
# global _OOB_ATTR_UPDATE_HOOKS
# _OOB_ATTR_UPDATE_HOOKS[hid][name] = True
# elif entity == "ndb":
# global _OOB_NDB_UPDATE_HOOKS
# _OOB_NDB_UPDATE_HOOKS[hid][name] = True
# elif entity == "custom":
# global _OOB_CUSTOM_UPDATE_HOOKS
# _OOB_CUSTOM_UPDATE_HOOKS[hid][name] = True
# else:
# return None
#
#def unregister_oob_update_hook(obj, name, entity="property"):
# """
# Un-register a report hook
# """
# hid = hashid(obj)
# if hid:
# global _OOB_FIELD_UPDATE_HOOKS,_OOB_PROP_UPDATE_HOOKS, _OOB_ATTR_UPDATE_HOOKS
# global _OOB_CUSTOM_UPDATE_HOOKS, _OOB_NDB_UPDATE_HOOKS
# if entity == "field" and name in _OOB_FIELD_UPDATE_HOOKS:
# del _OOB_FIELD_UPDATE_HOOKS[hid][name]
# elif entity == "property" and name in _OOB_PROP_UPDATE_HOOKS:
# del _OOB_PROP_UPDATE_HOOKS[hid][name]
# elif entity == "db" and name in _OOB_ATTR_UPDATE_HOOKS:
# del _OOB_ATTR_UPDATE_HOOKS[hid][name]
# elif entity == "ndb" and name in _OOB_NDB_UPDATE_HOOKS:
# del _OOB_NDB_UPDATE_HOOKS[hid][name]
# elif entity == "custom" and name in _OOB_CUSTOM_UPDATE_HOOKS:
# del _OOB_CUSTOM_UPDATE_HOOKS[hid][name]
# else:
# return None
#
#def call_ndb_hooks(obj, attrname, value):
# """
# No caching is done of ndb here, but
# we use this as a way to call OOB hooks.
# """
# hid = hashid(obj)
# if hid:
# oob_hook = _OOB_NDB_UPDATE_HOOKS[hid].get(attrname)
# if oob_hook:
# oob_hook[0](obj.typeclass, attrname, value, *oob_hook[1], **oob_hook[2])
#
#def call_custom_hooks(obj, attrname, value):
# """
# Custom handler for developers adding their own oob hooks, e.g. to
# custom typeclass properties.
# """
# hid = hashid(obj)
# if hid:
# oob_hook = _OOB_CUSTOM_UPDATE_HOOKS[hid].get(attrname)
# if oob_hook:
# oob_hook[0](obj.typeclass, attrname, value, *oob_hook[1], **oob_hook[2])
#
#
attr_n = sum(len(dic) for dic in _ATTR_CACHE.values()) # # old cache system
attr_mb = sum(sum(getsizeof(obj) for obj in dic.values()) for dic in _ATTR_CACHE.values()) / 1024.0 #
# if _ENABLE_LOCAL_CACHES:
# # Cache stores
# _ATTR_CACHE = defaultdict(dict)
# _FIELD_CACHE = defaultdict(dict)
# _PROP_CACHE = defaultdict(dict)
#
#
# def get_cache_sizes():
# """
# Get cache sizes, expressed in number of objects and memory size in MB
# """
# global _ATTR_CACHE, _FIELD_CACHE, _PROP_CACHE
#
# attr_n = sum(len(dic) for dic in _ATTR_CACHE.values())
# attr_mb = sum(sum(getsizeof(obj) for obj in dic.values()) for dic in _ATTR_CACHE.values()) / 1024.0
#
# field_n = sum(len(dic) for dic in _FIELD_CACHE.values())
# field_mb = sum(sum([getsizeof(obj) for obj in dic.values()]) for dic in _FIELD_CACHE.values()) / 1024.0
#
# prop_n = sum(len(dic) for dic in _PROP_CACHE.values())
# prop_mb = sum(sum([getsizeof(obj) for obj in dic.values()]) for dic in _PROP_CACHE.values()) / 1024.0
#
# return (attr_n, attr_mb), (field_n, field_mb), (prop_n, prop_mb)
#
# # on-object database field cache
# def get_field_cache(obj, name):
# "On-model Cache handler."
# global _FIELD_CACHE
# hid = hashid(obj)
# if hid:
# try:
# return _FIELD_CACHE[hid][name]
# except KeyError:
# val = _GA(obj, "db_%s" % name)
# _FIELD_CACHE[hid][name] = val
# return val
# return _GA(obj, "db_%s" % name)
#
# def set_field_cache(obj, name, val):
# "On-model Cache setter. Also updates database."
# _SA(obj, "db_%s" % name, val)
# _GA(obj, "save")()
# hid = hashid(obj)
# if hid:
# global _FIELD_CACHE
# _FIELD_CACHE[hid][name] = val
# # oob hook functionality
# if _OOB_FIELD_UPDATE_HOOKS[hid].get(name):
# _OOB_HANDLER.update(hid, name, val)
#
# def del_field_cache(obj, name):
# "On-model cache deleter"
# hid = hashid(obj)
# _SA(obj, "db_%s" % name, None)
# _GA(obj, "save")()
# if hid:
# try:
# del _FIELD_CACHE[hid][name]
# except KeyError:
# pass
# if _OOB_FIELD_UPDATE_HOOKS[hid].get(name):
# _OOB_HANDLER.update(hid, name, None)
#
# def flush_field_cache(obj=None):
# "On-model cache resetter"
# hid = hashid(obj)
# global _FIELD_CACHE
# if hid:
# try:
# del _FIELD_CACHE[hashid(obj)]
# except KeyError, e:
# pass
# else:
# # clean cache completely
# _FIELD_CACHE = defaultdict(dict)
#
# # on-object property cache (unrelated to database)
# # Note that the get/set_prop_cache handler do not actually
# # get/set the property "on" the object but only reads the
# # value to/from the cache. This is intended to be used
# # with a get/setter property on the object.
#
# def get_prop_cache(obj, name, default=None):
# "On-model Cache handler."
# global _PROP_CACHE
# hid = hashid(obj)
# if hid:
# try:
# val = _PROP_CACHE[hid][name]
# except KeyError:
# return default
# _PROP_CACHE[hid][name] = val
# return val
# return default
#
# def set_prop_cache(obj, name, val):
# "On-model Cache setter. Also updates database."
# hid = hashid(obj)
# if hid:
# global _PROP_CACHE
# _PROP_CACHE[hid][name] = val
# # oob hook functionality
# oob_hook = _OOB_PROP_UPDATE_HOOKS[hid].get(name)
# if oob_hook:
# oob_hook[0](obj.typeclass, name, val, *oob_hook[1], **oob_hook[2])
#
#
# def del_prop_cache(obj, name):
# "On-model cache deleter"
# try:
# del _PROP_CACHE[hashid(obj)][name]
# except KeyError:
# pass
# def flush_prop_cache(obj=None):
# "On-model cache resetter"
# hid = hashid(obj)
# global _PROP_CACHE
# if hid:
# try:
# del _PROP_CACHE[hid]
# except KeyError,e:
# pass
# else:
# # clean cache completely
# _PROP_CACHE = defaultdict(dict)
#
# # attribute cache
#
# def get_attr_cache(obj, attrname):
# """
# Attribute cache store
# """
# return _ATTR_CACHE[hashid(obj)].get(attrname, None)
#
# def set_attr_cache(obj, attrname, attrobj):
# """
# Cache an attribute object
# """
# hid = hashid(obj)
# if hid:
# global _ATTR_CACHE
# _ATTR_CACHE[hid][attrname] = attrobj
# # oob hook functionality
# oob_hook = _OOB_ATTR_UPDATE_HOOKS[hid].get(attrname)
# if oob_hook:
# oob_hook[0](obj.typeclass, attrname, attrobj.value, *oob_hook[1], **oob_hook[2])
#
# def del_attr_cache(obj, attrname):
# """
# Remove attribute from cache
# """
# global _ATTR_CACHE
# try:
# _ATTR_CACHE[hashid(obj)][attrname].no_cache = True
# del _ATTR_CACHE[hashid(obj)][attrname]
# except KeyError:
# pass
#
# def flush_attr_cache(obj=None):
# """
# Flush the attribute cache for this object.
# """
# global _ATTR_CACHE
# if obj:
# for attrobj in _ATTR_CACHE[hashid(obj)].values():
# attrobj.no_cache = True
# del _ATTR_CACHE[hashid(obj)]
# else:
# # clean cache completely
# for objcache in _ATTR_CACHE.values():
# for attrobj in objcache.values():
# attrobj.no_cache = True
# _ATTR_CACHE = defaultdict(dict)
#
#
# def flush_obj_caches(obj=None):
# "Clean all caches on this object"
# flush_field_cache(obj)
# flush_prop_cache(obj)
# flush_attr_cache(obj)
#
field_n = sum(len(dic) for dic in _FIELD_CACHE.values()) #else:
field_mb = sum(sum([getsizeof(obj) for obj in dic.values()]) for dic in _FIELD_CACHE.values()) / 1024.0
prop_n = sum(len(dic) for dic in _PROP_CACHE.values())
prop_mb = sum(sum([getsizeof(obj) for obj in dic.values()]) for dic in _PROP_CACHE.values()) / 1024.0
return (attr_n, attr_mb), (field_n, field_mb), (prop_n, prop_mb)
# on-object database field cache
def get_field_cache(obj, name):
"On-model Cache handler."
global _FIELD_CACHE
hid = hashid(obj)
if hid:
try:
return _FIELD_CACHE[hid][name]
except KeyError:
val = _GA(obj, "db_%s" % name)
_FIELD_CACHE[hid][name] = val
return val
return _GA(obj, "db_%s" % name)
def set_field_cache(obj, name, val):
"On-model Cache setter. Also updates database."
_SA(obj, "db_%s" % name, val)
_GA(obj, "save")()
hid = hashid(obj)
if hid:
global _FIELD_CACHE
_FIELD_CACHE[hid][name] = val
# oob hook functionality
if _OOB_FIELD_UPDATE_HOOKS[hid].get(name):
_OOB_HANDLER.update(hid, name, val)
def del_field_cache(obj, name):
"On-model cache deleter"
hid = hashid(obj)
_SA(obj, "db_%s" % name, None)
_GA(obj, "save")()
if hid:
try:
del _FIELD_CACHE[hid][name]
except KeyError:
pass
if _OOB_FIELD_UPDATE_HOOKS[hid].get(name):
_OOB_HANDLER.update(hid, name, None)
def flush_field_cache(obj=None):
"On-model cache resetter"
hid = hashid(obj)
global _FIELD_CACHE
if hid:
del _FIELD_CACHE[hashid(obj)]
else:
# clean cache completely
_FIELD_CACHE = defaultdict(dict)
# on-object property cache (unrelated to database)
# Note that the get/set_prop_cache handler do not actually
# get/set the property "on" the object but only reads the
# value to/from the cache. This is intended to be used
# with a get/setter property on the object.
def get_prop_cache(obj, name, default=None):
"On-model Cache handler."
global _PROP_CACHE
hid = hashid(obj)
if hid:
try:
val = _PROP_CACHE[hid][name]
except KeyError:
return default
_PROP_CACHE[hid][name] = val
return val
return default
def set_prop_cache(obj, name, val):
"On-model Cache setter. Also updates database."
hid = hashid(obj)
if hid:
global _PROP_CACHE
_PROP_CACHE[hid][name] = val
# oob hook functionality
oob_hook = _OOB_PROP_UPDATE_HOOKS[hid].get(name)
if oob_hook:
oob_hook[0](obj.typeclass, name, val, *oob_hook[1], **oob_hook[2])
def del_prop_cache(obj, name):
"On-model cache deleter"
try:
del _PROP_CACHE[hashid(obj)][name]
except KeyError:
pass
def flush_prop_cache(obj=None):
"On-model cache resetter"
hid = hashid(obj)
global _PROP_CACHE
if hid:
del _PROP_CACHE[hashid(obj)]
else:
# clean cache completely
_PROP_CACHE = defaultdict(dict)
# attribute cache
def get_attr_cache(obj, attrname):
"""
Attribute cache store
"""
return _ATTR_CACHE[hashid(obj)].get(attrname, None)
def set_attr_cache(obj, attrname, attrobj):
"""
Cache an attribute object
"""
hid = hashid(obj)
if hid:
global _ATTR_CACHE
_ATTR_CACHE[hid][attrname] = attrobj
# oob hook functionality
oob_hook = _OOB_ATTR_UPDATE_HOOKS[hid].get(attrname)
if oob_hook:
oob_hook[0](obj.typeclass, attrname, attrobj.value, *oob_hook[1], **oob_hook[2])
def del_attr_cache(obj, attrname):
"""
Remove attribute from cache
"""
global _ATTR_CACHE
try:
_ATTR_CACHE[hashid(obj)][attrname].no_cache = True
del _ATTR_CACHE[hashid(obj)][attrname]
except KeyError:
pass
def flush_attr_cache(obj=None):
"""
Flush the attribute cache for this object.
"""
global _ATTR_CACHE
if obj:
for attrobj in _ATTR_CACHE[hashid(obj)].values():
attrobj.no_cache = True
del _ATTR_CACHE[hashid(obj)]
else:
# clean cache completely
for objcache in _ATTR_CACHE.values():
for attrobj in objcache.values():
attrobj.no_cache = True
_ATTR_CACHE = defaultdict(dict)
else:
# local caches disabled. Use simple pass-through replacements # local caches disabled. Use simple pass-through replacements
def get_cache_sizes(): def get_cache_sizes():
return (0, 0), (0, 0), (0, 0) return (0, 0), (0, 0), (0, 0)
def get_field_cache(obj, name): def get_field_cache(obj, name):
return _GA(obj, "db_%s" % name) return _GA(obj, "db_%s" % name)
def set_field_cache(obj, name, val): def set_field_cache(obj, name, val):
_SA(obj, "db_%s" % name, val) _SA(obj, "db_%s" % name, val)
_GA(obj, "save")() _GA(obj, "save")()
hid = hashid(obj) #hid = hashid(obj)
if _OOB_FIELD_UPDATE_HOOKS[hid].get(name): #if _OOB_FIELD_UPDATE_HOOKS[hid].get(name):
_OOB_HANDLER.update(hid, name, val) # _OOB_HANDLER.update(hid, name, val)
def del_field_cache(obj, name): def del_field_cache(obj, name):
_SA(obj, "db_%s" % name, None) _SA(obj, "db_%s" % name, None)
_GA(obj, "save")() _GA(obj, "save")()
hid = hashid(obj) #hid = hashid(obj)
if _OOB_FIELD_UPDATE_HOOKS[hid].get(name): #if _OOB_FIELD_UPDATE_HOOKS[hid].get(name):
_OOB_HANDLER.update(hid, name, None) # _OOB_HANDLER.update(hid, name, None)
def flush_field_cache(obj=None): #def flush_field_cache(obj=None):
pass # pass
# these should get oob handlers when oob is implemented. # these should get oob handlers when oob is implemented.
def get_prop_cache(obj, name, default=None): #def get_prop_cache(obj, name, default=None):
return None # return None
def set_prop_cache(obj, name, val): #def set_prop_cache(obj, name, val):
pass # pass
def del_prop_cache(obj, name): #def del_prop_cache(obj, name):
pass # pass
def flush_prop_cache(obj=None): #def flush_prop_cache(obj=None):
pass # pass
def get_attr_cache(obj, attrname): #def get_attr_cache(obj, attrname):
return None # return None
def set_attr_cache(obj, attrname, attrobj): #def set_attr_cache(obj, attrname, attrobj):
pass # pass
def del_attr_cache(obj, attrname): #def del_attr_cache(obj, attrname):
pass # passk
def flush_attr_cache(obj=None): #def flush_attr_cache(obj=None):
pass # pass

View file

View file

@ -2,7 +2,7 @@
MCCP - Mud Client Compression Protocol MCCP - Mud Client Compression Protocol
The implements the MCCP v2 telnet protocol as per This implements the MCCP v2 telnet protocol as per
http://tintin.sourceforge.net/mccp/. MCCP allows for the server to http://tintin.sourceforge.net/mccp/. MCCP allows for the server to
compress data when sending to supporting clients, reducing bandwidth compress data when sending to supporting clients, reducing bandwidth
by 70-90%.. The compression is done using Python's builtin zlib by 70-90%.. The compression is done using Python's builtin zlib

View file

@ -11,15 +11,15 @@ import sys
import os import os
if os.name == 'nt': if os.name == 'nt':
# For Windows batchfile we need an extra path insertion here. # For Windows batchfile we need an extra path insertion here.
sys.path.insert(0, os.path.dirname(os.path.dirname( sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(
os.path.dirname(os.path.abspath(__file__))))) os.path.dirname(os.path.abspath(__file__))))))
from twisted.application import internet, service from twisted.application import internet, service
from twisted.internet import protocol, reactor from twisted.internet import protocol, reactor
from twisted.web import server, static from twisted.web import server
from django.conf import settings from django.conf import settings
from src.utils.utils import get_evennia_version, mod_import, make_iter from src.utils.utils import get_evennia_version, mod_import, make_iter
from src.server.sessionhandler import PORTAL_SESSIONS from src.server.portal.portalsessionhandler import PORTAL_SESSIONS
PORTAL_SERVICES_PLUGIN_MODULES = [mod_import(module) for module in make_iter(settings.PORTAL_SERVICES_PLUGIN_MODULES)] PORTAL_SERVICES_PLUGIN_MODULES = [mod_import(module) for module in make_iter(settings.PORTAL_SERVICES_PLUGIN_MODULES)]
@ -55,7 +55,8 @@ WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
AMP_HOST = settings.AMP_HOST AMP_HOST = settings.AMP_HOST
AMP_PORT = settings.AMP_PORT AMP_PORT = settings.AMP_PORT
AMP_ENABLED = AMP_HOST and AMP_PORT AMP_INTERFACE = settings.AMP_INTERFACE
AMP_ENABLED = AMP_HOST and AMP_PORT and AMP_INTERFACE
#------------------------------------------------------------ #------------------------------------------------------------
@ -156,6 +157,8 @@ if AMP_ENABLED:
from src.server import amp from src.server import amp
print ' amp (to Server): %s' % AMP_PORT
factory = amp.AmpClientFactory(PORTAL) factory = amp.AmpClientFactory(PORTAL)
amp_client = internet.TCPClient(AMP_HOST, AMP_PORT, factory) amp_client = internet.TCPClient(AMP_HOST, AMP_PORT, factory)
amp_client.setName('evennia_amp') amp_client.setName('evennia_amp')
@ -168,7 +171,7 @@ if TELNET_ENABLED:
# Start telnet game connections # Start telnet game connections
from src.server import telnet from src.server.portal import telnet
for interface in TELNET_INTERFACES: for interface in TELNET_INTERFACES:
if ":" in interface: if ":" in interface:
@ -192,7 +195,7 @@ if SSL_ENABLED:
# Start SSL game connection (requires PyOpenSSL). # Start SSL game connection (requires PyOpenSSL).
from src.server import ssl from src.server.portal import ssl
for interface in SSL_INTERFACES: for interface in SSL_INTERFACES:
if ":" in interface: if ":" in interface:
@ -218,7 +221,7 @@ if SSH_ENABLED:
# Start SSH game connections. Will create a keypair in evennia/game if necessary. # Start SSH game connections. Will create a keypair in evennia/game if necessary.
from src.server import ssh from src.server.portal import ssh
for interface in SSH_INTERFACES: for interface in SSH_INTERFACES:
if ":" in interface: if ":" in interface:
@ -240,29 +243,9 @@ if SSH_ENABLED:
if WEBSERVER_ENABLED: if WEBSERVER_ENABLED:
# Start a django-compatible webserver. # Start a reverse proxy to relay data to the Server-side webserver
from twisted.python import threadpool from twisted.web import proxy
from src.server.webserver import DjangoWebRoot, WSGIWebServer
# start a thread pool and define the root url (/) as a wsgi resource
# recognized by Django
threads = threadpool.ThreadPool()
web_root = DjangoWebRoot(threads)
# point our media resources to url /media
web_root.putChild("media", static.File(settings.MEDIA_ROOT))
webclientstr = ""
if WEBCLIENT_ENABLED:
# create ajax client processes at /webclientdata
from src.server.webclient import WebClient
webclient = WebClient()
webclient.sessionhandler = PORTAL_SESSIONS
web_root.putChild("webclientdata", webclient)
webclientstr = "/client"
web_site = server.Site(web_root, logPath=settings.HTTP_LOG_FILE)
for interface in WEBSERVER_INTERFACES: for interface in WEBSERVER_INTERFACES:
if ":" in interface: if ":" in interface:
@ -271,14 +254,23 @@ if WEBSERVER_ENABLED:
ifacestr = "" ifacestr = ""
if interface != '0.0.0.0' or len(WEBSERVER_INTERFACES) > 1: if interface != '0.0.0.0' or len(WEBSERVER_INTERFACES) > 1:
ifacestr = "-%s" % interface ifacestr = "-%s" % interface
for port in WEBSERVER_PORTS: for proxyport, serverport in WEBSERVER_PORTS:
pstring = "%s:%s" % (ifacestr, port) pstring = "%s:%s<->%s" % (ifacestr, proxyport, serverport)
# create the webserver web_root = proxy.ReverseProxyResource('127.0.0.1', serverport, '')
webserver = WSGIWebServer(threads, port, web_site, interface=interface) webclientstr = ""
webserver.setName('EvenniaWebServer%s' % pstring) if WEBCLIENT_ENABLED:
PORTAL.services.addService(webserver) # create ajax client processes at /webclientdata
from src.server.portal.webclient import WebClient
webclient = WebClient()
webclient.sessionhandler = PORTAL_SESSIONS
web_root.putChild("webclientdata", webclient)
webclientstr = "/client"
print " webserver%s%s: %s" % (webclientstr, ifacestr, port) web_root = server.Site(web_root, logPath=settings.HTTP_LOG_FILE)
proxy_service = internet.TCPServer(proxyport, web_root, interface=interface)
proxy_service.setName('EvenniaWebProxy%s' % pstring)
PORTAL.services.addService(proxy_service)
print " webproxy%s%s:%s (<-> %s)" % (webclientstr, ifacestr, proxyport, serverport)
for plugin_module in PORTAL_SERVICES_PLUGIN_MODULES: for plugin_module in PORTAL_SERVICES_PLUGIN_MODULES:
# external plugin services to start # external plugin services to start
@ -286,7 +278,6 @@ for plugin_module in PORTAL_SERVICES_PLUGIN_MODULES:
print '-' * 50 # end of terminal output print '-' * 50 # end of terminal output
if os.name == 'nt': if os.name == 'nt':
# Windows only: Set PID file manually # Windows only: Set PID file manually
f = open(os.path.join(settings.GAME_DIR, 'portal.pid'), 'w') f = open(os.path.join(settings.GAME_DIR, 'portal.pid'), 'w')

View file

@ -0,0 +1,167 @@
"""
Sessionhandler for portal sessions
"""
import time
from src.server.sessionhandler import SessionHandler, PCONN, PDISCONN
#------------------------------------------------------------
# Portal-SessionHandler class
#------------------------------------------------------------
class PortalSessionHandler(SessionHandler):
"""
This object holds the sessions connected to the portal at any time.
It is synced with the server's equivalent SessionHandler over the AMP
connection.
Sessions register with the handler using the connect() method. This
will assign a new unique sessionid to the session and send that sessid
to the server using the AMP connection.
"""
def __init__(self):
"""
Init the handler
"""
self.portal = None
self.sessions = {}
self.latest_sessid = 0
self.uptime = time.time()
self.connection_time = 0
def at_server_connection(self):
"""
Called when the Portal establishes connection with the
Server. At this point, the AMP connection is already
established.
"""
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.
"""
self.latest_sessid += 1
sessid = self.latest_sessid
session.sessid = sessid
sessdata = session.get_sync_data()
self.sessions[sessid] = session
# sync with server-side
self.portal.amp_protocol.call_remote_ServerAdmin(sessid,
operation=PCONN,
data=sessdata)
def disconnect(self, session):
"""
Called from portal side when the connection is closed from the portal side.
"""
sessid = session.sessid
if sessid in self.sessions:
del self.sessions[sessid]
del session
# tell server to also delete this session
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)
if sessid in self.sessions:
# in case sess.disconnect doesn't delete it
del self.sessions[sessid]
del session
def server_disconnect_all(self, reason=""):
"""
Called by server when forcing a clean disconnect for everyone.
"""
for session in self.sessions.values():
session.disconnect(reason)
del session
self.sessions = {}
def server_logged_in(self, sessid, data):
"The server tells us that the session has been authenticated. Updated it."
sess = self.get_session(sessid)
sess.load_sync_data(data)
def server_session_sync(self, serversessions):
"""
Server wants to save data to the portal, maybe because it's about to shut down.
We don't overwrite any sessions here, just update them in-place and remove
any that are out of sync (which should normally not be the case)
serversessions - dictionary {sessid:{property:value},...} describing the properties
to sync on all sessions
"""
to_save = [sessid for sessid in serversessions if sessid in self.sessions]
to_delete = [sessid for sessid in self.sessions if sessid not in to_save]
# save protocols
for sessid in to_save:
self.sessions[sessid].load_sync_data(serversessions[sessid])
# disconnect out-of-sync missing protocols
for sessid in to_delete:
self.server_disconnect(sessid)
def count_loggedin(self, include_unloggedin=False):
"""
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)
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.
"""
#print "portal_data_in:", string
self.portal.amp_protocol.call_remote_MsgPortal2Server(session.sessid,
msg=string,
data=data)
def announce_all(self, message):
"""
Send message to all connection sessions
"""
for session in self.sessions.values():
session.data_out(message)
def data_out(self, sessid, string="", data=""):
"""
Called by server for having the portal relay messages and data
to the correct session protocol.
"""
session = self.sessions.get(sessid, None)
if session:
session.data_out(string, data=data)
def oob_data_in(self, session, data):
"""
OOB (Out-of-band) data Portal -> Server
"""
print "portal_oob_data_in:", data
self.portal.amp_protocol.call_remote_OOBPortal2Server(session.sessid,
data=data)
def oob_data_out(self, sessid, data):
"""
OOB (Out-of-band) data Server -> Portal
"""
print "portal_oob_data_out:", data
session = self.sessions.get(sessid, None)
if session:
session.oob_data_out(data)
PORTAL_SESSIONS = PortalSessionHandler()

View file

@ -11,7 +11,7 @@ except ImportError:
print " SSL_ENABLED requires PyOpenSSL." print " SSL_ENABLED requires PyOpenSSL."
sys.exit(5) sys.exit(5)
from src.server.telnet import TelnetProtocol from src.server.portal.telnet import TelnetProtocol
class SSLProtocol(TelnetProtocol): class SSLProtocol(TelnetProtocol):
""" """

View file

@ -10,8 +10,8 @@ sessions etc.
import re import re
from twisted.conch.telnet import Telnet, StatefulTelnetProtocol, IAC, LINEMODE from twisted.conch.telnet import Telnet, StatefulTelnetProtocol, IAC, LINEMODE
from src.server.session import Session from src.server.session import Session
from src.server import ttype, mssp from src.server.portal import ttype, mssp
from src.server.mccp import Mccp, mccp_compress, MCCP from src.server.portal.mccp import Mccp, mccp_compress, MCCP
from src.utils import utils, ansi, logger from src.utils import utils, ansi, logger
_RE_N = re.compile(r"\{n$") _RE_N = re.compile(r"\{n$")

View file

@ -63,11 +63,11 @@ class WebClient(resource.Resource):
self.requests = {} self.requests = {}
self.databuffer = {} self.databuffer = {}
def getChild(self, path, request): #def getChild(self, path, request):
""" # """
This is the place to put dynamic content. # This is the place to put dynamic content.
""" # """
return self # return self
def _responseFailed(self, failure, suid, request): def _responseFailed(self, failure, suid, request):
"callback if a request is lost/timed out" "callback if a request is lost/timed out"

View file

@ -14,7 +14,7 @@ if os.name == 'nt':
# For Windows batchfile we need an extra path insertion here. # For Windows batchfile we need an extra path insertion here.
sys.path.insert(0, os.path.dirname(os.path.dirname( sys.path.insert(0, os.path.dirname(os.path.dirname(
os.path.dirname(os.path.abspath(__file__))))) os.path.dirname(os.path.abspath(__file__)))))
from twisted.web import server, static
from twisted.application import internet, service from twisted.application import internet, service
from twisted.internet import reactor, defer from twisted.internet import reactor, defer
import django import django
@ -30,6 +30,12 @@ from src.utils.utils import get_evennia_version, mod_import, make_iter
from src.comms import channelhandler from src.comms import channelhandler
from src.server.sessionhandler import SESSIONS from src.server.sessionhandler import SESSIONS
# setting up server-side field cache
from django.db.models.signals import pre_save
from src.server.caches import field_pre_save
pre_save.connect(field_pre_save, dispatch_uid="fieldcache")
_SA = object.__setattr__ _SA = object.__setattr__
if os.name == 'nt': if os.name == 'nt':
@ -57,10 +63,15 @@ AMP_HOST = settings.AMP_HOST
AMP_PORT = settings.AMP_PORT AMP_PORT = settings.AMP_PORT
AMP_INTERFACE = settings.AMP_INTERFACE AMP_INTERFACE = settings.AMP_INTERFACE
WEBSERVER_PORTS = settings.WEBSERVER_PORTS
WEBSERVER_INTERFACES = settings.WEBSERVER_INTERFACES
# server-channel mappings # server-channel mappings
WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS and WEBSERVER_INTERFACES
IMC2_ENABLED = settings.IMC2_ENABLED IMC2_ENABLED = settings.IMC2_ENABLED
IRC_ENABLED = settings.IRC_ENABLED IRC_ENABLED = settings.IRC_ENABLED
RSS_ENABLED = settings.RSS_ENABLED RSS_ENABLED = settings.RSS_ENABLED
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
#------------------------------------------------------------ #------------------------------------------------------------
@ -325,7 +336,7 @@ if AMP_ENABLED:
ifacestr = "" ifacestr = ""
if AMP_INTERFACE != '127.0.0.1': if AMP_INTERFACE != '127.0.0.1':
ifacestr = "-%s" % AMP_INTERFACE ifacestr = "-%s" % AMP_INTERFACE
print ' amp (to Portal)%s:%s' % (ifacestr, AMP_PORT) print ' amp (to Portal)%s: %s' % (ifacestr, AMP_PORT)
from src.server import amp from src.server import amp
@ -334,6 +345,30 @@ if AMP_ENABLED:
amp_service.setName("EvenniaPortal") amp_service.setName("EvenniaPortal")
EVENNIA.services.addService(amp_service) EVENNIA.services.addService(amp_service)
if WEBSERVER_ENABLED:
# Start a django-compatible webserver.
from twisted.python import threadpool
from src.server.webserver import DjangoWebRoot, WSGIWebServer
# start a thread pool and define the root url (/) as a wsgi resource
# recognized by Django
threads = threadpool.ThreadPool(minthreads=max(1, settings.WEBSERVER_THREADPOOL_LIMITS[0]),
maxthreads=max(1, settings.WEBSERVER_THREADPOOL_LIMITS[1]))
web_root = DjangoWebRoot(threads)
# point our media resources to url /media
web_root.putChild("media", static.File(settings.MEDIA_ROOT))
web_site = server.Site(web_root, logPath=settings.HTTP_LOG_FILE)
for proxyport, serverport in WEBSERVER_PORTS:
# create the webserver (we only need the port for this)
webserver = WSGIWebServer(threads, serverport, web_site, interface='127.0.0.1')
webserver.setName('EvenniaWebServer%s' % serverport)
EVENNIA.services.addService(webserver)
print " webserver: %s" % serverport
if IRC_ENABLED: if IRC_ENABLED:
# IRC channel connections # IRC channel connections

View file

@ -380,167 +380,4 @@ class ServerSessionHandler(SessionHandler):
""" """
self.server.amp_protocol.call_remote_OOBServer2Portal(session.sessid, self.server.amp_protocol.call_remote_OOBServer2Portal(session.sessid,
data=data) data=data)
#------------------------------------------------------------
# Portal-SessionHandler class
#------------------------------------------------------------
class PortalSessionHandler(SessionHandler):
"""
This object holds the sessions connected to the portal at any time.
It is synced with the server's equivalent SessionHandler over the AMP
connection.
Sessions register with the handler using the connect() method. This
will assign a new unique sessionid to the session and send that sessid
to the server using the AMP connection.
"""
def __init__(self):
"""
Init the handler
"""
self.portal = None
self.sessions = {}
self.latest_sessid = 0
self.uptime = time.time()
self.connection_time = 0
def at_server_connection(self):
"""
Called when the Portal establishes connection with the
Server. At this point, the AMP connection is already
established.
"""
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.
"""
self.latest_sessid += 1
sessid = self.latest_sessid
session.sessid = sessid
sessdata = session.get_sync_data()
self.sessions[sessid] = session
# sync with server-side
self.portal.amp_protocol.call_remote_ServerAdmin(sessid,
operation=PCONN,
data=sessdata)
def disconnect(self, session):
"""
Called from portal side when the connection is closed from the portal side.
"""
sessid = session.sessid
if sessid in self.sessions:
del self.sessions[sessid]
del session
# tell server to also delete this session
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)
if sessid in self.sessions:
# in case sess.disconnect doesn't delete it
del self.sessions[sessid]
del session
def server_disconnect_all(self, reason=""):
"""
Called by server when forcing a clean disconnect for everyone.
"""
for session in self.sessions.values():
session.disconnect(reason)
del session
self.sessions = {}
def server_logged_in(self, sessid, data):
"The server tells us that the session has been authenticated. Updated it."
sess = self.get_session(sessid)
sess.load_sync_data(data)
def server_session_sync(self, serversessions):
"""
Server wants to save data to the portal, maybe because it's about to shut down.
We don't overwrite any sessions here, just update them in-place and remove
any that are out of sync (which should normally not be the case)
serversessions - dictionary {sessid:{property:value},...} describing the properties
to sync on all sessions
"""
to_save = [sessid for sessid in serversessions if sessid in self.sessions]
to_delete = [sessid for sessid in self.sessions if sessid not in to_save]
# save protocols
for sessid in to_save:
self.sessions[sessid].load_sync_data(serversessions[sessid])
# disconnect out-of-sync missing protocols
for sessid in to_delete:
self.server_disconnect(sessid)
def count_loggedin(self, include_unloggedin=False):
"""
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)
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.
"""
#print "portal_data_in:", string
self.portal.amp_protocol.call_remote_MsgPortal2Server(session.sessid,
msg=string,
data=data)
def announce_all(self, message):
"""
Send message to all connection sessions
"""
for session in self.sessions.values():
session.data_out(message)
def data_out(self, sessid, string="", data=""):
"""
Called by server for having the portal relay messages and data
to the correct session protocol.
"""
session = self.sessions.get(sessid, None)
if session:
session.data_out(string, data=data)
def oob_data_in(self, session, data):
"""
OOB (Out-of-band) data Portal -> Server
"""
print "portal_oob_data_in:", data
self.portal.amp_protocol.call_remote_OOBPortal2Server(session.sessid,
data=data)
def oob_data_out(self, sessid, data):
"""
OOB (Out-of-band) data Server -> Portal
"""
print "portal_oob_data_out:", data
session = self.sessions.get(sessid, None)
if session:
session.oob_data_out(data)
SESSIONS = ServerSessionHandler() SESSIONS = ServerSessionHandler()
PORTAL_SESSIONS = PortalSessionHandler()

View file

@ -44,13 +44,21 @@ WEBSERVER_ENABLED = True
# attacks. It defaults to allowing all. In production, make # attacks. It defaults to allowing all. In production, make
# sure to change this to your actual host addresses/IPs. # sure to change this to your actual host addresses/IPs.
ALLOWED_HOSTS = ["*"] ALLOWED_HOSTS = ["*"]
# A list of ports the Evennia webserver listens on # The webserver sits behind a Portal proxy. This is a list
WEBSERVER_PORTS = [8000] # of tuples (proxyport,serverport) used. The proxyports are what
# the Portal proxy presents to the world. The serverports are
# the internal ports the proxy uses to forward data to the Server-side
# webserver (these should not be publicly open)
WEBSERVER_PORTS = [(8000, 5001)]
# Interface addresses to listen to. If 0.0.0.0, listen to all. # Interface addresses to listen to. If 0.0.0.0, listen to all.
WEBSERVER_INTERFACES = ['0.0.0.0'] WEBSERVER_INTERFACES = ['0.0.0.0']
# IP addresses that may talk to the server in a reverse proxy configuration, # IP addresses that may talk to the server in a reverse proxy configuration,
# like NginX. # like NginX.
UPSTREAM_IPS = ['127.0.0.1'] UPSTREAM_IPS = ['127.0.0.1']
# The webserver uses threadpool for handling requests. This will scale
# with server load. Set the minimum and maximum number of threads it
# may use as (min, max) (must be > 0)
WEBSERVER_THREADPOOL_LIMITS = (1, 20)
# Start the evennia ajax client on /webclient # Start the evennia ajax client on /webclient
# (the webserver must also be running) # (the webserver must also be running)
WEBCLIENT_ENABLED = True WEBCLIENT_ENABLED = True
@ -152,14 +160,23 @@ DATABASES = {
'HOST':'', 'HOST':'',
'PORT':'' 'PORT':''
}} }}
# Engine Config style for Django versions < 1.2 only. See above. # This manages the object-level caches. Evennia will agressively cache
DATABASE_ENGINE = 'sqlite3' # fields, properties and attribute lookup. Evennia uses a fast and
DATABASE_NAME = os.path.join(GAME_DIR, 'evennia.db3') # local in-memory cache by default. If a Memcached server is available
DATABASE_USER = '' # it can be used instead (see django docs). Cache performance can be
DATABASE_PASSWORD = '' # tweaked by adding options to each cache. Finally, any cache can
DATABASE_HOST = '' # be completely turned off by pointing its backend
DATABASE_PORT = '' # to 'django.core.cache.backends.dummy.DummyCache'.
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'},
'field_cache': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'},
'prop_cache': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'},
'attr_cache': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'},
}
###################################################################### ######################################################################
# Evennia pluggable modules # Evennia pluggable modules
###################################################################### ######################################################################

View file

@ -38,11 +38,12 @@ from django.db import models, IntegrityError
from django.conf import settings from django.conf import settings
from django.utils.encoding import smart_str from django.utils.encoding import smart_str
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db.models.fields import AutoField, FieldDoesNotExist
from src.utils.idmapper.models import SharedMemoryModel from src.utils.idmapper.models import SharedMemoryModel
from src.server.caches import get_field_cache, set_field_cache, del_field_cache from src.server.caches import get_field_cache, set_field_cache, del_field_cache
from src.server.caches import get_attr_cache, set_attr_cache, del_attr_cache from src.server.caches import get_attr_cache, set_attr_cache
from src.server.caches import get_prop_cache, set_prop_cache, del_prop_cache, flush_attr_cache from src.server.caches import get_prop_cache, set_prop_cache, del_prop_cache, flush_attr_cache
from src.server.caches import call_ndb_hooks #from src.server.caches import call_ndb_hooks
from src.server.models import ServerConfig from src.server.models import ServerConfig
from src.typeclasses import managers from src.typeclasses import managers
from src.locks.lockhandler import LockHandler from src.locks.lockhandler import LockHandler
@ -59,8 +60,6 @@ _CTYPEGET = ContentType.objects.get
_GA = object.__getattribute__ _GA = object.__getattribute__
_SA = object.__setattr__ _SA = object.__setattr__
_DA = object.__delattr__ _DA = object.__delattr__
#_PLOADS = pickle.loads
#_PDUMPS = pickle.dumps
#------------------------------------------------------------ #------------------------------------------------------------
# #
@ -102,7 +101,7 @@ class Attribute(SharedMemoryModel):
# Attribute Database Model setup # Attribute Database Model setup
# #
# #
# These databse fields are all set using their corresponding properties, # These database fields are all set using their corresponding properties,
# named same as the field, but withtout the db_* prefix. # named same as the field, but withtout the db_* prefix.
db_key = models.CharField('key', max_length=255, db_index=True) db_key = models.CharField('key', max_length=255, db_index=True)
@ -111,7 +110,7 @@ class Attribute(SharedMemoryModel):
# Lock storage # Lock storage
db_lock_storage = models.TextField('locks', blank=True) db_lock_storage = models.TextField('locks', blank=True)
# references the object the attribute is linked to (this is set # references the object the attribute is linked to (this is set
# by each child class to this abstact class) # by each child class to this abstract class)
db_obj = None # models.ForeignKey("RefencedObject") db_obj = None # models.ForeignKey("RefencedObject")
# time stamp # time stamp
db_date_created = models.DateTimeField('date_created', editable=False, auto_now_add=True) db_date_created = models.DateTimeField('date_created', editable=False, auto_now_add=True)
@ -455,53 +454,52 @@ class TypedObject(SharedMemoryModel):
# value = self.attr and del self.attr respectively (where self # value = self.attr and del self.attr respectively (where self
# is the object in question). # is the object in question).
# key property (wraps db_key) # key property (wraps db_key)
#@property #@property
def __key_get(self): #def __key_get(self):
"Getter. Allows for value = self.key" # "Getter. Allows for value = self.key"
return get_field_cache(self, "key") # return get_field_cache(self, "key")
#@key.setter ##@key.setter
def __key_set(self, value): #def __key_set(self, value):
"Setter. Allows for self.key = value" # "Setter. Allows for self.key = value"
set_field_cache(self, "key", value) # set_field_cache(self, "key", value)
#@key.deleter ##@key.deleter
def __key_del(self): #def __key_del(self):
"Deleter. Allows for del self.key" # "Deleter. Allows for del self.key"
raise Exception("Cannot delete objectdb key!") # raise Exception("Cannot delete objectdb key!")
key = property(__key_get, __key_set, __key_del) #key = property(__key_get, __key_set, __key_del)
# name property (wraps db_key too - alias to self.key) # name property (wraps db_key too - alias to self.key)
#@property #@property
def __name_get(self): def __name_get(self):
"Getter. Allows for value = self.name" "Getter. Allows for value = self.name"
return get_field_cache(self, "key") return self.key
#@name.setter #@name.sette
def __name_set(self, value): def __name_set(self, value):
"Setter. Allows for self.name = value" "Setter. Allows for self.name = value"
set_field_cache(self, "key", value) self.key = value
#@name.deleter #@name.deleter
def __name_del(self): def __name_del(self):
"Deleter. Allows for del self.name" "Deleter. Allows for del self.name"
raise Exception("Cannot delete name!") raise Exception("Cannot delete name!")
name = property(__name_get, __name_set, __name_del) name = property(__name_get, __name_set, __name_del)
# typeclass_path property # typeclass_path property - we don't cache this.
#@property #@property
def __typeclass_path_get(self): def __typeclass_path_get(self):
"Getter. Allows for value = self.typeclass_path" "Getter. Allows for value = self.typeclass_path"
return get_field_cache(self, "typeclass_path") return _GA(self, "db_typeclass_path")#get_field_cache(self, "typeclass_path")
#@typeclass_path.setter #@typeclass_path.setter
def __typeclass_path_set(self, value): def __typeclass_path_set(self, value):
"Setter. Allows for self.typeclass_path = value" "Setter. Allows for self.typeclass_path = value"
set_field_cache(self, "typeclass_path", value) _SA(self, "db_typeclass_path", value)
_SA(self, "_cached_typeclass", None) _GA(self, "save")(update_fields=["db_typeclass_path"])
#@typeclass_path.deleter #@typeclass_path.deleter
def __typeclass_path_del(self): def __typeclass_path_del(self):
"Deleter. Allows for del self.typeclass_path" "Deleter. Allows for del self.typeclass_path"
self.db_typeclass_path = "" self.db_typeclass_path = ""
self.save() _GA(self, "save")(update_fields=["db_typeclass_path"])
del_field_cache(self, "typeclass_path")
_SA(self, "_cached_typeclass", None)
typeclass_path = property(__typeclass_path_get, __typeclass_path_set, __typeclass_path_del) typeclass_path = property(__typeclass_path_get, __typeclass_path_set, __typeclass_path_del)
# date_created property # date_created property
@ -932,7 +930,7 @@ class TypedObject(SharedMemoryModel):
attrib_obj = _GA(self, "_attribute_class").objects.filter( attrib_obj = _GA(self, "_attribute_class").objects.filter(
db_obj=self, db_key__iexact=attribute_name) db_obj=self, db_key__iexact=attribute_name)
if attrib_obj: if attrib_obj:
set_attr_cache(self, attribute_name, attrib_obj[0]) set_attr_cache(attrib_obj[0])
else: else:
return False return False
return True return True
@ -959,8 +957,9 @@ class TypedObject(SharedMemoryModel):
if attrib_obj: if attrib_obj:
# use old attribute # use old attribute
attrib_obj = attrib_obj[0] attrib_obj = attrib_obj[0]
set_attr_cache(attrib_obj) # renew cache
else: else:
# no match; create new attribute # no match; create new attribute (this will cache automatically)
attrib_obj = attrclass(db_key=attribute_name, db_obj=self) attrib_obj = attrclass(db_key=attribute_name, db_obj=self)
if lockstring: if lockstring:
attrib_obj.locks.add(lockstring) attrib_obj.locks.add(lockstring)
@ -973,7 +972,6 @@ class TypedObject(SharedMemoryModel):
flush_attr_cache(self) flush_attr_cache(self)
self.delete() self.delete()
raise IntegrityError("Attribute could not be saved - object %s was deleted from database." % self.key) raise IntegrityError("Attribute could not be saved - object %s was deleted from database." % self.key)
set_attr_cache(self, attribute_name, attrib_obj)
def get_attribute_obj(self, attribute_name, default=None): def get_attribute_obj(self, attribute_name, default=None):
""" """
@ -985,7 +983,7 @@ class TypedObject(SharedMemoryModel):
db_obj=self, db_key__iexact=attribute_name) db_obj=self, db_key__iexact=attribute_name)
if not attrib_obj: if not attrib_obj:
return default return default
set_attr_cache(self, attribute_name, attrib_obj[0]) #query is first evaluated here set_attr_cache(attrib_obj[0]) #query is first evaluated here
return attrib_obj[0] return attrib_obj[0]
return attrib_obj return attrib_obj
@ -1004,7 +1002,7 @@ class TypedObject(SharedMemoryModel):
db_obj=self, db_key__iexact=attribute_name) db_obj=self, db_key__iexact=attribute_name)
if not attrib_obj: if not attrib_obj:
return default return default
set_attr_cache(self, attribute_name, attrib_obj[0]) #query is first evaluated here set_attr_cache(attrib_obj[0]) #query is first evaluated here
return attrib_obj[0].value return attrib_obj[0].value
return attrib_obj.value return attrib_obj.value
@ -1021,7 +1019,7 @@ class TypedObject(SharedMemoryModel):
db_obj=self, db_key__iexact=attribute_name) db_obj=self, db_key__iexact=attribute_name)
if not attrib_obj: if not attrib_obj:
raise AttributeError raise AttributeError
set_attr_cache(self, attribute_name, attrib_obj[0]) #query is first evaluated here set_attr_cache(attrib_obj[0]) #query is first evaluated here
return attrib_obj[0].value return attrib_obj[0].value
return attrib_obj.value return attrib_obj.value
@ -1033,8 +1031,7 @@ class TypedObject(SharedMemoryModel):
""" """
attr_obj = get_attr_cache(self, attribute_name) attr_obj = get_attr_cache(self, attribute_name)
if attr_obj: if attr_obj:
del_attr_cache(self, attribute_name) attr_obj.delete() # this will clear attr cache automatically
attr_obj.delete()
else: else:
try: try:
_GA(self, "_attribute_class").objects.filter( _GA(self, "_attribute_class").objects.filter(
@ -1051,8 +1048,7 @@ class TypedObject(SharedMemoryModel):
""" """
attr_obj = get_attr_cache(self, attribute_name) attr_obj = get_attr_cache(self, attribute_name)
if attr_obj: if attr_obj:
del_attr_cache(self, attribute_name) attr_obj.delete() # this will clear attr cache automatically
attr_obj.delete()
else: else:
try: try:
_GA(self, "_attribute_class").objects.filter( _GA(self, "_attribute_class").objects.filter(
@ -1244,7 +1240,7 @@ class TypedObject(SharedMemoryModel):
return None return None
def __setattr__(self, key, value): def __setattr__(self, key, value):
# hook the oob handler here # hook the oob handler here
call_ndb_hooks(self, key, value) #call_ndb_hooks(self, key, value)
_SA(self, key, value) _SA(self, key, value)
self._ndb_holder = NdbHolder() self._ndb_holder = NdbHolder()
return self._ndb_holder return self._ndb_holder

View file

@ -28,13 +28,10 @@ except ImportError:
from django.db import transaction from django.db import transaction
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from src.server.models import ServerConfig
from src.utils.utils import to_str, uses_database from src.utils.utils import to_str, uses_database
from src.utils import logger from src.utils import logger
__all__ = ("to_pickle", "from_pickle", "do_pickle", "do_unpickle") __all__ = ("to_pickle", "from_pickle", "do_pickle", "do_unpickle")
PICKLE_PROTOCOL = 2 PICKLE_PROTOCOL = 2
@ -47,13 +44,21 @@ _FROM_MODEL_MAP = None
_TO_MODEL_MAP = None _TO_MODEL_MAP = None
_TO_TYPECLASS = lambda o: hasattr(o, 'typeclass') and o.typeclass or o _TO_TYPECLASS = lambda o: hasattr(o, 'typeclass') and o.typeclass or o
_IS_PACKED_DBOBJ = lambda o: type(o) == tuple and len(o) == 4 and o[0] == '__packed_dbobj__' _IS_PACKED_DBOBJ = lambda o: type(o) == tuple and len(o) == 4 and o[0] == '__packed_dbobj__'
_TO_DATESTRING = lambda o: _GA(o, "db_date_created").strftime("%Y:%m:%d-%H:%M:%S:%f") if uses_database("mysql") and ServerConfig.objects.get_mysql_db_version() < '5.6.4':
if uses_database("mysql"): # mysql <5.6.4 don't support millisecond precision
from src.server.models import ServerConfig _DATESTRING = "%Y:%m:%d-%H:%M:%S:000000"
mysql_version = ServerConfig.objects.get_mysql_db_version() else:
if mysql_version < '5.6.4': _DATESTRING = "%Y:%m:%d-%H:%M:%S:%f"
# mysql <5.6.4 don't support millisecond precision
_TO_DATESTRING = lambda o: _GA(o, "db_date_created").strftime("%Y:%m:%d-%H:%M:%S:000000") def _TO_DATESTRING(obj):
"this will only be called with valid database objects. Returns datestring on correct form."
try:
return _GA(obj, "db_date_created").strftime(_DATESTRING)
except AttributeError:
# this can happen if object is not yet saved - no datestring is then set
obj.save()
return _GA(obj, "db_date_created").strftime(_DATESTRING)
def _init_globals(): def _init_globals():
"Lazy importing to avoid circular import issues" "Lazy importing to avoid circular import issues"

View file

@ -7,13 +7,20 @@ leave caching unexpectedly (no use if WeakRefs).
Also adds cache_size() for monitoring the size of the cache. Also adds cache_size() for monitoring the size of the cache.
""" """
import os import os, threading
#from twisted.internet import reactor
#from twisted.internet.threads import blockingCallFromThread
from twisted.internet.reactor import callFromThread
from django.db.models.base import Model, ModelBase from django.db.models.base import Model, ModelBase
from django.db.models.signals import post_save, pre_delete, \ from django.db.models.signals import post_save, pre_delete, post_syncdb
post_syncdb
from manager import SharedMemoryManager from manager import SharedMemoryManager
_GA = object.__getattribute__
_SA = object.__setattr__
_DA = object.__delattr__
# determine if our current pid is different from the server PID (i.e. # determine if our current pid is different from the server PID (i.e.
# if we are in a subprocess or not) # if we are in a subprocess or not)
from src import PROC_MODIFIED_OBJS from src import PROC_MODIFIED_OBJS
@ -37,11 +44,19 @@ def _get_pids():
if server_pid and portal_pid: if server_pid and portal_pid:
return int(server_pid), int(portal_pid) return int(server_pid), int(portal_pid)
return None, None return None, None
_SELF_PID = os.getpid()
_SERVER_PID = None
_PORTAL_PID = None
_IS_SUBPROCESS = False
# get info about the current process and thread
_SELF_PID = os.getpid()
_SERVER_PID, _PORTAL_PID = _get_pids()
_IS_SUBPROCESS = (_SERVER_PID and _PORTAL_PID) and not _SELF_PID in (_SERVER_PID, _PORTAL_PID)
_IS_MAIN_THREAD = threading.currentThread().getName() == "MainThread"
#_SERVER_PID = None
#_PORTAL_PID = None
# #global _SERVER_PID, _PORTAL_PID, _IS_SUBPROCESS, _SELF_PID
# if not _SERVER_PID and not _PORTAL_PID:
# _IS_SUBPROCESS = (_SERVER_PID and _PORTAL_PID) and not _SELF_PID in (_SERVER_PID, _PORTAL_PID)
class SharedMemoryModelBase(ModelBase): class SharedMemoryModelBase(ModelBase):
# CL: upstream had a __new__ method that skipped ModelBase's __new__ if # CL: upstream had a __new__ method that skipped ModelBase's __new__ if
@ -68,13 +83,44 @@ class SharedMemoryModelBase(ModelBase):
if cached_instance is None: if cached_instance is None:
cached_instance = new_instance() cached_instance = new_instance()
cls.cache_instance(cached_instance) cls.cache_instance(cached_instance)
return cached_instance return cached_instance
def _prepare(cls): def _prepare(cls):
cls.__instance_cache__ = {} #WeakValueDictionary() cls.__instance_cache__ = {} #WeakValueDictionary()
super(SharedMemoryModelBase, cls)._prepare() super(SharedMemoryModelBase, cls)._prepare()
def __init__(cls, *args, **kwargs):
"""
Takes field names db_* and creates property wrappers named without the db_ prefix. So db_key -> key
This wrapper happens on the class level, so there is no overhead when creating objects. If a class
already has a wrapper of the given name, the automatic creation is skipped. Note: Remember to
document this auto-wrapping in the class header, this could seem very much like magic to the user otherwise.
"""
super(SharedMemoryModelBase, cls).__init__(*args, **kwargs)
def create_wrapper(cls, fieldname, wrappername):
"Helper method to create property wrappers with unique names (must be in separate call)"
def _get(cls, fname):
return _GA(cls, fname)
def _set(cls, fname, value):
_SA(cls, fname, value)
_GA(cls, "save")(update_fields=[fname]) # important!
def _del(cls, fname):
raise RuntimeError("You cannot delete field %s on %s; set it to None instead." % (fname, cls))
type(cls).__setattr__(cls, wrappername, property(lambda cls: _get(cls, fieldname),
lambda cls,val: _set(cls, fieldname, val),
lambda cls: _del(cls, fieldname)))
# eclude some models that should not auto-create wrapper fields
if cls.__name__ in ("ServerConfig", "TypeNick"):
return
# dynamically create the properties
for field in cls._meta.fields:
fieldname = field.name
wrappername = fieldname == "id" and "dbid" or fieldname.replace("db_", "")
if not hasattr(cls, wrappername):
# make sure not to overload manually created wrappers on the model
#print "wrapping %s -> %s" % (fieldname, wrappername)
create_wrapper(cls, fieldname, wrappername)
class SharedMemoryModel(Model): class SharedMemoryModel(Model):
# CL: setting abstract correctly to allow subclasses to inherit the default # CL: setting abstract correctly to allow subclasses to inherit the default
@ -116,6 +162,13 @@ class SharedMemoryModel(Model):
return result return result
_get_cache_key = classmethod(_get_cache_key) _get_cache_key = classmethod(_get_cache_key)
def _flush_cached_by_key(cls, key):
try:
del cls.__instance_cache__[key]
except KeyError:
pass
_flush_cached_by_key = classmethod(_flush_cached_by_key)
def get_cached_instance(cls, id): def get_cached_instance(cls, id):
""" """
Method to retrieve a cached instance by pk value. Returns None when not found Method to retrieve a cached instance by pk value. Returns None when not found
@ -138,13 +191,6 @@ class SharedMemoryModel(Model):
return cls.__instance_cache__.values() return cls.__instance_cache__.values()
get_all_cached_instances = classmethod(get_all_cached_instances) get_all_cached_instances = classmethod(get_all_cached_instances)
def _flush_cached_by_key(cls, key):
try:
del cls.__instance_cache__[key]
except KeyError:
pass
_flush_cached_by_key = classmethod(_flush_cached_by_key)
def flush_cached_instance(cls, instance): def flush_cached_instance(cls, instance):
""" """
Method to flush an instance from the cache. The instance will always be flushed from the cache, Method to flush an instance from the cache. The instance will always be flushed from the cache,
@ -158,15 +204,22 @@ class SharedMemoryModel(Model):
flush_instance_cache = classmethod(flush_instance_cache) flush_instance_cache = classmethod(flush_instance_cache)
def save(cls, *args, **kwargs): def save(cls, *args, **kwargs):
"overload spot for saving" "save method tracking process/thread issues"
global _SERVER_PID, _PORTAL_PID, _IS_SUBPROCESS, _SELF_PID
if not _SERVER_PID and not _PORTAL_PID:
_SERVER_PID, _PORTAL_PID = _get_pids()
_IS_SUBPROCESS = (_SERVER_PID and _PORTAL_PID) and (_SERVER_PID != _SELF_PID) and (_PORTAL_PID != _SELF_PID)
if _IS_SUBPROCESS: if _IS_SUBPROCESS:
#print "storing in PROC_MODIFIED_OBJS:", cls.db_key, cls.id # we keep a store of objects modified in subprocesses so
# we know to update their caches in the central process
PROC_MODIFIED_OBJS.append(cls) PROC_MODIFIED_OBJS.append(cls)
super(SharedMemoryModel, cls).save(*args, **kwargs)
if _IS_MAIN_THREAD:
# in main thread - normal operation
super(SharedMemoryModel, cls).save(*args, **kwargs)
else:
# in another thread; make sure to save in reactor thread
def _save_callback(cls, *args, **kwargs):
super(SharedMemoryModel, cls).save(*args, **kwargs)
#blockingCallFromThread(reactor, _save_callback, cls, *args, **kwargs)
callFromThread(_save_callback, cls, *args, **kwargs)
# Use a signal so we make sure to catch cascades. # Use a signal so we make sure to catch cascades.
def flush_cache(**kwargs): def flush_cache(**kwargs):

View file

@ -467,9 +467,9 @@ def delay(delay=2, retval=None, callback=None):
""" """
Delay the return of a value. Delay the return of a value.
Inputs: Inputs:
to_return (any) - this will be returned by this function after a delay
delay (int) - the delay in seconds delay (int) - the delay in seconds
callback (func(r)) - if given, this will be called with the to_return after delay seconds retval (any) - this will be returned by this function after a delay
callback (func(retval)) - if given, this will be called with retval after delay seconds
Returns: Returns:
deferred that will fire with to_return after delay seconds deferred that will fire with to_return after delay seconds
""" """

View file

@ -1,9 +1,9 @@
""" """
This structures the url tree for the news application. This structures the url tree for the news application.
It is imported from the root handler, game.web.urls.py. It is imported from the root handler, game.web.urls.py.
""" """
from django.conf.urls.defaults import * from django.conf.urls import *
urlpatterns = patterns('src.web.news.views', urlpatterns = patterns('src.web.news.views',
(r'^show/(?P<entry_id>\d+)/$', 'show_news'), (r'^show/(?P<entry_id>\d+)/$', 'show_news'),

View file

@ -6,7 +6,7 @@
# http://diveintopython.org/regular_expressions/street_addresses.html#re.matching.2.3 # http://diveintopython.org/regular_expressions/street_addresses.html#re.matching.2.3
# #
from django.conf.urls.defaults import * from django.conf.urls import *
from django.conf import settings from django.conf import settings
from django.contrib import admin from django.contrib import admin
from django.views.generic import RedirectView from django.views.generic import RedirectView
@ -17,12 +17,12 @@ from django.db.models.loading import cache as model_cache
if not model_cache.loaded: if not model_cache.loaded:
model_cache.get_models() model_cache.get_models()
# loop over all settings.INSTALLED_APPS and execute code in # loop over all settings.INSTALLED_APPS and execute code in
# files named admin.py in each such app (this will add those # files named admin.py in each such app (this will add those
# models to the admin site) # models to the admin site)
admin.autodiscover() admin.autodiscover()
# Setup the root url tree from / # Setup the root url tree from /
urlpatterns = patterns('', urlpatterns = patterns('',
# User Authentication # User Authentication
@ -36,11 +36,11 @@ urlpatterns = patterns('',
# Page place-holder for things that aren't implemented yet. # Page place-holder for things that aren't implemented yet.
url(r'^tbi/', 'src.web.website.views.to_be_implemented'), url(r'^tbi/', 'src.web.website.views.to_be_implemented'),
# Admin interface # Admin interface
url(r'^admin/doc/', include('django.contrib.admindocs.urls')), url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', include(admin.site.urls)),
# favicon # favicon
url(r'^favicon\.ico$', RedirectView.as_view(url='/media/images/favicon.ico')), url(r'^favicon\.ico$', RedirectView.as_view(url='/media/images/favicon.ico')),

View file

@ -1,6 +1,6 @@
""" """
This structures the (simple) structure of the This structures the (simple) structure of the
webpage 'application'. webpage 'application'.
""" """
from django.conf.urls import * from django.conf.urls import *

View file

@ -1,19 +1,25 @@
""" """
This contains a simple view for rendering the webclient This contains a simple view for rendering the webclient
page and serve it eventual static content. page and serve it eventual static content.
""" """
from django.shortcuts import render_to_response from django.shortcuts import render_to_response, redirect
from django.template import RequestContext from django.template import RequestContext
from django.conf import settings from django.conf import settings
from src.server.sessionhandler import SESSIONS from src.server.sessionhandler import SESSIONS
def webclient(request): def webclient(request):
""" """
Webclient page template loading. Webclient page template loading.
""" """
# analyze request to find which port we are on
if int(request.META["SERVER_PORT"]) == 8000:
# we relay webclient to the portal port
print "Called from port 8000!"
#return redirect("http://localhost:8001/webclient/", permanent=True)
# as an example we send the number of connected players to the template # as an example we send the number of connected players to the template
pagevars = {'num_players_connected': SESSIONS.player_count()} pagevars = {'num_players_connected': SESSIONS.player_count()}

View file

@ -1,9 +1,9 @@
""" """
This structures the (simple) structure of the This structures the (simple) structure of the
webpage 'application'. webpage 'application'.
""" """
from django.conf.urls.defaults import * from django.conf.urls import *
urlpatterns = patterns('src.web.website.views', urlpatterns = patterns('src.web.website.views',
(r'^$', 'page_index'), (r'^$', 'page_index'),