evennia/src/comms/managers.py

519 lines
18 KiB
Python

"""
These managers handles the
"""
from django.db import models
from django.db.models import Q
from src.typeclasses.managers import returns_typeclass_list, returns_typeclass
_GA = object.__getattribute__
_PlayerDB = None
_ObjectDB = None
_ChannelDB = None
_SESSIONS = None
_ExternalConnection = None
# error class
class CommError(Exception):
"Raise by comm system, to allow feedback to player when caught."
pass
#
# helper functions
#
def dbref(dbref, reqhash=True):
"""
Valid forms of dbref (database reference number)
are either a string '#N' or an integer N.
Output is the integer part.
"""
if reqhash and not (isinstance(dbref, basestring) and dbref.startswith("#")):
return None
if isinstance(dbref, basestring):
dbref = dbref.lstrip('#')
try:
if int(dbref) < 0:
return None
except Exception:
return None
return dbref
def identify_object(inp):
"identify if an object is a player or an object; return its database model"
# load global stores
global _PlayerDB, _ObjectDB, _ChannelDB, _ExternalConnection
if not _PlayerDB:
from src.players.models import PlayerDB as _PlayerDB
if not _ObjectDB:
from src.objects.models import ObjectDB as _ObjectDB
if not _ChannelDB:
from src.comms.models import ChannelDB as _ChannelDB
if not _ExternalConnection:
from src.comms.models import ExternalChannelConnection as _ExternalConnection
if not inp:
return inp, None
# try to identify the type
try:
obj = _GA(inp, "dbobj") # this works for all typeclassed entities
except AttributeError:
obj = inp
typ = type(obj)
if typ == _PlayerDB:
return obj, "player"
elif typ == _ObjectDB:
return obj, "object"
elif typ == _ChannelDB:
return obj, "channel"
elif dbref(obj):
return dbref(obj), "dbref"
elif typ == basestring:
return obj, "string"
elif typ == _ExternalConnection:
return obj, "external"
return obj, None # Something else
def to_object(inp, objtype='player'):
"""
Locates the object related to the given
playername or channel key. If input was already
the correct object, return it.
inp - the input object/string
objtype - 'player' or 'channel'
"""
obj, typ = identify_object(inp)
if typ == objtype:
return obj
if objtype == 'player':
if typ == 'object':
return obj.player
if typ == 'string':
return _PlayerDB.objects.get(user_username__iexact=obj)
if typ == 'dbref':
return _PlayerDB.objects.get(id=obj)
print objtype, inp, obj, typ, type(inp)
raise CommError()
elif objtype == 'object':
if typ == 'player':
return obj.obj
if typ == 'string':
return _ObjectDB.objects.get(db_key__iexact=obj)
if typ == 'dbref':
return _ObjectDB.objects.get(id=obj)
print objtype, inp, obj, typ, type(inp)
raise CommError()
elif objtype == 'channel':
if typ == 'string':
return _ChannelDB.objects.get(db_key__iexact=obj)
if typ == 'dbref':
return _ChannelDB.objects.get(id=obj)
print objtype, inp, obj, typ, type(inp)
raise CommError()
elif objtype == 'external':
if typ == 'string':
return _ExternalConnection.objects.get(db_key=inp)
if typ == 'dbref':
return _ExternalConnection.objects.get(id=obj)
print objtype, inp, obj, typ, type(inp)
raise CommError()
#
# Msg manager
#
class MsgManager(models.Manager):
"""
This MsgManager implements methods for searching
and manipulating Messages directly from the database.
These methods will all return database objects
(or QuerySets) directly.
A Message represents one unit of communication, be it over a
Channel or via some form of in-game mail system. Like an e-mail,
it always has a sender and can have any number of receivers (some
of which may be Channels).
Evennia-specific:
get_message_by_id
get_messages_by_sender
get_messages_by_receiver
get_messages_by_channel
text_search
message_search (equivalent to ev.search_messages)
"""
def identify_object(self, obj):
"method version for easy access"
return identify_object(obj)
def get_message_by_id(self, idnum):
"Retrieve message by its id."
try:
return self.get(id=self.dbref(idnum, reqhash=False))
except Exception:
return None
def get_messages_by_sender(self, obj, exclude_channel_messages=False):
"""
Get all messages sent by one entity - this could be either a
player or an object
only_non_channel: only return messages -not- aimed at a channel
(e.g. private tells)
"""
obj, typ = identify_object(obj)
if exclude_channel_messages:
# explicitly exclude channel recipients
if typ == 'player':
return list(self.filter(db_sender_players=obj,
db_receivers_channels__isnull=True).exclude(db_hide_from_players=obj))
elif typ == 'object':
return list(self.filter(db_sender_objects=obj,
db_receivers_channels__isnull=True).exclude(db_hide_from_objects=obj))
else:
raise CommError
else:
# get everything, channel or not
if typ == 'player':
return list(self.filter(db_sender_players=obj).exclude(db_hide_from_players=obj))
elif typ == 'object':
return list(self.filter(db_sender_objects=obj).exclude(db_hide_from_objects=obj))
else:
raise CommError
def get_messages_by_receiver(self, obj):
"""
Get all messages sent to one give recipient
"""
obj, typ = identify_object(obj)
if typ == 'player':
return list(self.filter(db_receivers_players=obj).exclude(db_hide_from_players=obj))
elif typ == 'object':
return list(self.filter(db_receivers_objects=obj).exclude(db_hide_from_objects=obj))
elif typ == 'channel':
return list(self.filter(db_receivers_channels=obj).exclude(db_hide_from_channels=obj))
else:
raise CommError
def get_messages_by_channel(self, channel):
"""
Get all messages sent to one channel
"""
return self.filter(db_receivers_channels=channel).exclude(db_hide_from_channels=channel)
def message_search(self, sender=None, receiver=None, freetext=None, dbref=None):
"""
Search the message database for particular messages. At least one
of the arguments must be given to do a search.
sender - get messages sent by a particular player or object
receiver - get messages received by a certain player,object or channel
freetext - Search for a text string in a message.
NOTE: This can potentially be slow, so make sure to supply
one of the other arguments to limit the search.
dbref - (int) the exact database id of the message. This will override
all other search criteria since it's unique and
always gives a list with only one match.
"""
# unique msg id
if dbref:
msg = self.objects.filter(id=dbref)
if msg:
return msg[0]
# We use Q objects to gradually build up the query - this way we only
# need to do one database lookup at the end rather than gradually
# refining with multiple filter:s. Django Note: Q objects can be
# combined with & and | (=AND,OR). ~ negates the queryset
# filter by sender
sender, styp = identify_object(sender)
if styp == 'player':
sender_restrict = Q(db_sender_players=sender) & ~Q(db_hide_from_players=sender)
elif styp == 'object':
sender_restrict = Q(db_sender_objects=sender) & ~Q(db_hide_from_objects=sender)
else:
sender_restrict = Q()
# filter by receiver
receiver, rtyp = identify_object(receiver)
if rtyp == 'player':
receiver_restrict = Q(db_receivers_players=receiver) & ~Q(db_hide_from_players=receiver)
elif rtyp == 'object':
receiver_restrict = Q(db_receivers_objects=receiver) & ~Q(db_hide_from_objects=receiver)
elif rtyp == 'channel':
receiver_restrict = Q(db_receivers_channels=receiver) & ~Q(db_hide_from_channels=receiver)
else:
receiver_restrict = Q()
# filter by full text
if freetext:
fulltext_restrict = Q(db_header__icontains=freetext) | Q(db_message__icontains=freetext)
else:
fulltext_restrict = Q()
# execute the query
return list(self.filter(sender_restrict & receiver_restrict & fulltext_restrict))
#
# Channel manager
#
class ChannelManager(models.Manager):
"""
This ChannelManager implements methods for searching
and manipulating Channels directly from the database.
These methods will all return database objects
(or QuerySets) directly.
A Channel is an in-game venue for communication. It's
essentially representation of a re-sender: Users sends
Messages to the Channel, and the Channel re-sends those
messages to all users subscribed to the Channel.
Evennia-specific:
get_all_channels
get_channel(channel)
get_subscriptions(player)
channel_search (equivalent to ev.search_channel)
"""
@returns_typeclass_list
def get_all_channels(self):
"""
Returns all channels in game.
"""
return self.all()
@returns_typeclass
def get_channel(self, channelkey):
"""
Return the channel object if given its key.
Also searches its aliases.
"""
# first check the channel key
channels = self.filter(db_key__iexact=channelkey)
if not channels:
# also check aliases
channels = [channel for channel in self.all()
if channelkey in channel.aliases.all()]
if channels:
return channels[0]
return None
@returns_typeclass_list
def get_subscriptions(self, player):
"""
Return all channels a given player is subscribed to
"""
return player.dbobj.subscription_set.all()
# def del_channel(self, channelkey):
# """
# Delete channel matching channelkey.
# Also cleans up channelhandler.
# """
# channels = self.filter(db_key__iexact=channelkey)
# if not channels:
# # no aliases allowed for deletion.
# return False
# for channel in channels:
# channel.delete()
# from src.comms.channelhandler import CHANNELHANDLER
# CHANNELHANDLER.update()
# return None
# def get_all_connections(self, channel, online=False):
# """
# Return the connections of all players listening
# to this channel. If Online is true, it only returns
# connected players.
# """
# global _SESSIONS
# if not _SESSIONS:
# from src.server.sessionhandler import SESSIONS as _SESSIONS
#
# PlayerChannelConnection = ContentType.objects.get(app_label="comms",
# model="playerchannelconnection").model_class()
# ExternalChannelConnection = ContentType.objects.get(app_label="comms",
# model="externalchannelconnection").model_class()
# players = []
# if online:
# session_list = _SESSIONS.get_sessions()
# unique_online_users = set(sess.uid for sess in session_list if sess.logged_in)
# online_players = (sess.get_player() for sess in session_list if sess.uid in unique_online_users)
# for player in online_players:
# players.extend(PlayerChannelConnection.objects.filter(
# db_player=player.dbobj, db_channel=channel.dbobj))
# else:
# players.extend(PlayerChannelConnection.objects.get_all_connections(channel))
#
# external_connections = ExternalChannelConnection.objects.get_all_connections(channel)
#
# return itertools.chain(players, external_connections)
@returns_typeclass_list
def channel_search(self, ostring, exact=True):
"""
Search the channel database for a particular channel.
ostring - the key or database id of the channel.
exact - require an exact key match (still not case sensitive)
"""
channels = []
if not ostring: return channels
try:
# try an id match first
dbref = int(ostring.strip('#'))
channels = self.filter(id=dbref)
except Exception:
pass
if not channels:
# no id match. Search on the key.
if exact:
channels = self.filter(db_key__iexact=ostring)
else:
channels = self.filter(db_key__icontains=ostring)
if not channels:
# still no match. Search by alias.
channels = [channel for channel in self.all()
if ostring.lower() in [a.lower
for a in channel.aliases.all()]]
return channels
#
# PlayerChannelConnection manager
#
class PlayerChannelConnectionManager(models.Manager):
"""
This PlayerChannelConnectionManager implements methods for searching
and manipulating PlayerChannelConnections directly from the database.
These methods will all return database objects
(or QuerySets) directly.
A PlayerChannelConnection defines a user's subscription to an in-game
channel - deleting the connection object will disconnect the player
from the channel.
Evennia-specific:
get_all_player_connections
has_connection
get_all_connections
create_connection
break_connection
"""
@returns_typeclass_list
def get_all_player_connections(self, player):
"Get all connections that the given player has."
player = to_object(player)
return self.filter(db_player=player)
def has_player_connection(self, player, channel):
"Checks so a connection exists player<->channel"
if player and channel:
return self.filter(db_player=player.dbobj).filter(
db_channel=channel.dbobj).count() > 0
return False
def get_all_connections(self, channel):
"""
Get all connections for a channel
"""
channel = to_object(channel, objtype='channel')
return self.filter(db_channel=channel)
def create_connection(self, player, channel):
"""
Connect a player to a channel. player and channel
can be actual objects or keystrings.
"""
player = to_object(player)
channel = to_object(channel, objtype='channel')
if not player or not channel:
raise CommError("NOTFOUND")
new_connection = self.model(db_player=player, db_channel=channel)
new_connection.save()
return new_connection
def break_connection(self, player, channel):
"Remove link between player and channel"
player = to_object(player)
channel = to_object(channel, objtype='channel')
if not player or not channel:
raise CommError("NOTFOUND")
conns = self.filter(db_player=player).filter(db_channel=channel)
for conn in conns:
conn.delete()
class ExternalChannelConnectionManager(models.Manager):
"""
This ExternalChannelConnectionManager implements methods for searching
and manipulating HelpEntries directly from the database.
These methods will all return database objects
(or QuerySets) directly.
An ExternalChannelConnetion describes the connection between an in-game
channel and some external source, such as an IRC or IMC channel.
Evennia-specific:
get_all_external_connections
has_connection
get_all_connections
create_connection
break_connection
"""
def get_all_external_connections(self, external):
"Get all connections that the given as external."
external = to_object(external, objtype='external')
return self.filter(db_external_key=external)
def has_connection(self, external, channel):
"Checks so a connection exists external<->channel"
external = to_object(external, objtype='external')
channel = to_object(channel, objtype="channel")
if external and channel:
return self.filter(db_external_key=external).filter(db_channel=channel).count() > 0
return False
def get_all_connections(self, channel):
"""
Get all connections for a channel
"""
channel = to_object(channel, objtype='channel')
return self.filter(db_channel=channel)
def create_connection(self, external, channel, config=""):
"""
Connect a external to a channel. external and channel
can be actual objects or keystrings.
"""
channel = to_object(channel, objtype='channel')
if not channel:
raise CommError("NOTFOUND")
new_connection = self.model(db_external_key=external, db_channel=channel, db_external_config=config)
new_connection.save()
return new_connection
def break_connection(self, external, channel):
"Remove link between external and channel"
external = to_object(external)
channel = to_object(channel, objtype='channel')
if not external or not channel:
raise CommError("NOTFOUND")
conns = self.filter(db_external_key=external).filter(db_channel=channel)
for conn in conns:
conn.delete()