diff --git a/src/commands/default/player.py b/src/commands/default/player.py index b50c3639e7..073cb5a40d 100644 --- a/src/commands/default/player.py +++ b/src/commands/default/player.py @@ -118,15 +118,16 @@ class CmdOOCLook(MuxPlayerCommand): MAX_NR_CHARACTERS > 1 and " (%i/%i)" % (len(characters), MAX_NR_CHARACTERS) or "") for char in characters: - csessid = char.sessid + csessid = char.sessid.get() if csessid: # character is already puppeted - sess = player.get_session(csessid) - sid = sess in sessions and sessions.index(sess) + 1 - if sess and sid: - string += "\n - {G%s{n [%s] (played by you in session %i)" % (char.key, ", ".join(char.permissions.all()), sid) - else: - string += "\n - {R%s{n [%s] (played by someone else)" % (char.key, ", ".join(char.permissions.all())) + sessi = player.get_session(csessid) + for sess in utils.make_iter(sessi): + sid = sess in sessions and sessions.index(sess) + 1 + if sess and sid: + string += "\n - {G%s{n [%s] (played by you in session %i)" % (char.key, ", ".join(char.permissions.all()), sid) + else: + string += "\n - {R%s{n [%s] (played by someone else)" % (char.key, ", ".join(char.permissions.all())) else: # character is "free to puppet" string += "\n - %s [%s]" % (char.key, ", ".join(char.permissions.all())) diff --git a/src/objects/migrations/0026_auto__chg_field_objectdb_db_sessid__chg_field_objectdb_db_player__chg_.py b/src/objects/migrations/0026_auto__chg_field_objectdb_db_sessid__chg_field_objectdb_db_player__chg_.py new file mode 100644 index 0000000000..58a640dde4 --- /dev/null +++ b/src/objects/migrations/0026_auto__chg_field_objectdb_db_sessid__chg_field_objectdb_db_player__chg_.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Changing field 'ObjectDB.db_sessid' + db.alter_column(u'objects_objectdb', 'db_sessid', self.gf('django.db.models.fields.CommaSeparatedIntegerField')(max_length=32, null=True)) + + # Changing field 'ObjectDB.db_player' + db.alter_column(u'objects_objectdb', 'db_player_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['players.PlayerDB'], null=True, on_delete=models.SET_NULL)) + + # Changing field 'ObjectDB.db_destination' + db.alter_column(u'objects_objectdb', 'db_destination_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, on_delete=models.SET_NULL, to=orm['objects.ObjectDB'])) + + # Changing field 'ObjectDB.db_home' + db.alter_column(u'objects_objectdb', 'db_home_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, on_delete=models.SET_NULL, to=orm['objects.ObjectDB'])) + + # Changing field 'ObjectDB.db_location' + db.alter_column(u'objects_objectdb', 'db_location_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, on_delete=models.SET_NULL, to=orm['objects.ObjectDB'])) + + def backwards(self, orm): + + # Changing field 'ObjectDB.db_sessid' + db.alter_column(u'objects_objectdb', 'db_sessid', self.gf('django.db.models.fields.IntegerField')(null=True)) + + # Changing field 'ObjectDB.db_player' + db.alter_column(u'objects_objectdb', 'db_player_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['players.PlayerDB'], null=True)) + + # Changing field 'ObjectDB.db_destination' + db.alter_column(u'objects_objectdb', 'db_destination_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, to=orm['objects.ObjectDB'])) + + # Changing field 'ObjectDB.db_home' + db.alter_column(u'objects_objectdb', 'db_home_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, to=orm['objects.ObjectDB'])) + + # Changing field 'ObjectDB.db_location' + db.alter_column(u'objects_objectdb', 'db_location_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, to=orm['objects.ObjectDB'])) + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'objects.objectdb': { + 'Meta': {'object_name': 'ObjectDB'}, + 'db_attributes': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['typeclasses.Attribute']", 'null': 'True', 'symmetrical': 'False'}), + 'db_cmdset_storage': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'db_destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destinations_set'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['objects.ObjectDB']"}), + 'db_home': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'homes_set'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['objects.ObjectDB']"}), + 'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'db_location': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'locations_set'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['objects.ObjectDB']"}), + 'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'db_player': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['players.PlayerDB']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'db_sessid': ('django.db.models.fields.CommaSeparatedIntegerField', [], {'max_length': '32', 'null': 'True'}), + 'db_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['typeclasses.Tag']", 'null': 'True', 'symmetrical': 'False'}), + 'db_typeclass_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + u'players.playerdb': { + 'Meta': {'object_name': 'PlayerDB'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'db_attributes': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['typeclasses.Attribute']", 'null': 'True', 'symmetrical': 'False'}), + 'db_cmdset_storage': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'db_is_bot': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'db_is_connected': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'db_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['typeclasses.Tag']", 'null': 'True', 'symmetrical': 'False'}), + 'db_typeclass_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'typeclasses.attribute': { + 'Meta': {'object_name': 'Attribute'}, + 'db_attrtype': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '16', 'null': 'True', 'blank': 'True'}), + 'db_category': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'db_model': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'db_strvalue': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'db_value': ('src.utils.picklefield.PickledObjectField', [], {'null': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + u'typeclasses.tag': { + 'Meta': {'unique_together': "(('db_key', 'db_category', 'db_tagtype'),)", 'object_name': 'Tag', 'index_together': "(('db_key', 'db_category', 'db_tagtype'),)"}, + 'db_category': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'db_index': 'True'}), + 'db_data': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'db_index': 'True'}), + 'db_model': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'db_index': 'True'}), + 'db_tagtype': ('django.db.models.fields.CharField', [], {'max_length': '16', 'null': 'True', 'db_index': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + } + } + + complete_apps = ['objects'] diff --git a/src/objects/models.py b/src/objects/models.py index 9aeb8ea658..aa3ba6cbf2 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -41,6 +41,54 @@ _GA = object.__getattribute__ _SA = object.__setattr__ _DA = object.__delattr__ +# the sessid_max is based on the length of the db_sessid csv field (excluding commas) +_SESSID_MAX = 16 if settings.MULTISESSION_MODE > 2 else 1 + +class SessidHandler(object): + """ + Handles the get/setting of the sessid + comma-separated integer field + """ + def __init__(self, obj): + self.obj = obj + self._cache = set() + self._recache() + + def _recache(self): + self._cache = set(int(val) for val in (_GA(self.obj, "db_sessid") or "").split(",") if val) + + def get(self): + "Returns a single integer or a list" + return self._cache if _SESSID_MAX > 1 else self._cache[0] if self._cache else None + + def add(self, sessid): + "Add sessid to handler" + _cache = self._cache + if len(_cache) >= _SESSID_MAX: + return False + _cache.add(int(sessid)) + _SA(self.obj, "db_sessid", ",".join(str(val) for val in _cache)) + _GA(self.obj, "save")(update_fields=["db_sessid"]) + return True + + def remove(self, sessid): + "Remove sessid from handler" + _cache = self._cache + if sessid in _cache: + _cache.remove(sessid) + _SA(self.obj, "db_sessid", ",".join(str(val) for val in _cache)) + _GA(self.obj, "save")(update_fields=["db_sessid"]) + + def clear(self): + "Clear sessids" + self._cache = set() + _SA(self.obj, "db_sessid", None) + _GA(self.obj, "save")(update_fields=["db_sessid"]) + + def count(self): + "Return amount of sessions connected" + return len(self._cache) + #------------------------------------------------------------ # @@ -101,11 +149,11 @@ class ObjectDB(TypedObject): # will automatically save and cache the data more efficiently. # If this is a character object, the player is connected here. - db_player = models.ForeignKey("players.PlayerDB", blank=True, null=True, verbose_name='player', on_delete=models.SET_NULL, + db_player = models.ForeignKey("players.PlayerDB", null=True, verbose_name='player', on_delete=models.SET_NULL, help_text='a Player connected to this object, if any.') # the session id associated with this player, if any - db_sessid = models.IntegerField(null=True, verbose_name="session id", - help_text="unique session id of connected Player, if any.") + db_sessid = models.CommaSeparatedIntegerField(null=True, max_length=32, verbose_name="session id", + help_text="csv list of session ids of connected Player, if any.") # The location in the game world. Since this one is likely # to change often, we set this with the 'location' property # to transparently handle Typeclassing. @@ -142,6 +190,10 @@ class ObjectDB(TypedObject): def nicks(self): return NickHandler(self) + @lazy_property + def sessid(self): + return SessidHandler(self) + def _at_db_player_postsave(self): """ This hook is called automatically after the player field is saved. @@ -224,7 +276,6 @@ class ObjectDB(TypedObject): _GA(_GA(self, "dbobj"), "save")(upate_fields=["db_location"]) location = property(__location_get, __location_set, __location_del) - class Meta: "Define Django meta options" verbose_name = "Object" @@ -500,8 +551,8 @@ class ObjectDB(TypedObject): except Exception: logger.log_trace() - session = _SESSIONS.session_from_sessid(sessid if sessid else _GA(self, "sessid")) - if session: + sessions = _SESSIONS.session_from_sessid([sessid] if sessid else make_iter(_GA(self, "sessid").get())) + for session in sessions: session.msg(text=text, **kwargs) def msg_contents(self, message, exclude=None, from_obj=None, **kwargs): diff --git a/src/players/models.py b/src/players/models.py index 23d2b5363e..dcf920ed8d 100644 --- a/src/players/models.py +++ b/src/players/models.py @@ -247,7 +247,7 @@ class PlayerDB(TypedObject, AbstractUser): _GA(from_obj, "at_msg_send")(text=text, to_obj=_GA(self, "typeclass"), **kwargs) except Exception: pass - session = _MULTISESSION_MODE == 2 and sessid and _GA(self, "get_session")(sessid) or None + session = _MULTISESSION_MODE > 1 and sessid and _GA(self, "get_session")(sessid) or None if session: obj = session.puppet if obj and not obj.at_msg_receive(text=text, **kwargs): @@ -323,7 +323,7 @@ class PlayerDB(TypedObject, AbstractUser): if normal_mode: _GA(obj.typeclass, "at_pre_puppet")(_GA(self, "typeclass"), sessid=sessid) # do the connection - obj.sessid = sessid + obj.sessid.add(sessid) obj.player = self session.puid = obj.id session.puppet = obj @@ -349,8 +349,9 @@ class PlayerDB(TypedObject, AbstractUser): return False # do the disconnect _GA(obj.typeclass, "at_pre_unpuppet")() - del obj.dbobj.sessid - del obj.dbobj.player + obj.dbobj.sessid.remove(sessid) + if not obj.dbobj.sessid.count(): + del obj.dbobj.player session.puppet = None session.puid = None _GA(obj.typeclass, "at_post_unpuppet")(_GA(self, "typeclass"), sessid=sessid) diff --git a/src/server/sessionhandler.py b/src/server/sessionhandler.py index 2dc3edd152..db91160376 100644 --- a/src/server/sessionhandler.py +++ b/src/server/sessionhandler.py @@ -15,7 +15,7 @@ There are two similar but separate stores of sessions: import time from django.conf import settings from src.commands.cmdhandler import CMD_LOGINSTART -from src.utils.utils import variable_from_module +from src.utils.utils import variable_from_module, is_iter try: import cPickle as pickle except ImportError: @@ -416,12 +416,18 @@ class ServerSessionHandler(SessionHandler): """ Return session based on sessid, or None if not found """ + if is_iter(sessid): + return [self.sessions.get(sid) for sid in sessid if sid in self.sessions] return self.sessions.get(sessid) def session_from_player(self, player, sessid): """ Given a player and a session id, return the actual session object """ + if is_iter(sessid): + sessions = [self.sessions.get(sid) for sid in sessid] + s = [sess for sess in sessions if sess and sess.logged_in and player.uid == sess.uid] + return s session = self.sessions.get(sessid) return session and session.logged_in and player.uid == session.uid and session or None