merged branch frontends_multi_profiles

This commit is contained in:
Goffi 2015-03-18 10:52:28 +01:00
commit 6760c927d3
29 changed files with 4611 additions and 4514 deletions

View file

@ -230,9 +230,9 @@ class DbusObject(dbus.service.Object):
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='ss', out_signature='',
async_callbacks=None)
def delContact(self, entity_jid, profile_key="@DEFAULT@"):
return self._callback("delContact", unicode(entity_jid), unicode(profile_key))
async_callbacks=('callback', 'errback'))
def delContact(self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None):
return self._callback("delContact", unicode(entity_jid), unicode(profile_key), callback=callback, errback=errback)
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='ss', out_signature='(asa(sss))',
@ -260,9 +260,9 @@ class DbusObject(dbus.service.Object):
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='s', out_signature='a(sa{ss}as)',
async_callbacks=None)
def getContacts(self, profile_key="@DEFAULT@"):
return self._callback("getContacts", unicode(profile_key))
async_callbacks=('callback', 'errback'))
def getContacts(self, profile_key="@DEFAULT@", callback=None, errback=None):
return self._callback("getContacts", unicode(profile_key), callback=callback, errback=errback)
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='ss', out_signature='as',
@ -270,6 +270,12 @@ class DbusObject(dbus.service.Object):
def getContactsFromGroup(self, group, profile_key="@DEFAULT@"):
return self._callback("getContactsFromGroup", unicode(group), unicode(profile_key))
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='asass', out_signature='a{sa{ss}}',
async_callbacks=None)
def getEntitiesData(self, jids, keys, profile):
return self._callback("getEntitiesData", jids, keys, unicode(profile))
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='sass', out_signature='a{ss}',
async_callbacks=None)
@ -285,8 +291,8 @@ class DbusObject(dbus.service.Object):
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='ss', out_signature='s',
async_callbacks=None)
def getLastResource(self, contact_jid, profile_key="@DEFAULT@"):
return self._callback("getLastResource", unicode(contact_jid), unicode(profile_key))
def getMainResource(self, contact_jid, profile_key="@DEFAULT@"):
return self._callback("getMainResource", unicode(contact_jid), unicode(profile_key))
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='ss', out_signature='s',
@ -295,7 +301,7 @@ class DbusObject(dbus.service.Object):
return self._callback("getMenuHelp", unicode(menu_id), unicode(language))
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='si', out_signature='a(ssasas)',
in_signature='si', out_signature='a(ssasasa{ss})',
async_callbacks=None)
def getMenus(self, language, security_limit):
return self._callback("getMenus", unicode(language), security_limit)
@ -434,9 +440,9 @@ class DbusObject(dbus.service.Object):
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='ssass', out_signature='',
async_callbacks=None)
def updateContact(self, entity_jid, name, groups, profile_key="@DEFAULT@"):
return self._callback("updateContact", unicode(entity_jid), unicode(name), groups, unicode(profile_key))
async_callbacks=('callback', 'errback'))
def updateContact(self, entity_jid, name, groups, profile_key="@DEFAULT@", callback=None, errback=None):
return self._callback("updateContact", unicode(entity_jid), unicode(name), groups, unicode(profile_key), callback=callback, errback=errback)
def __attributes(self, in_sign):
"""Return arguments to user given a in_sign

View file

@ -446,17 +446,35 @@ class DbusConstructor(Constructor):
'args': self.getArguments(function['sig_in'], name=arg_doc, default=default)}
if function["type"] == "method":
# XXX: we can manage blocking call in the same way as async one: if callback is None the call will be blocking
completion['debug'] = "" if not self.options.debug else 'log.debug ("%s")\n%s' % (section, 8 * ' ')
completion['args_result'] = self.getArguments(function['sig_in'], name=arg_doc)
completion['async_args'] = 'callback=None, errback=None' if async else ''
completion['async_comma'] = ', ' if async and function['sig_in'] else ''
completion['error_handler'] = ("error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))\n" + 8*" ") if async else ''
completion['async_args_result'] = 'timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler' if async else ''
completion['async_args'] = 'callback=None, errback=None'
completion['async_comma'] = ', ' if function['sig_in'] else ''
completion['error_handler'] = """if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
"""
if async:
completion['blocking_call'] = ''
completion['async_args_result'] = 'timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler'
else:
# XXX: To have a blocking call, we must have not reply_handler, so we test if callback exists, and add reply_handler only in this case
completion['blocking_call'] = """kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
"""
completion['async_args_result'] = '**kwargs'
result = "self.db_%(category)s_iface.%(name)s(%(args_result)s%(async_comma)s%(async_args_result)s)" % completion
completion['result'] = ("unicode(%s)" if self.options.unicode and function['sig_out'] == 's' else "%s") % result
methods_part.append("""\
def %(name)s(self, %(args)s%(async_comma)s%(async_args)s):
%(error_handler)s%(debug)sreturn %(result)s
%(error_handler)s%(blocking_call)s%(debug)sreturn %(result)s
""" % completion)
#at this point, methods_part should be filled,

View file

@ -184,12 +184,24 @@ type=method
category=core
sig_in=sass
sig_out=a{ss}
doc=Get data for an entity
doc=Get data in cache for an entity
doc_param_0=jid: entity's bare jid
doc_param_1=keys: list of keys to get
doc_param_2=%(doc_profile)s
doc_return=dictionary of asked key,
if key doesn't exist, the resulting dictionary will neither have the key
if key doesn't exist, the resulting dictionary will not have the key
[getEntitiesData]
type=method
category=core
sig_in=asass
sig_out=a{sa{ss}}
doc=Get data in cache for several entities at once
doc_param_0=jids: list of entities bare jid, or empty list to have all jids in cache
doc_param_1=keys: list of keys to get
doc_param_2=%(doc_profile)s
doc_return=dictionary with jids as keys and dictionary of asked key as values
if key doesn't exist for a jid, the resulting dictionary will not have it
[asyncCreateProfile]
async=
@ -255,12 +267,13 @@ doc=Tell if a profile is connected
doc_param_0=%(doc_profile_key)s
[getContacts]
async=
type=method
category=core
sig_in=s
sig_out=a(sa{ss}as)
param_0_default="@DEFAULT@"
doc=Return information about all contacts
doc=Return information about all contacts (the roster)
doc_param_0=%(doc_profile_key)s
doc_return=array of tuples with the following values:
- JID of the contact
@ -278,7 +291,7 @@ doc_param_0=group: name of the group to check
doc_param_1=%(doc_profile_key)s
doc_return=array of jids
[getLastResource]
[getMainResource]
type=method
category=core
sig_in=ss
@ -287,7 +300,7 @@ param_1_default="@DEFAULT@"
doc=Return the last resource connected for a contact
doc_param_0=contact_jid: jid of the contact
doc_param_1=%(doc_profile_key)s
doc_return=the last resource connected of the contact, or ""
doc_return=the resource connected of the contact with highest priority, or ""
[getPresenceStatuses]
type=method
@ -508,6 +521,7 @@ doc_param_0=entity_jid: JID to add to roster
doc_param_1=%(doc_profile_key)s
[updateContact]
async=
type=method
category=core
sig_in=ssass
@ -520,6 +534,7 @@ doc_param_2=groups: list of group where the entity is
doc_param_3=%(doc_profile_key)s
[delContact]
async=
type=method
category=core
sig_in=ss
@ -571,7 +586,7 @@ doc_return=dict with progress information:
type=method
category=core
sig_in=si
sig_out=a(ssasas)
sig_out=a(ssasasa{ss})
doc=Get all additional menus
doc_param_0=language: language in which the menu should be translated (empty string for default)
doc_param_1=security_limit: %(doc_security_limit)s
@ -581,6 +596,7 @@ doc_return=list of tuple with the following value:
* NORMAL: Classical application menu
- menu_path: raw path of the menu
- menu_path_i18n: translated path of the menu
- extra: extra data, like icon name
[getMenuHelp]
type=method

View file

@ -102,10 +102,10 @@ class DBusBridgeFrontend(BridgeFrontend):
async = False
if kwargs:
if 'callback' in kwargs and 'errback' in kwargs:
if 'callback' in kwargs:
async = True
_callback = kwargs.pop('callback')
_errback = kwargs.pop('errback')
_errback = kwargs.pop('errback', lambda failure: log.error(unicode(failure)))
elif len(args) >= 2 and callable(args[-1]) and callable(args[-2]):
async = True
args = list(args)
@ -122,4 +122,5 @@ class DBusBridgeFrontend(BridgeFrontend):
return method(*args, **kwargs)
return getPluginMethod
##METHODS_PART##

View file

@ -70,18 +70,36 @@ class Const(object):
MENU_JID_CONTEXT = "JID_CONTEXT"
MENU_ROSTER_JID_CONTEXT = "ROSTER_JID_CONTEXT"
MENU_ROSTER_GROUP_CONTEXT = "MENU_ROSTER_GROUP_CONTEXT"
MENU_ROOM_OCCUPANT_CONTEXT = "MENU_ROOM_OCCUPANT_CONTEXT"
## Profile and entities ##
PROF_KEY_NONE = '@NONE@'
PROF_KEY_DEFAULT = '@DEFAULT@'
ENTITY_ALL = '@ALL@'
ENTITY_LAST_RESOURCE = 'LAST_RESOURCE'
ENTITY_ALL_RESOURCES = '@ALL_RESOURCES@'
ENTITY_MAIN_RESOURCE = '@MAIN_RESOURCE@'
ENTITY_CAP_HASH = 'CAP_HASH'
## Messages ##
MESS_TYPE_INFO = 'info'
MESS_TYPE_CHAT = 'chat'
MESS_TYPE_ERROR = 'error'
MESS_TYPE_GROUPCHAT = 'groupchat'
MESS_TYPE_HEADLINE = 'headline'
MESS_TYPE_NORMAL = 'normal'
## PRESENCE ##
PRESENCE_UNAVAILABLE = 'unavailable'
PRESENCE_SHOW_AWAY = 'away'
PRESENCE_SHOW_CHAT = 'chat'
PRESENCE_SHOW_DND = 'dnd'
PRESENCE_SHOW_XA = 'xa'
PRESENCE_SHOW = 'show'
PRESENCE_STATUSES = 'statuses'
PRESENCE_PRIORITY = 'priority'
## Configuration ##
if BaseDirectory: # skipped when xdg module is not available (should not happen in backend)
@ -202,14 +220,12 @@ class Const(object):
@classmethod
def bool(cls, value):
"""@return: bool value for any type"""
if isinstance(value, str) or isinstance(value, unicode): # dbus.String is unicode but not str
return value.lower() == cls.BOOL_TRUE
return bool(value)
"""@return (bool): bool value for associated constant"""
assert isinstance(value, basestring)
return value.lower() == cls.BOOL_TRUE
@classmethod
def str(cls, value):
"""@return: str text value for any type"""
if isinstance(value, bool):
return cls.BOOL_TRUE if value else cls.BOOL_FALSE
return str(value)
def boolConst(cls, value):
"""@return (str): constant associated to bool value"""
assert isinstance(value, bool)
return cls.BOOL_TRUE if value else cls.BOOL_FALSE

View file

@ -93,5 +93,10 @@ class DatabaseError(Exception):
class PasswordError(Exception):
pass
class PermissionError(Exception):
pass
class SkipHistory(Exception): # used in MessageReceivedTrigger to avoid history writting
pass

View file

@ -83,14 +83,15 @@ class SAT(service.Service):
self.bridge.register("getVersion", lambda: C.APP_VERSION)
self.bridge.register("getProfileName", self.memory.getProfileName)
self.bridge.register("getProfilesList", self.memory.getProfilesList)
self.bridge.register("getEntityData", lambda _jid, keys, profile: self.memory.getEntityData(jid.JID(_jid), keys, profile))
self.bridge.register("getEntityData", lambda jid_, keys, profile: self.memory.getEntityData(jid.JID(jid_), keys, profile))
self.bridge.register("getEntitiesData", self.memory._getEntitiesData)
self.bridge.register("asyncCreateProfile", self.memory.asyncCreateProfile)
self.bridge.register("asyncDeleteProfile", self.memory.asyncDeleteProfile)
self.bridge.register("asyncConnect", self.asyncConnect)
self.bridge.register("disconnect", self.disconnect)
self.bridge.register("getContacts", self.getContacts)
self.bridge.register("getContactsFromGroup", self.getContactsFromGroup)
self.bridge.register("getLastResource", self.memory._getLastResource)
self.bridge.register("getMainResource", self.memory._getMainResource)
self.bridge.register("getPresenceStatuses", self.memory._getPresenceStatuses)
self.bridge.register("getWaitingSub", self.memory.getWaitingSub)
self.bridge.register("getWaitingConf", self.getWaitingConf)
@ -264,9 +265,13 @@ class SAT(service.Service):
for plugin in self.plugins.iteritems():
if plugin[1].is_handler:
plugin[1].getHandler(profile).setHandlerParent(current)
connected_cb = getattr(plugin[1], "profileConnected", None)
connected_cb = getattr(plugin[1], "profileConnected", None) # profile connected is called after client is ready and roster is got
if connected_cb:
plugin_conn_cb.append((plugin[0], connected_cb))
try:
yield plugin[1].profileConnecting(profile) # profile connecting is called before actually starting client
except AttributeError:
pass
current.startService()
@ -289,7 +294,7 @@ class SAT(service.Service):
{'name': plugin_conn_cb[idx][0], 'failure': result})
yield list_d.addCallback(logPluginResults) # FIXME: we should have a timeout here, and a way to know if a plugin freeze
# TODO: mesure time to launch of each plugin
# TODO: mesure launch time of each plugin
def _authenticateProfile(self, password, profile):
"""Authenticate the profile.
@ -331,12 +336,15 @@ class SAT(service.Service):
def getContacts(self, profile_key):
client = self.getClient(profile_key)
ret = []
for item in client.roster.getItems(): # we get all items for client's roster
# and convert them to expected format
attr = client.roster.getAttributes(item)
ret.append([item.jid.userhost(), attr, item.groups])
return ret
def got_roster(dummy):
ret = []
for item in client.roster.getItems(): # we get all items for client's roster
# and convert them to expected format
attr = client.roster.getAttributes(item)
ret.append([item.jid.userhost(), attr, item.groups])
return ret
return client.roster.got_roster.addCallback(got_roster)
def getContactsFromGroup(self, group, profile_key):
client = self.getClient(profile_key)
@ -456,7 +464,7 @@ class SAT(service.Service):
def sendMessage(self, to_jid, msg, subject=None, mess_type='auto', extra={}, no_trigger=False, profile_key=C.PROF_KEY_NONE):
#FIXME: check validity of recipient
profile = self.memory.getProfileName(profile_key)
assert(profile)
assert profile
client = self.profiles[profile]
if extra is None:
extra = {}
@ -479,7 +487,7 @@ class SAT(service.Service):
# we may have a groupchat message, we check if the we know this jid
try:
entity_type = self.memory.getEntityData(mess_data["to"], ['type'], profile)["type"]
#FIXME: should entity_type manage ressources ?
#FIXME: should entity_type manage resources ?
except (exceptions.UnknownEntityError, KeyError):
entity_type = "contact"
@ -497,7 +505,7 @@ class SAT(service.Service):
if not self.trigger.point("sendMessage", mess_data, pre_xml_treatments, post_xml_treatments, profile):
return defer.succeed(None)
log.debug(_("Sending jabber message of type [%(type)s] to %(to)s...") % {"type": mess_data["type"], "to": to_jid.full()})
log.debug(_(u"Sending message (type {type}, to {to})").format(type=mess_data["type"], to=to_jid.full()))
def cancelErrorTrap(failure):
"""A message sending can be cancelled by a plugin treatment"""
@ -572,7 +580,7 @@ class SAT(service.Service):
if statuses is None:
statuses = {}
profile = self.memory.getProfileName(profile_key)
assert(profile)
assert profile
priority = int(self.memory.getParamA("Priority", "Connection", profile_key=profile))
self.profiles[profile].presence.available(to_jid, show, statuses, priority)
#XXX: FIXME: temporary fix to work around openfire 3.7.0 bug (presence is not broadcasted to generating resource)
@ -588,7 +596,7 @@ class SAT(service.Service):
@param raw_jid: unicode entity's jid
@param profile_key: profile"""
profile = self.memory.getProfileName(profile_key)
assert(profile)
assert profile
to_jid = jid.JID(raw_jid)
log.debug(_('subsciption request [%(subs_type)s] for %(jid)s') % {'subs_type': subs_type, 'jid': to_jid.full()})
if subs_type == "subscribe":
@ -606,8 +614,8 @@ class SAT(service.Service):
def addContact(self, to_jid, profile_key):
"""Add a contact in roster list"""
profile = self.memory.getProfileName(profile_key)
assert(profile)
#self.profiles[profile].roster.addItem(to_jid) #XXX: disabled (cf http://wokkel.ik.nu/ticket/56))
assert profile
# presence is sufficient, as a roster push will be sent according to RFC 6121 §3.1.2
self.profiles[profile].presence.subscribe(to_jid)
def _updateContact(self, to_jid_s, name, groups, profile_key):
@ -616,12 +624,12 @@ class SAT(service.Service):
def updateContact(self, to_jid, name, groups, profile_key):
"""update a contact in roster list"""
profile = self.memory.getProfileName(profile_key)
assert(profile)
assert profile
groups = set(groups)
roster_item = RosterItem(to_jid)
roster_item.name = name or None
roster_item.groups = set(groups)
self.profiles[profile].roster.updateItem(roster_item)
return self.profiles[profile].roster.setItem(roster_item)
def _delContact(self, to_jid_s, profile_key):
return self.delContact(jid.JID(to_jid_s), profile_key)
@ -629,10 +637,9 @@ class SAT(service.Service):
def delContact(self, to_jid, profile_key):
"""Remove contact from roster list"""
profile = self.memory.getProfileName(profile_key)
assert(profile)
self.profiles[profile].roster.removeItem(to_jid)
self.profiles[profile].presence.unsubscribe(to_jid)
assert profile
self.profiles[profile].presence.unsubscribe(to_jid) # is not asynchronous
return self.profiles[profile].roster.removeItem(to_jid)
## Discovery ##
# discovery methods are shortcuts to self.memory.disco
@ -770,6 +777,9 @@ class SAT(service.Service):
@profile_key: %(doc_profile_key)s
@return: a deferred which fire a dict where key can be:
- xmlui: a XMLUI need to be displayed
- validated: if present, can be used to launch a callback, it can have the values
- C.BOOL_TRUE
- C.BOOL_FALSE
"""
profile = self.memory.getProfileName(profile_key)
if not profile:
@ -848,6 +858,7 @@ class SAT(service.Service):
def getMenus(self, language='', security_limit=C.NO_SECURITY_LIMIT):
"""Return all menus registered
@param language: language used for translation, or empty string for default
@param security_limit: %(doc_security_limit)s
@return: array of tuple with:
@ -855,7 +866,9 @@ class SAT(service.Service):
- menu type
- raw menu path (array of strings)
- translated menu path
- extra (dict(unicode, unicode)): extra data where key can be:
- icon: name of the icon to use (TODO)
- help_url: link to a page with more complete documentation (TODO)
"""
ret = []
for menu_id, menu_data in self._menus.iteritems():
@ -867,7 +880,8 @@ class SAT(service.Service):
languageSwitch(language)
path_i18n = [_(elt) for elt in path]
languageSwitch()
ret.append((menu_id, type_, path, path_i18n))
extra = {} # TODO: manage extra data like icon
ret.append((menu_id, type_, path, path_i18n, extra))
return ret

View file

@ -174,14 +174,42 @@ class SatRosterProtocol(xmppim.RosterClientProtocol):
def __init__(self, host):
xmppim.RosterClientProtocol.__init__(self)
self.host = host
self.got_roster = defer.Deferred()
self.got_roster = defer.Deferred() # called when roster is received and ready
#XXX: the two following dicts keep a local copy of the roster
self._groups = {} # map from groups to bare jids: key=group value=set of bare jids
self._jids = {} # map from bare jids to RosterItem: key=jid value=RosterItem
self._groups = {} # map from groups to jids: key=group value=set of jids
self._jids = None # map from jids to RosterItem: key=jid value=RosterItem
def rosterCb(self, roster):
for raw_jid, item in roster.iteritems():
self.onRosterSet(item)
assert roster is not None # FIXME: must be managed with roster versioning
self._jids = roster
for roster_item in roster.itervalues():
self._registerItem(roster_item)
def _registerItem(self, item):
"""Register item in local cache
item must be already registered in self._jids before this method is called
@param item (RosterIem): item added
"""
log.debug(u"registering item: {}".format(item.jid.full()))
if item.entity.resource:
log.warning(u"Received a roster item with a resource, this is not common but not restricted by RFC 6121, this case may be not well tested.")
if not item.subscriptionTo and not item.subscriptionFrom and not item.ask:
#XXX: current behaviour: we don't want contact in our roster list
# if there is no presence subscription
# may change in the future
self.removeItem(item.jid) # FIXME: to be checked
return
if not item.subscriptionTo:
if not item.subscriptionFrom:
log.info(_(u"There's no subscription between you and [{}]!").format(item.jid.full()))
else:
log.info(_(u"You are not subscribed to [{}]!").format(item.jid.full()))
if not item.subscriptionFrom:
log.info(_(u"[{}] is not subscribed to you!").format(item.jid.full()))
for group in item.groups:
self._groups.setdefault(group, set()).add(item.entity)
def requestRoster(self):
""" ask the server for Roster list """
@ -192,31 +220,9 @@ class SatRosterProtocol(xmppim.RosterClientProtocol):
def removeItem(self, to_jid):
"""Remove a contact from roster list
@param to_jid: a JID instance
@return: Deferred
"""
xmppim.RosterClientProtocol.removeItem(self, to_jid)
#TODO: check IQ result
#XXX: disabled (cf http://wokkel.ik.nu/ticket/56))
#def addItem(self, to):
#"""Add a contact to roster list"""
#xmppim.RosterClientProtocol.addItem(self, to)
#TODO: check IQ result"""
def updateItem(self, roster_item):
"""
Update an item of the contact list.
@param roster_item: item to update
"""
iq = compat.IQ(self.xmlstream, 'set')
iq.addElement((xmppim.NS_ROSTER, 'query'))
item = iq.query.addElement('item')
item['jid'] = roster_item.jid.userhost()
if roster_item.name:
item['name'] = roster_item.name
for group in roster_item.groups:
item.addElement('group', content=group)
return iq.send()
return xmppim.RosterClientProtocol.removeItem(self, to_jid)
def getAttributes(self, item):
"""Return dictionary of attributes as used in bridge from a RosterItem
@ -230,72 +236,54 @@ class SatRosterProtocol(xmppim.RosterClientProtocol):
item_attr['name'] = item.name
return item_attr
def onRosterSet(self, item):
"""Called when a new/update roster item is received"""
#TODO: send a signal to frontends
if not item.subscriptionTo and not item.subscriptionFrom and not item.ask:
#XXX: current behaviour: we don't want contact in our roster list
# if there is no presence subscription
# may change in the future
self.removeItem(item.jid)
return
log.debug(_("New contact in roster list: %s") % item.jid.full())
if not item.subscriptionTo:
if not item.subscriptionFrom:
log.info(_("There's no subscription between you and [%s]!") % item.jid.full())
else:
log.info(_("You are not subscribed to [%s]!") % item.jid.full())
if not item.subscriptionFrom:
log.info(_("[%s] is not subscribed to you!") % item.jid.full())
#self.host.memory.addContact(item.jid, item_attr, item.groups, self.parent.profile)
bare_jid = item.jid.userhostJID()
self._jids[bare_jid] = item
for group in item.groups:
self._groups.setdefault(group, set()).add(bare_jid)
def setReceived(self, request):
#TODO: implement roster versioning (cf RFC 6121 §2.6)
item = request.item
self._jids[item.entity] = item
self.host.bridge.newContact(item.jid.full(), self.getAttributes(item), item.groups, self.parent.profile)
def onRosterRemove(self, entity):
"""Called when a roster removal event is received"""
print _("removing %s from roster list") % entity.full()
bare_jid = entity.userhostJID()
def removeReceived(self, request):
entity = request.item.entity
log.info(u"removing %s from roster list" % entity.full())
# we first remove item from local cache (self._groups and self._jids)
try:
item = self._jids.pop(bare_jid)
item = self._jids.pop(entity)
except KeyError:
log.warning("Received a roster remove event for an item not in cache")
log.error("Received a roster remove event for an item not in cache ({})".format(entity))
return
for group in item.groups:
try:
jids_set = self._groups[group]
jids_set.remove(bare_jid)
jids_set.remove(entity)
if not jids_set:
del self._groups[group]
except KeyError:
log.warning("there is not cache for the group [%(groups)s] of the removed roster item [%(jid)s]" %
{"group": group, "jid": bare_jid})
{"group": group, "jid": entity})
# then we send the bridge signal
self.host.bridge.contactDeleted(entity.userhost(), self.parent.profile)
self.host.bridge.contactDeleted(entity.full(), self.parent.profile)
def getGroups(self):
"""Return a list of groups"""
return self._groups.keys()
def getItem(self, jid):
def getItem(self, entity_jid):
"""Return RosterItem for a given jid
@param jid: jid of the contact
@return: RosterItem or None if contact is not in cache"""
return self._jids.get(jid.userhostJID(), None)
def getBareJids(self):
"""Return all bare jids (as unicode) of the roster"""
@param entity_jid: jid of the contact
@return: RosterItem or None if contact is not in cache
"""
return self._jids.get(entity_jid, None)
def getJids(self):
"""Return all jids of the roster"""
return self._jids.keys()
def isJidInRoster(self, entity_jid):
"""Return True if jid is in roster"""
return entity_jid.userhostJID() in self._jids
return entity_jid in self._jids
def getItems(self):
"""Return all items of the roster"""
@ -305,7 +293,7 @@ class SatRosterProtocol(xmppim.RosterClientProtocol):
try:
return self._groups[group]
except KeyError:
raise exceptions.UnknownGroupError
raise exceptions.UnknownGroupError(group)
class SatPresenceProtocol(xmppim.PresenceClientProtocol):
@ -320,7 +308,7 @@ class SatPresenceProtocol(xmppim.PresenceClientProtocol):
super(SatPresenceProtocol, self).send(obj)
def availableReceived(self, entity, show=None, statuses=None, priority=0):
log.debug(_("presence update for [%(entity)s] (available, show=%(show)s statuses=%(statuses)s priority=%(priority)d)") % {'entity': entity, 'show': show, 'statuses': statuses, 'priority': priority})
log.debug(_("presence update for [%(entity)s] (available, show=%(show)s statuses=%(statuses)s priority=%(priority)d)") % {'entity': entity, C.PRESENCE_SHOW: show, C.PRESENCE_STATUSES: statuses, C.PRESENCE_PRIORITY: priority})
if not statuses:
statuses = {}
@ -333,17 +321,13 @@ class SatPresenceProtocol(xmppim.PresenceClientProtocol):
int(priority), statuses,
self.parent.profile)
# uncomment these two lines if you need the trigger
#if not self.host.trigger.point("presenceReceived", entity, "unavailable", 0, statuses, self.parent.profile):
# return
# now it's time to notify frontends
self.host.bridge.presenceUpdate(entity.full(), show or "",
int(priority), statuses,
self.parent.profile)
def unavailableReceived(self, entity, statuses=None):
log.debug(_("presence update for [%(entity)s] (unavailable, statuses=%(statuses)s)") % {'entity': entity, 'statuses': statuses})
log.debug(_("presence update for [%(entity)s] (unavailable, statuses=%(statuses)s)") % {'entity': entity, C.PRESENCE_STATUSES: statuses})
if not statuses:
statuses = {}

View file

@ -102,18 +102,19 @@ class Discovery(object):
jid_ = jid.JID(client.jid.host)
try:
cap_hash = self.host.memory.getEntityData(jid_, [C.ENTITY_CAP_HASH], client.profile)[C.ENTITY_CAP_HASH]
disco_infos = self.hashes[cap_hash]
return defer.succeed(disco_infos)
except KeyError:
except (KeyError, exceptions.UnknownEntityError):
# capability hash is not available, we'll compute one
def infosCb(disco_infos):
cap_hash = self.generateHash(disco_infos)
self.hashes[cap_hash] = disco_infos
self.host.memory.updateEntityData(jid_, C.ENTITY_CAP_HASH, cap_hash, client.profile)
self.host.memory.updateEntityData(jid_, C.ENTITY_CAP_HASH, cap_hash, profile_key=client.profile)
return disco_infos
d = client.disco.requestInfo(jid_)
d.addCallback(infosCb)
return d
else:
disco_infos = self.hashes[cap_hash]
return defer.succeed(disco_infos)
@defer.inlineCallbacks
def getItems(self, jid_=None, nodeIdentifier='', profile_key=C.PROF_KEY_NONE):
@ -131,10 +132,10 @@ class Discovery(object):
try:
items = self.host.memory.getEntityData(jid_, ["DISCO_ITEMS"], client.profile)["DISCO_ITEMS"]
log.debug("[%s] disco items are in cache" % jid_.full())
except KeyError:
except (KeyError, exceptions.UnknownEntityError):
log.debug("Caching [%s] disco items" % jid_.full())
items = yield client.disco.requestItems(jid_, nodeIdentifier)
self.host.memory.updateEntityData(jid_, "DISCO_ITEMS", items, client.profile)
self.host.memory.updateEntityData(jid_, "DISCO_ITEMS", items, profile_key=client.profile)
else:
items = yield client.disco.requestItems(jid_, nodeIdentifier)

View file

@ -19,11 +19,14 @@
from sat.core.i18n import _
import os.path
from ConfigParser import SafeConfigParser, NoOptionError, NoSectionError
from uuid import uuid4
from sat.core.log import getLogger
log = getLogger(__name__)
import os.path
import copy
from collections import namedtuple
from ConfigParser import SafeConfigParser, NoOptionError, NoSectionError
from uuid import uuid4
from twisted.internet import defer, reactor
from twisted.words.protocols.jabber import jid
from sat.core import exceptions
@ -36,6 +39,8 @@ from sat.memory.crypto import BlockCipher
from sat.tools import config as tools_config
PresenceTuple = namedtuple("PresenceTuple", ('show', 'priority', 'statuses'))
class Sessions(object):
"""Sessions are data associated to key used for a temporary moment, with optional profile checking."""
DEFAULT_TIMEOUT = 600
@ -212,6 +217,9 @@ class Memory(object):
self.host = host
self._entities_cache = {} # XXX: keep presence/last resource/other data in cache
# /!\ an entity is not necessarily in roster
# main key is bare jid, value is a dict
# where main key is resource, or None for bare jid
self._key_signals = set() # key which need a signal to frontends when updated
self.subscriptions = {}
self.auth_sessions = PasswordSessions() # remember the authenticated profiles
self.disco = Discovery(host)
@ -228,6 +236,8 @@ class Memory(object):
d.addCallback(lambda ignore: self.memory_data.load())
d.chainDeferred(self.initialized)
## Configuration ##
def parseMainConf(self):
"""look for main .ini configuration file, and parse it"""
config = SafeConfigParser(defaults=C.DEFAULT_CONFIG)
@ -265,6 +275,24 @@ class Memory(object):
log.error(_("Can't load parameters from file: %s") % e)
return False
def save_xml(self, filename):
"""Save parameters template to xml file
@param filename (str): output file
@return: bool: True in case of success
"""
if not filename:
return False
#TODO: need to encrypt files (at least passwords !) and set permissions
filename = os.path.expanduser(filename)
try:
self.params.save_xml(filename)
log.debug(_("Parameters saved to file: %s") % filename)
return True
except Exception as e:
log.error(_("Can't save parameters to file: %s") % e)
return False
def load(self):
"""Load parameters and all memory things from db"""
#parameters data
@ -275,6 +303,8 @@ class Memory(object):
@param profile: %(doc_profile)s"""
return self.params.loadIndParams(profile)
## Profiles/Sessions management ##
def startProfileSession(self, profile):
""""Iniatialise session for a profile
@param profile: %(doc_profile)s"""
@ -309,31 +339,16 @@ class Memory(object):
except KeyError:
log.error(_("Trying to purge roster status cache for a profile not in memory: [%s]") % profile)
def save_xml(self, filename):
"""Save parameters template to xml file
@param filename (str): output file
@return: bool: True in case of success
"""
if not filename:
return False
#TODO: need to encrypt files (at least passwords !) and set permissions
filename = os.path.expanduser(filename)
try:
self.params.save_xml(filename)
log.debug(_("Parameters saved to file: %s") % filename)
return True
except Exception as e:
log.error(_("Can't save parameters to file: %s") % e)
return False
def getProfilesList(self):
return self.storage.getProfilesList()
def getProfileName(self, profile_key, return_profile_keys=False):
"""Return name of profile from keyword
@param profile_key: can be the profile name or a keywork (like @DEFAULT@)
@return: profile name or None if it doesn't exist"""
@param return_profile_keys: if True, return unmanaged profile keys (like "@ALL@"). This keys must be managed by the caller
@return: requested profile name or emptry string if it doesn't exist
"""
return self.params.getProfileName(profile_key, return_profile_keys)
def asyncCreateProfile(self, name, password=''):
@ -342,6 +357,10 @@ class Memory(object):
@param password: profile password
@return: Deferred
"""
if not name:
raise ValueError("Empty profile name")
if name[0] == '@':
raise ValueError("A profile name can't start with a '@'")
personal_key = BlockCipher.getRandomKey(base64=True) # generated once for all and saved in a PersistentDict
self.auth_sessions.newSession({C.MEMORY_CRYPTO_KEY: personal_key}, profile=name) # will be encrypted by setParam
d = self.params.asyncCreateProfile(name)
@ -358,6 +377,8 @@ class Memory(object):
self.auth_sessions.profileDelUnique(name)
return self.params.asyncDeleteProfile(name, force)
## History ##
def addToHistory(self, from_jid, to_jid, message, type_='chat', extra=None, timestamp=None, profile=C.PROF_KEY_NONE):
assert profile != C.PROF_KEY_NONE
if extra is None:
@ -386,163 +407,305 @@ class Memory(object):
return defer.succeed([])
return self.storage.getHistory(jid.JID(from_jid), jid.JID(to_jid), limit, between, search, profile)
def _getLastResource(self, jid_s, profile_key):
jid_ = jid.JID(jid_s)
return self.getLastResource(jid_, profile_key) or ""
def getLastResource(self, entity_jid, profile_key):
"""Return the last resource used by an entity
@param entity_jid: entity jid
@param profile_key: %(doc_profile_key)s"""
data = self.getEntityData(entity_jid.userhostJID(), [C.ENTITY_LAST_RESOURCE], profile_key)
try:
return data[C.ENTITY_LAST_RESOURCE]
except KeyError:
return None
## Statuses ##
def _getPresenceStatuses(self, profile_key):
ret = self.getPresenceStatuses(profile_key)
return {entity.full():data for entity, data in ret.iteritems()}
def getPresenceStatuses(self, profile_key):
"""Get all the presence status of a profile
"""Get all the presence statuses of a profile
@param profile_key: %(doc_profile_key)s
@return: presence data: key=entity JID, value=presence data for this entity
"""
profile = self.getProfileName(profile_key)
if not profile:
raise exceptions.ProfileUnknownError(_('Trying to get entity data for a non-existant profile'))
profile_cache = self._getProfileCache(profile_key)
entities_presence = {}
for entity in self._entities_cache[profile]:
if "presence" in self._entities_cache[profile][entity]:
entities_presence[entity] = self._entities_cache[profile][entity]["presence"]
log.debug("Memory getPresenceStatus (%s)" % entities_presence)
for entity_jid, entity_data in profile_cache.iteritems():
for resource, resource_data in entity_data.iteritems():
full_jid = copy.copy(entity_jid)
full_jid.resource = resource
try:
presence_data = self.getEntityDatum(full_jid, "presence", profile_key)
except KeyError:
continue
entities_presence.setdefault(entity_jid, {})[resource or ''] = presence_data
return entities_presence
def isContactConnected(self, entity_jid, profile_key):
"""Tell from the presence information if the given contact is connected.
def setPresenceStatus(self, entity_jid, show, priority, statuses, profile_key):
"""Change the presence status of an entity
@param entity_jid (JID): the entity to check
@param entity_jid: jid.JID of the entity
@param show: show status
@param priority: priority
@param statuses: dictionary of statuses
@param profile_key: %(doc_profile_key)s
@return: boolean
"""
presence_data = PresenceTuple(show, priority, statuses)
self.updateEntityData(entity_jid, "presence", presence_data, profile_key=profile_key)
if entity_jid.resource and show != C.PRESENCE_UNAVAILABLE:
# If a resource is available, bare jid should not have presence information
try:
self.delEntityDatum(entity_jid.userhostJID(), "presence", profile_key)
except (KeyError, exceptions.UnknownEntityError):
pass
## Resources ##
def _getAllResource(self, jid_s, profile_key):
jid_ = jid.JID(jid_s)
return self.getAllResources(jid_, profile_key)
def getAllResources(self, entity_jid, profile_key):
"""Return all resource from jid for which we have had data in this session
@param entity_jid: bare jid of the entit
@param profile_key: %(doc_profile_key)s
return (list[unicode]): list of resources
@raise exceptions.UnknownEntityError: if entity is not in cache
"""
if entity_jid.resource:
raise ValueError("getAllResources must be used with a bare jid (got {})".format(entity_jid))
profile_cache = self._getProfileCache(profile_key)
try:
entity_data = profile_cache[entity_jid.userhostJID()]
except KeyError:
raise exceptions.UnknownEntityError(u"Entity {} not in cache".format(entity_jid))
resources= set(entity_data.keys())
resources.discard(None)
return resources
def getAvailableResources(self, entity_jid, profile_key):
"""Return available resource for entity_jid
This method differs from getAllResources by returning only available resources
@param entity_jid: bare jid of the entit
@param profile_key: %(doc_profile_key)s
return (list[unicode]): list of available resources
@raise exceptions.UnknownEntityError: if entity is not in cache
"""
available = []
for resource in self.getAllResources(entity_jid, profile_key):
full_jid = copy.copy(entity_jid)
full_jid.resource = resource
try:
presence_data = self.getEntityDatum(full_jid, "presence", profile_key)
except KeyError:
log.debug("Can't get presence data for {}".format(full_jid))
else:
if presence_data.show != C.PRESENCE_UNAVAILABLE:
available.append(resource)
return available
def _getMainResource(self, jid_s, profile_key):
jid_ = jid.JID(jid_s)
return self.getMainResource(jid_, profile_key) or ""
def getMainResource(self, entity_jid, profile_key):
"""Return the main resource used by an entity
@param entity_jid: bare entity jid
@param profile_key: %(doc_profile_key)s
@return (unicode): main resource or None
"""
if entity_jid.resource:
raise ValueError("getMainResource must be used with a bare jid (got {})".format(entity_jid))
resources = self.getAllResources(entity_jid, profile_key)
priority_resources = []
for resource in resources:
full_jid = copy.copy(entity_jid)
full_jid.resource = resource
try:
presence_data = self.getEntityDatum(full_jid, "presence", profile_key)
except KeyError:
log.debug("No presence information for {}".format(full_jid))
continue
priority_resources.append((resource, presence_data.priority))
try:
return max(priority_resources, key=lambda res_tuple: res_tuple[1])[0]
except ValueError:
log.warning("No resource found at all for {}".format(entity_jid))
return None
## Entities data ##
def _getProfileCache(self, profile_key):
"""Check profile validity and return its cache
@param profile: %(doc_profile_key)_s
@return (dict): profile cache
@raise exceptions.ProfileUnknownError: if profile doesn't exist
@raise exceptions.ProfileNotInCacheError: if there is no cache for this profile
"""
profile = self.getProfileName(profile_key)
if not profile:
raise exceptions.ProfileUnknownError(_('Trying to get entity data for a non-existant profile'))
try:
presence = self._entities_cache[profile][entity_jid]['presence']
return len([True for status in presence.values() if status[0] != 'unavailable']) > 0
profile_cache = self._entities_cache[profile]
except KeyError:
return False
def setPresenceStatus(self, entity_jid, show, priority, statuses, profile_key):
"""Change the presence status of an entity
@param entity_jid: jid.JID of the entity
@param show: show status
@param priotity: priotity
@param statuses: dictionary of statuses
@param profile_key: %(doc_profile_key)s
"""
profile = self.getProfileName(profile_key)
if not profile:
raise exceptions.ProfileUnknownError(_('Trying to get entity data for a non-existant profile'))
entity_data = self._getEntitiesData(entity_jid, profile)[entity_jid]
resource = entity_jid.resource
if resource:
try:
type_ = self.getEntityDatum(entity_jid.userhostJID(), 'type', profile)
except KeyError:
type_ = 'contact'
if type_ != 'chatroom':
self.updateEntityData(entity_jid.userhostJID(), C.ENTITY_LAST_RESOURCE, resource, profile)
entity_data.setdefault("presence", {})[resource or ''] = (show, priority, statuses)
def _getEntitiesData(self, entity_jid, profile):
"""Get data dictionary for entities
@param entity_jid: JID of the entity, or C.ENTITY_ALL for all entities)
@param profile: %(doc_profile)s
@return: entities_data (key=jid, value=entity_data)
@raise: exceptions.ProfileNotInCacheError if profile is not in cache
"""
if not profile in self._entities_cache:
raise exceptions.ProfileNotInCacheError
if entity_jid == C.ENTITY_ALL:
entities_data = self._entities_cache[profile]
else:
entity_data = self._entities_cache[profile].setdefault(entity_jid, {})
entities_data = {entity_jid: entity_data}
return entities_data
return profile_cache
def _updateEntityResources(self, entity_jid, profile):
"""Add a known resource to bare entity_jid cache
@param entity_jid: full entity_jid (with resource)
@param profile: %(doc_profile)s
def setSignalOnUpdate(self, key, signal=True):
"""Set a signal flag on the key
When the key will be updated, a signal will be sent to frontends
@param key: key to signal
@param signal(boolean): if True, do the signal
"""
assert(entity_jid.resource)
entity_data = self._getEntitiesData(entity_jid.userhostJID(), profile)[entity_jid.userhostJID()]
resources = entity_data.setdefault('resources', set())
resources.add(entity_jid.resource)
if signal:
self._key_signals.add(key)
else:
self._key_signals.discard(key)
def updateEntityData(self, entity_jid, key, value, profile_key):
def getAllEntitiesIter(self, with_bare=False, profile_key=C.PROF_KEY_NONE):
"""Return an iterator of full jids of all entities in cache
@param with_bare: if True, include bare jids
@param profile_key: %(doc_profile_key)s
@return (list[unicode]): list of jids
"""
profile_cache = self._getProfileCache(profile_key)
# we construct a list of all known full jids (bare jid of entities x resources)
for bare_jid, entity_data in profile_cache.iteritems():
for resource in entity_data.iterkeys():
if resource is None:
continue
full_jid = copy.copy(bare_jid)
full_jid.resource = resource
yield full_jid
def updateEntityData(self, entity_jid, key, value, silent=False, profile_key=C.PROF_KEY_NONE):
"""Set a misc data for an entity
@param entity_jid: JID of the entity, or C.ENTITY_ALL to update all entities)
If key was registered with setSignalOnUpdate, a signal will be sent to frontends
@param entity_jid: JID of the entity, C.ENTITY_ALL_RESOURCES for all resources of all entities,
C.ENTITY_ALL for all entities (all resources + bare jids)
@param key: key to set (eg: "type")
@param value: value for this key (eg: "chatroom")
@param silent(bool): if True, doesn't send signal to frontend, even there is a signal flag (see setSignalOnUpdate)
@param profile_key: %(doc_profile_key)s
"""
profile = self.getProfileName(profile_key)
if not profile:
raise exceptions.ProfileUnknownError(_('Trying to get entity data for a non-existant profile'))
entities_data = self._getEntitiesData(entity_jid, profile)
if entity_jid != C.ENTITY_ALL and entity_jid.resource:
self._updateEntityResources(entity_jid, profile)
profile_cache = self._getProfileCache(profile_key)
if entity_jid in (C.ENTITY_ALL_RESOURCES, C.ENTITY_ALL):
entities = self.getAllEntitiesIter(entity_jid==C.ENTITY_ALL, profile_key)
else:
entities = (entity_jid,)
for jid_ in entities_data:
entity_data = entities_data[jid_]
if value == C.PROF_KEY_NONE and key in entity_data:
del entity_data[key]
else:
entity_data[key] = value
if isinstance(value, basestring):
self.host.bridge.entityDataUpdated(jid_.full(), key, value, profile)
for jid_ in entities:
entity_data = profile_cache.setdefault(jid_.userhostJID(),{}).setdefault(jid_.resource, {})
def delEntityData(self, entity_jid, key, profile_key):
"""Delete data for an entity
@param entity_jid: JID of the entity, or C.ENTITY_ALL to delete data from all entities)
entity_data[key] = value
if key in self._key_signals and not silent:
if not isinstance(value, basestring):
log.error(u"Setting a non string value ({}) for a key ({}) which has a signal flag".format(value, key))
else:
self.host.bridge.entityDataUpdated(jid_.full(), key, value, self.getProfileName(profile_key))
def delEntityDatum(self, entity_jid, key, profile_key):
"""Delete a data for an entity
@param entity_jid: JID of the entity, C.ENTITY_ALL_RESOURCES for all resources of all entities,
C.ENTITY_ALL for all entities (all resources + bare jids)
@param key: key to delete (eg: "type")
@param profile_key: %(doc_profile_key)s
@raise exceptions.UnknownEntityError: if entity is not in cache
@raise KeyError: key is not in cache
"""
entities_data = self._getEntitiesData(entity_jid, profile_key)
for entity_jid in entities_data:
entity_data = entities_data[entity_jid]
profile_cache = self._getProfileCache(profile_key)
if entity_jid in (C.ENTITY_ALL_RESOURCES, C.ENTITY_ALL):
entities = self.getAllEntitiesIter(entity_jid==C.ENTITY_ALL, profile_key)
else:
entities = (entity_jid,)
for jid_ in entities:
try:
entity_data = profile_cache[jid_.userhostJID()][jid_.resource]
except KeyError:
raise exceptions.UnknownEntityError(u"Entity {} not in cache".format(jid_))
try:
del entity_data[key]
except KeyError:
log.debug("Key [%s] doesn't exist for [%s] in entities_cache" % (key, entity_jid.full()))
except KeyError as e:
if entity_jid in (C.ENTITY_ALL_RESOURCES, C.ENTITY_ALL):
continue # we ignore KeyError when deleting keys from several entities
else:
raise e
def getEntityData(self, entity_jid, keys_list, profile_key):
"""Get a list of cached values for entity
def _getEntitiesData(self, entities_jids, keys_list, profile_key):
ret = self.getEntitiesData([jid.JID(jid_) for jid_ in entities_jids], keys_list, profile_key)
return {jid_.full(): data for jid_, data in ret.iteritems()}
@param entity_jid: JID of the entity
@param keys_list (iterable): list of keys to get, empty list for everything
def getEntitiesData(self, entities_jids, keys_list=None, profile_key=C.PROF_KEY_NONE):
"""Get a list of cached values for several entities at once
@param entities_jids: jids of the entities, or empty list for all entities in cache
@param keys_list (iterable,None): list of keys to get, None for everything
@param profile_key: %(doc_profile_key)s
@return: dict withs values for each key in keys_list.
if there is no value of a given key, resulting dict will
have nothing with that key nether
@raise: exceptions.UnknownEntityError if entity is not in cache
if an entity doesn't exist in cache, it will not appear
in resulting dict
@raise exceptions.UnknownEntityError: if entity is not in cache
"""
profile = self.getProfileName(profile_key)
if not profile:
raise exceptions.ProfileUnknownError(_('Trying to get entity data for a non-existant profile'))
entity_data = self._getEntitiesData(entity_jid, profile)[entity_jid]
if not keys_list:
def fillEntityData(entity_cache_data):
entity_data = {}
if keys_list is None:
entity_data = entity_cache_data
else:
for key in keys_list:
try:
entity_data[key] = entity_cache_data[key]
except KeyError:
continue
return entity_data
ret = {}
for key in keys_list:
if key in entity_data:
ret[key] = entity_data[key]
return ret
profile_cache = self._getProfileCache(profile_key)
ret_data = {}
if entities_jids:
for entity in entities_jids:
try:
entity_cache_data = profile_cache[entity.userhostJID()][entity.resource]
except KeyError:
continue
ret_data[entity.full()] = fillEntityData(entity_cache_data, keys_list)
else:
for bare_jid, data in profile_cache.iteritems():
for resource, entity_cache_data in data.iteritems():
full_jid = copy.copy(bare_jid)
full_jid.resource = resource
ret_data[full_jid] = fillEntityData(entity_cache_data)
return ret_data
def getEntityData(self, entity_jid, keys_list=None, profile_key=C.PROF_KEY_NONE):
"""Get a list of cached values for entity
@param entity_jid: JID of the entity
@param keys_list (iterable,None): list of keys to get, None for everything
@param profile_key: %(doc_profile_key)s
@return: dict withs values for each key in keys_list.
if there is no value of a given key, resulting dict will
have nothing with that key nether
@raise exceptions.UnknownEntityError: if entity is not in cache
"""
profile_cache = self._getProfileCache(profile_key)
try:
entity_data = profile_cache[entity_jid.userhostJID()][entity_jid.resource]
except KeyError:
raise exceptions.UnknownEntityError(u"Entity {} not in cache (was requesting {})".format(entity_jid, keys_list))
if keys_list is None:
return entity_data
return {key: entity_data[key] for key in keys_list if key in entity_data}
def getEntityDatum(self, entity_jid, key, profile_key):
"""Get a datum from entity
@ -552,34 +715,36 @@ class Memory(object):
@param profile_key: %(doc_profile_key)s
@return: requested value
@raise: exceptions.UnknownEntityError if entity is not in cache
@raise: KeyError if there is no value for this key and this entity
@raise exceptions.UnknownEntityError: if entity is not in cache
@raise KeyError: if there is no value for this key and this entity
"""
return self.getEntityData(entity_jid, (key,), profile_key)[key]
def delEntityCache(self, entity_jid, delete_all_resources=True, profile_key=C.PROF_KEY_NONE):
"""Remove cached data for entity
"""Remove all cached data for entity
@param entity_jid: JID of the entity to delete
@param delete_all_resources: if True also delete all known resources form cache
@param delete_all_resources: if True also delete all known resources from cache (a bare jid must be given in this case)
@param profile_key: %(doc_profile_key)s
@raise exceptions.UnknownEntityError: if entity is not in cache
"""
profile = self.getProfileName(profile_key)
if not profile:
raise exceptions.ProfileUnknownError(_('Trying to get entity data for a non-existant profile'))
to_delete = set([entity_jid])
profile_cache = self._getProfileCache(profile_key)
if delete_all_resources:
if entity_jid.resource:
raise ValueError(_("Need a bare jid to delete all resources"))
entity_data = self._getEntitiesData(entity_jid, profile)[entity_jid]
resources = entity_data.setdefault('resources', set())
to_delete.update([jid.JID("%s/%s" % (entity_jid.userhost(), resource)) for resource in resources])
for entity in to_delete:
try:
del self._entities_cache[profile][entity]
del profile_cache[entity_jid]
except KeyError:
log.debug("Can't delete entity [%s]: not in cache" % entity.full())
raise exceptions.UnknownEntityError(u"Entity {} not in cache".format(entity_jid))
else:
try:
del profile_cache[entity_jid.userhostJID()][entity_jid.resource]
except KeyError:
raise exceptions.UnknownEntityError(u"Entity {} not in cache".format(entity_jid))
## Encryption ##
def encryptValue(self, value, profile):
"""Encrypt a value for the given profile. The personal key must be loaded
@ -637,6 +802,8 @@ class Memory(object):
d = PersistentDict(C.MEMORY_CRYPTO_NAMESPACE, profile).load()
return d.addCallback(gotIndMemory).addCallback(done)
## Subscription requests ##
def addWaitingSub(self, type_, entity_jid, profile_key):
"""Called when a subcription request is received"""
profile = self.getProfileName(profile_key)
@ -663,6 +830,8 @@ class Memory(object):
return self.subscriptions[profile]
## Parameters ##
def getStringParamA(self, name, category, attr="value", profile_key=C.PROF_KEY_NONE):
return self.params.getStringParamA(name, category, attr, profile_key)
@ -698,3 +867,21 @@ class Memory(object):
def setDefault(self, name, category, callback, errback=None):
return self.params.setDefault(name, category, callback, errback)
## Misc ##
def isEntityAvailable(self, entity_jid, profile_key):
"""Tell from the presence information if the given entity is available.
@param entity_jid (JID): the entity to check (if bare jid is used, all resources are tested)
@param profile_key: %(doc_profile_key)s
@return (bool): True if entity is available
"""
if not entity_jid.resource:
return bool(self.getAvailableResources) # is any resource is available, entity is available
try:
presence_data = self.getEntityDatum(entity_jid, "presence", profile_key)
except KeyError:
log.debug("No presence information for {}".format(entity_jid))
return False
return presence_data.show != C.PRESENCE_UNAVAILABLE

View file

@ -49,14 +49,14 @@ class Params(object):
<param name="%(show_empty_groups)s" label="%(show_empty_groups_label)s" value="true" type="bool" security="0" />
</category>
<category name="Connection" label="%(category_connection)s">
<param name="JabberID" value="name@example.org/SàT" type="string" />
<param name="JabberID" value="name@example.org/SàT" type="string" security="0" />
<param name="Password" value="" type="password" />
<param name="Priority" value="50" type="int" constraint="-128;127"/>
<param name="%(force_server_param)s" value="" type="string" />
<param name="%(force_port_param)s" value="" type="int" constraint="1;65535" />
<param name="NewAccount" label="%(new_account_label)s" type="button" callback_id="registerNewAccount"/>
<param name="autoconnect" label="%(autoconnect_label)s" value="true" type="bool" />
<param name="autodisconnect" label="%(autodisconnect_label)s" value="false" type="bool" />
<param name="autoconnect" label="%(autoconnect_label)s" value="true" type="bool" security="0" />
<param name="autodisconnect" label="%(autodisconnect_label)s" value="false" type="bool" security="0" />
</category>
<category name="Misc" label="%(category_misc)s">
<param name="Watched" value="test@Jabber.goffi.int" type="string" />
@ -179,7 +179,7 @@ class Params(object):
return defer.fail(Failure(exceptions.ConnectedProfileError))
return self.storage.deleteProfile(profile)
def getProfileName(self, profile_key, return_profile_keys = False):
def getProfileName(self, profile_key, return_profile_keys=False):
"""return profile according to profile_key
@param profile_key: profile name or key which can be
@ -473,12 +473,12 @@ class Params(object):
node = self._getParamNode(name, category)
if not node:
log.error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category})
return defer.succeed(None)
raise ValueError("Requested param doesn't exist")
if not self.checkSecurityLimit(node[1], security_limit):
log.warning(_("Trying to get parameter '%(param)s' in category '%(cat)s' without authorization!!!"
% {'param': name, 'cat': category}))
return defer.succeed(None)
raise exceptions.PermissionError
if node[0] == C.GENERAL:
value = self._getParam(category, name, C.GENERAL)

View file

@ -53,6 +53,12 @@ class PersistentDict(object):
d = self.storage.loadIndPrivates(self._cache, self.namespace, self.profile)
return d.addCallback(lambda dummy: self)
def iteritems(self):
return self._cache.iteritems()
def items(self):
return self._cache.items()
def __repr__(self):
return self._cache.__repr__()
@ -112,6 +118,13 @@ class PersistentDict(object):
def get(self, key, default=None):
return self._cache.get(key, default)
def setdefault(self, key, default):
try:
return self._cache[key]
except:
self.__setitem__(key, default)
return default
def force(self, name):
"""Force saving of an attribute to storage
@return: deferred fired when data is actually saved"""

View file

@ -91,7 +91,7 @@ class Exp_Parrot(object):
entity_type = "contact"
if entity_type == 'chatroom':
src_txt = from_jid.resource
if src_txt == self.host.plugins["XEP-0045"].getRoomNick(from_jid.userhost(), profile):
if src_txt == self.host.plugins["XEP-0045"].getRoomNick(from_jid.userhostJID(), profile):
#we won't repeat our own messages
return True
else:

View file

@ -58,9 +58,9 @@ class Quiz(object):
log.info(_("Plugin Quiz initialization"))
self.inheritFromRoomGame(host)
RoomGame._init_(self, host, PLUGIN_INFO, (NS_QG, QG_TAG), game_init={'stage': None}, player_init={'score': 0})
host.bridge.addMethod("quizGameLaunch", ".plugin", in_sign='asss', out_sign='', method=self.prepareRoom) # args: players, room_jid, profile
host.bridge.addMethod("quizGameCreate", ".plugin", in_sign='sass', out_sign='', method=self.createGame) # args: room_jid, players, profile
host.bridge.addMethod("quizGameReady", ".plugin", in_sign='sss', out_sign='', method=self.playerReady) # args: player, referee, profile
host.bridge.addMethod("quizGameLaunch", ".plugin", in_sign='asss', out_sign='', method=self._prepareRoom) # args: players, room_jid, profile
host.bridge.addMethod("quizGameCreate", ".plugin", in_sign='sass', out_sign='', method=self._createGame) # args: room_jid, players, profile
host.bridge.addMethod("quizGameReady", ".plugin", in_sign='sss', out_sign='', method=self._playerReady) # args: player, referee, profile
host.bridge.addMethod("quizGameAnswer", ".plugin", in_sign='ssss', out_sign='', method=self.playerAnswer)
host.bridge.addSignal("quizGameStarted", ".plugin", signature='ssass') # args: room_jid, referee, players, profile
host.bridge.addSignal("quizGameNew", ".plugin",
@ -192,7 +192,7 @@ class Quiz(object):
def timerExpired(self, room_jid, profile):
"""Called when nobody answered the question in time"""
game_data = self.games[room_jid.userhost()]
game_data = self.games[room_jid]
game_data['stage'] = 'expired'
mess = self.createGameElt(room_jid)
expired_elt = mess.firstChildElement().addElement('timer_expired')
@ -201,7 +201,7 @@ class Quiz(object):
def pauseTimer(self, room_jid):
"""Stop the timer and save the time left"""
game_data = self.games[room_jid.userhost()]
game_data = self.games[room_jid]
left = max(0, game_data["timer"].getTime() - time())
game_data['timer'].cancel()
game_data['time_left'] = int(left)
@ -210,7 +210,7 @@ class Quiz(object):
def restartTimer(self, room_jid, profile):
"""Restart a timer with the saved time"""
game_data = self.games[room_jid.userhost()]
game_data = self.games[room_jid]
assert game_data['time_left'] is not None
mess = self.createGameElt(room_jid)
restarted_elt = mess.firstChildElement().addElement('timer_restarted')
@ -223,7 +223,7 @@ class Quiz(object):
def askQuestion(self, room_jid, profile):
"""Ask a new question"""
game_data = self.games[room_jid.userhost()]
game_data = self.games[room_jid]
game_data['stage'] = "question"
game_data['question_id'] = "1"
timer = 30
@ -235,7 +235,7 @@ class Quiz(object):
def checkAnswer(self, room_jid, player, answer, profile):
"""Check if the answer given is right"""
game_data = self.games[room_jid.userhost()]
game_data = self.games[room_jid]
players_data = game_data['players_data']
good_answer = game_data['question_id'] == "1" and answer == "42"
players_data[player]['score'] += 1 if good_answer else -1
@ -264,7 +264,7 @@ Attention, tu es prêt ?""")}
from_jid = jid.JID(mess_elt['from'])
room_jid = jid.JID(from_jid.userhost())
game_elt = mess_elt.firstChildElement()
game_data = self.games[room_jid.userhost()]
game_data = self.games[room_jid]
if 'players_data' in game_data:
players_data = game_data['players_data']
@ -278,8 +278,8 @@ Attention, tu es prêt ?""")}
elif elt.name == 'player_ready': # ready to play
player = elt['player']
status = self.games[room_jid.userhost()]['status']
nb_players = len(self.games[room_jid.userhost()]['players'])
status = self.games[room_jid]['status']
nb_players = len(self.games[room_jid]['players'])
status[player] = 'ready'
log.debug(_('Player %(player)s is ready to start [status: %(status)s]') % {'player': player, 'status': status})
if status.values().count('ready') == nb_players: # everybody is ready, we can start the game
@ -294,7 +294,7 @@ Attention, tu es prêt ?""")}
elif elt.name == 'player_answer':
player = elt['player']
pause = game_data['stage'] == 'question' # we pause the game only if we are have a question at the moment
#we first send a buzzer message
# we first send a buzzer message
mess = self.createGameElt(room_jid)
buzzer_elt = mess.firstChildElement().addElement('player_buzzed')
buzzer_elt['player'] = player
@ -302,7 +302,7 @@ Attention, tu es prêt ?""")}
self.host.profiles[profile].xmlstream.send(mess)
if pause:
self.pauseTimer(room_jid)
#and we send the player answer
# and we send the player answer
mess = self.createGameElt(room_jid)
_answer = unicode(elt)
say_elt = mess.firstChildElement().addElement('player_says')

View file

@ -67,10 +67,10 @@ class Radiocol(object):
log.info(_("Radio collective initialization"))
self.inheritFromRoomGame(host)
RoomGame._init_(self, host, PLUGIN_INFO, (NC_RADIOCOL, RADIOC_TAG),
game_init={'queue': [], 'upload': True, 'playing': None, 'playing_time': 0, 'to_delete': {}})
game_init={'queue': [], 'upload': True, 'playing': None, 'playing_time': 0, 'to_delete': {}})
self.host = host
host.bridge.addMethod("radiocolLaunch", ".plugin", in_sign='asss', out_sign='', method=self.prepareRoom, async=True)
host.bridge.addMethod("radiocolCreate", ".plugin", in_sign='sass', out_sign='', method=self.createGame)
host.bridge.addMethod("radiocolLaunch", ".plugin", in_sign='asss', out_sign='', method=self._prepareRoom, async=True)
host.bridge.addMethod("radiocolCreate", ".plugin", in_sign='sass', out_sign='', method=self._createGame)
host.bridge.addMethod("radiocolSongAdded", ".plugin", in_sign='sss', out_sign='', method=self.radiocolSongAdded, async=True)
host.bridge.addSignal("radiocolPlayers", ".plugin", signature='ssass') # room_jid, referee, players, profile
host.bridge.addSignal("radiocolStarted", ".plugin", signature='ssasais') # room_jid, referee, players, [QUEUE_TO_START, QUEUE_LIMIT], profile
@ -96,7 +96,7 @@ class Radiocol(object):
@param profile_key: %(doc_profile_key)s
@return: a Deferred instance
"""
#XXX: this is a Q&D way for the proof of concept. In the future, the song should
# XXX: this is a Q&D way for the proof of concept. In the future, the song should
# be streamed to the backend using XMPP file copy
# Here we cheat because we know we are on the same host, and we don't
# check data. Referee will have to parse the song himself to check it
@ -115,7 +115,7 @@ class Radiocol(object):
else:
song = OggVorbis(song_path)
except (OggVorbisHeaderError, HeaderNotFoundError):
#this file is not ogg vorbis nor mp3, we reject it
# this file is not ogg vorbis nor mp3, we reject it
self.deleteFile(song_path) # FIXME: same host trick (see note above)
return defer.fail(exceptions.DataError(D_("The uploaded file has been rejected, only Ogg Vorbis and MP3 songs are accepted.")))
@ -125,16 +125,16 @@ class Radiocol(object):
'album': song.get("album", ["Unknown"])[0],
'length': str(song.info.length)
}
radio_data = self.games[jid.JID(referee).userhost()] # FIXME: referee comes from Libervia's client side, it's unsecure
radio_data = self.games[jid.JID(referee).userhostJID()] # FIXME: referee comes from Libervia's client side, it's unsecure
radio_data['to_delete'][attrs['filename']] = song_path # FIXME: works only because of the same host trick, see the note under the docstring
return self.send(jid.JID(referee), ('', 'song_added'), attrs, profile=profile)
def playNext(self, room_jid, profile):
""""Play next song in queue if exists, and put a timer
which trigger after the song has been played to play next one"""
#TODO: songs need to be erased once played or found invalids
# TODO: songs need to be erased once played or found invalids
# ==> unlink done the Q&D way with the same host trick (see above)
radio_data = self.games[room_jid.userhost()]
radio_data = self.games[room_jid]
if len(radio_data['players']) == 0:
log.debug(_('No more participants in the radiocol: cleaning data'))
radio_data['queue'] = []
@ -143,7 +143,7 @@ class Radiocol(object):
radio_data['to_delete'] = {}
queue = radio_data['queue']
if not queue:
#nothing left to play, we need to wait for uploads
# nothing left to play, we need to wait for uploads
radio_data['playing'] = None
return
song = queue.pop(0)
@ -153,12 +153,12 @@ class Radiocol(object):
radio_data['playing_time'] = time.time()
if not radio_data['upload'] and len(queue) < QUEUE_LIMIT:
#upload is blocked and we now have resources to get more, we reactivate it
# upload is blocked and we now have resources to get more, we reactivate it
self.send(room_jid, ('', 'upload_ok'), profile=profile)
radio_data['upload'] = True
reactor.callLater(length, self.playNext, room_jid, profile)
#we wait more than the song length to delete the file, to manage poorly reactive networks/clients
# we wait more than the song length to delete the file, to manage poorly reactive networks/clients
reactor.callLater(length + 90, self.deleteFile, filename, radio_data) # FIXME: same host trick (see above)
def deleteFile(self, filename, radio_data=None):
@ -185,17 +185,17 @@ class Radiocol(object):
def room_game_cmd(self, mess_elt, profile):
from_jid = jid.JID(mess_elt['from'])
room_jid = jid.JID(from_jid.userhost())
nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid.userhost(), profile)
room_jid = from_jid.userhostJID()
nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid, profile)
radio_elt = mess_elt.firstChildElement()
radio_data = self.games[room_jid.userhost()]
radio_data = self.games[room_jid]
if 'queue' in radio_data:
queue = radio_data['queue']
from_referee = self.isReferee(room_jid.userhost(), from_jid.resource)
to_referee = self.isReferee(room_jid.userhost(), jid.JID(mess_elt['to']).user)
is_player = self.isPlayer(room_jid.userhost(), nick)
from_referee = self.isReferee(room_jid, from_jid.resource)
to_referee = self.isReferee(room_jid, jid.JID(mess_elt['to']).user)
is_player = self.isPlayer(room_jid, nick)
for elt in radio_elt.elements():
if not from_referee and not (to_referee and elt.name == 'song_added'):
continue # sender must be referee, expect when a song is submitted
@ -219,20 +219,20 @@ class Radiocol(object):
elif elt.name == 'upload_ok':
self.host.bridge.radiocolUploadOk(room_jid.userhost(), profile)
elif elt.name == 'song_added': # a song has been added
#FIXME: we are KISS for the proof of concept: every song is added, to a limit of 3 in queue.
# FIXME: we are KISS for the proof of concept: every song is added, to a limit of 3 in queue.
# Need to manage some sort of rules to allow peoples to send songs
if len(queue) >= QUEUE_LIMIT:
#there are already too many songs in queue, we reject this one
#FIXME: add an error code
# there are already too many songs in queue, we reject this one
# FIXME: add an error code
self.send(from_jid, ('', 'song_rejected'), {'reason': "Too many songs in queue"}, profile=profile)
return
#The song is accepted and added in queue
# The song is accepted and added in queue
preload_elt = self.__create_preload_elt(from_jid.resource, elt)
queue.append(preload_elt)
if len(queue) >= QUEUE_LIMIT:
#We are at the limit, we refuse new upload until next play
# We are at the limit, we refuse new upload until next play
self.send(room_jid, ('', 'no_upload'), profile=profile)
radio_data['upload'] = False
@ -244,8 +244,8 @@ class Radiocol(object):
else:
log.error(_('Unmanaged game element: %s') % elt.name)
def getSyncDataForPlayer(self, room_jid_s, nick):
game_data = self.games[room_jid_s]
def getSyncDataForPlayer(self, room_jid, nick):
game_data = self.games[room_jid]
elements = []
if game_data['playing']:
preload = copy.deepcopy(game_data['playing'])

View file

@ -21,7 +21,7 @@ from sat.core.i18n import _
from sat.core.constants import Const as C
from sat.core.log import getLogger
log = getLogger(__name__)
from twisted.words.protocols.jabber.jid import JID
from twisted.words.protocols.jabber import jid
from twisted.words.xish import domish
from twisted.internet import defer
from time import time
@ -51,7 +51,7 @@ PLUGIN_INFO = {
class RoomGame(object):
"""This class is used to help launching a MUC game.
Bridge methods callbacks: prepareRoom, playerReady, createGame
Bridge methods callbacks: _prepareRoom, _playerReady, _createGame
Triggered methods: userJoinedTrigger, userLeftTrigger
Also called from subclasses: newRound
@ -107,7 +107,7 @@ class RoomGame(object):
self.game_init = game_init
self.player_init = player_init
self.games = {}
self.invitations = {} # list of couple (x, y) with x the time and y a list of users
self.invitations = {} # values are a couple (x, y) with x the time and y a list of users
# These are the default settings, which can be overwritten by child class after initialization
self.invite_mode = self.FROM_PLAYERS if self.player_init == {} else self.FROM_NONE
@ -126,129 +126,134 @@ class RoomGame(object):
def _createOrInvite(self, room, other_players, profile):
"""
This is called only when someone explicitly wants to play.
The game will not be created if one already exists in the room,
also its creation could be postponed until all the expected players
join the room (in that case it will be created from userJoinedTrigger).
@param room: instance of wokkel.muc.Room
@param other_players: list for other players JID userhosts
@param room (wokkel.muc.Room): the room
@param other_players (list[jid.JID]): list of the other players JID (bare)
"""
user_jid = self.host.getJidNStream(profile)[0]
room_jid_s = room.occupantJID.userhost()
nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid_s, profile)
room_jid = room.occupantJID.userhostJID()
nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid, profile)
nicks = [nick]
if self._gameExists(room_jid_s):
if not self._checkJoinAuth(room_jid_s, user_jid.userhost(), nick):
if self._gameExists(room_jid):
if not self._checkJoinAuth(room_jid, user_jid, nick):
return
nicks.extend(self._invitePlayers(room, other_players, nick, profile))
self._updatePlayers(room_jid_s, nicks, True, profile)
self._updatePlayers(room_jid, nicks, True, profile)
else:
self._initGame(room_jid_s, nick)
self._initGame(room_jid, nick)
(auth, waiting, missing) = self._checkWaitAuth(room, other_players)
nicks.extend(waiting)
nicks.extend(self._invitePlayers(room, missing, nick, profile))
if auth:
self.createGame(room_jid_s, nicks, profile)
self.createGame(room_jid, nicks, profile)
else:
self._updatePlayers(room_jid_s, nicks, False, profile)
self._updatePlayers(room_jid, nicks, False, profile)
def _initGame(self, room_jid_s, referee_nick):
"""Important: do not add the referee to 'players' yet. For a
<players /> message to be emitted whenever a new player is joining,
it is necessary to not modify 'players' outside of _updatePlayers.
def _initGame(self, room_jid, referee_nick):
"""
referee = room_jid_s + '/' + referee_nick
self.games[room_jid_s] = {'referee': referee, 'players': [], 'started': False, 'status': {}}
self.games[room_jid_s].update(copy.deepcopy(self.game_init))
self.invitations.setdefault(room_jid_s, [])
def _gameExists(self, room_jid_s, started=False):
@param room_jid (jid.JID): JID of the room
@param referee_nick (unicode): nickname of the referee
"""
# Important: do not add the referee to 'players' yet. For a
# <players /> message to be emitted whenever a new player is joining,
# it is necessary to not modify 'players' outside of _updatePlayers.
referee_jid = jid.JID(room_jid.userhost() + '/' + referee_nick)
self.games[room_jid] = {'referee': referee_jid, 'players': [], 'started': False, 'status': {}}
self.games[room_jid].update(copy.deepcopy(self.game_init))
self.invitations.setdefault(room_jid, [])
def _gameExists(self, room_jid, started=False):
"""Return True if a game has been initialized/started.
@param started: if False, the game must be initialized to return True,
otherwise it must be initialized and started with createGame.
@return: True if a game is initialized/started in that room"""
return room_jid_s in self.games and (not started or self.games[room_jid_s]['started'])
return room_jid in self.games and (not started or self.games[room_jid]['started'])
def _checkJoinAuth(self, room_jid_s, user_jid_s=None, nick="", verbose=False):
def _checkJoinAuth(self, room_jid, user_jid=None, nick="", verbose=False):
"""Checks if this profile is allowed to join the game.
The parameter nick is used to check if the user is already
a player in that game. When this method is called from
userJoinedTrigger, nick is also used to check the user
identity instead of user_jid_s (see TODO comment below).
@param room_jid_s: the room hosting the game
@param user_jid_s: JID userhost of the user
@param nick: nick of the user
@param room_jid (jid.JID): the JID of the room hosting the game
@param user_jid (jid.JID): JID of the user
@param nick (unicode): nick of the user
@return: True if this profile can join the game
"""
auth = False
if not self._gameExists(room_jid_s):
if not self._gameExists(room_jid):
auth = False
elif self.join_mode == self.ALL or self.isPlayer(room_jid_s, nick):
elif self.join_mode == self.ALL or self.isPlayer(room_jid, nick):
auth = True
elif self.join_mode == self.INVITED:
user_jid_s = JID(user_jid_s).userhost()
# considering all the batches of invitations
for invitations in self.invitations[room_jid_s]:
if user_jid_s is not None:
if user_jid_s in invitations[1]:
for invitations in self.invitations[room_jid]:
if user_jid is not None:
if user_jid.userhostJID() in invitations[1]:
auth = True
break
else:
# TODO: that's not secure enough but what to do if
# wokkel.muc.User's 'entity' attribute is not set?!
if nick in [JID(invited).user for invited in invitations[1]]:
if nick in [invited.user for invited in invitations[1]]:
auth = True
break
if not auth and (verbose or _DEBUG):
log.debug(_("%(user)s not allowed to join the game %(game)s in %(room)s") % {'user': user_jid_s or nick, 'game': self.name, 'room': room_jid_s})
log.debug(_("%(user)s not allowed to join the game %(game)s in %(room)s") % {'user': user_jid.userhost() or nick, 'game': self.name, 'room': room_jid.userhost()})
return auth
def _updatePlayers(self, room_jid_s, nicks, sync, profile):
def _updatePlayers(self, room_jid, nicks, sync, profile):
"""Update the list of players and signal to the room that some players joined the game.
If sync is True, the news players are synchronized with the game data they have missed.
Remark: self.games[room_jid_s]['players'] should not be modified outside this method.
@param room_jid_s: room userhost
@param nicks: list of players nicks in the room (referee included, in first position)
@param sync: set to True to send synchronization data to the new players
@param profile
Remark: self.games[room_jid]['players'] should not be modified outside this method.
@param room_jid (jid.JID): JID of the room
@param nicks (list[unicode]): list of players nicks in the room (referee included, in first position)
@param sync (bool): set to True to send synchronization data to the new players
@param profile (unicode): %(doc_profile)s
"""
if nicks == []:
return
# this is better than set(nicks).difference(...) as it keeps the order
new_nicks = [nick for nick in nicks if nick not in self.games[room_jid_s]['players']]
new_nicks = [nick for nick in nicks if nick not in self.games[room_jid]['players']]
if len(new_nicks) == 0:
return
def setStatus(status):
for nick in new_nicks:
self.games[room_jid_s]['status'][nick] = status
self.games[room_jid]['status'][nick] = status
sync = sync and self._gameExists(room_jid_s, True) and len(self.games[room_jid_s]['players']) > 0
sync = sync and self._gameExists(room_jid, True) and len(self.games[room_jid]['players']) > 0
setStatus('desync' if sync else 'init')
self.games[room_jid_s]['players'].extend(new_nicks)
self._synchronizeRoom(room_jid_s, [JID(room_jid_s)], profile)
self.games[room_jid]['players'].extend(new_nicks)
self._synchronizeRoom(room_jid, [room_jid], profile)
if sync:
setStatus('init')
def _synchronizeRoom(self, room_jid_s, recipients, profile):
def _synchronizeRoom(self, room_jid, recipients, profile):
"""Communicate the list of players to the whole room or only to some users,
also send the synchronization data to the players who recently joined the game.
@param room_jid_s: room userhost
@recipients: list of JIDs, the recipients of the message could be:
- room JID
- room JID + "/" + user nick
@param profile
@param room_jid (jid.JID): JID of the room
@recipients (list[jid.JID]): list of JIDs, the recipients of the message could be:
- room JID
- room JID + "/" + user nick
@param profile (unicode): %(doc_profile)s
"""
if self._gameExists(room_jid_s, started=True):
element = self._createStartElement(self.games[room_jid_s]['players'])
if self._gameExists(room_jid, started=True):
element = self._createStartElement(self.games[room_jid]['players'])
else:
element = self._createStartElement(self.games[room_jid_s]['players'], name="players")
element = self._createStartElement(self.games[room_jid]['players'], name="players")
elements = [(element, None, None)]
sync_args = []
sync_data = self._getSyncData(room_jid_s)
sync_data = self._getSyncData(room_jid)
for nick in sync_data:
user_jid = JID(room_jid_s + '/' + nick)
user_jid = jid.JID(room_jid.userhost() + '/' + nick)
if user_jid in recipients:
user_elements = copy.deepcopy(elements)
for child in sync_data[nick]:
@ -263,18 +268,18 @@ class RoomGame(object):
for args, kwargs in sync_args:
self._sendElements(*args, **kwargs)
def _getSyncData(self, room_jid_s, force_nicks=None):
def _getSyncData(self, room_jid, force_nicks=None):
"""The synchronization data are returned for each player who
has the state 'desync' or if he's been contained by force_nicks.
@param room_jid_s: room userhost
@param room_jid (jid.JID): JID of the room
@param force_nicks: force the synchronization for this list of the nicks
@return: a mapping between player nicks and a list of elements to
be sent by self._synchronizeRoom for the game to be synchronized.
"""
if not self._gameExists(room_jid_s):
if not self._gameExists(room_jid):
return {}
data = {}
status = self.games[room_jid_s]['status']
status = self.games[room_jid]['status']
nicks = [nick for nick in status if status[nick] == 'desync']
if force_nicks is None:
force_nicks = []
@ -282,14 +287,14 @@ class RoomGame(object):
if nick not in nicks:
nicks.append(nick)
for nick in nicks:
elements = self.getSyncDataForPlayer(room_jid_s, nick)
elements = self.getSyncDataForPlayer(room_jid, nick)
if elements:
data[nick] = elements
return data
def getSyncDataForPlayer(self, room_jid_s, nick):
def getSyncDataForPlayer(self, room_jid, nick):
"""This method may (and should probably) be overwritten by a child class.
@param room_jid_s: room userhost
@param room_jid (jid.JID): JID of the room
@param nick: the nick of the player to be synchronized
@return: a list of elements to synchronize this player with the game.
"""
@ -297,20 +302,19 @@ class RoomGame(object):
def _invitePlayers(self, room, other_players, nick, profile):
"""Invite players to a room, associated game may exist or not.
@param room: wokkel.muc.Room instance
@param other_players: list of JID userhosts to invite
@param nick: nick of the user who send the invitation
@return: list of room nicks for invited players who are already in the room
@param room (wokkel.muc.Room): the room
@param other_players (list[jid.JID]): list of the players to invite
@param nick (unicode): nick of the user who send the invitation
@return: list[unicode] of room nicks for invited players who are already in the room
"""
room_jid = room.occupantJID.userhostJID()
room_jid_s = room.occupantJID.userhost()
if not self._checkInviteAuth(room_jid_s, nick):
if not self._checkInviteAuth(room_jid, nick):
return []
# TODO: remove invitation waiting for too long, using the time data
players_jids = [JID(player) for player in other_players]
self.invitations[room_jid_s].append((time(), [player.userhost() for player in players_jids]))
self.invitations[room_jid].append((time(), [player.userhostJID() for player in other_players]))
nicks = []
for player_jid in [player.userhostJID() for player in players_jids]:
for player_jid in [player.userhostJID() for player in other_players]:
# TODO: find a way to make it secure
other_nick = self.host.plugins["XEP-0045"].getRoomNickOfUser(room, player_jid, secure=self.testing)
if other_nick is None:
@ -319,58 +323,59 @@ class RoomGame(object):
nicks.append(other_nick)
return nicks
def _checkInviteAuth(self, room_jid_s, nick, verbose=False):
def _checkInviteAuth(self, room_jid, nick, verbose=False):
"""Checks if this user is allowed to invite players
@param room_jid_s: room userhost
@param room_jid (jid.JID): JID of the room
@param nick: user nick in the room
@param verbose: display debug message
@return: True if the user is allowed to invite other players
"""
auth = False
if self.invite_mode == self.FROM_ALL or not self._gameExists(room_jid_s):
if self.invite_mode == self.FROM_ALL or not self._gameExists(room_jid):
auth = True
elif self.invite_mode == self.FROM_NONE:
auth = not self._gameExists(room_jid_s, started=True) and self.isReferee(room_jid_s, nick)
auth = not self._gameExists(room_jid, started=True) and self.isReferee(room_jid, nick)
elif self.invite_mode == self.FROM_REFEREE:
auth = self.isReferee(room_jid_s, nick)
auth = self.isReferee(room_jid, nick)
elif self.invite_mode == self.FROM_PLAYERS:
auth = self.isPlayer(room_jid_s, nick)
auth = self.isPlayer(room_jid, nick)
if not auth and (verbose or _DEBUG):
log.debug(_("%(user)s not allowed to invite for the game %(game)s in %(room)s") % {'user': nick, 'game': self.name, 'room': room_jid_s})
log.debug(_("%(user)s not allowed to invite for the game %(game)s in %(room)s") % {'user': nick, 'game': self.name, 'room': room_jid.userhost()})
return auth
def isReferee(self, room_jid_s, nick):
def isReferee(self, room_jid, nick):
"""Checks if the player with this nick is the referee for the game in this room"
@param room_jid_s: room userhost
@param room_jid (jid.JID): room JID
@param nick: user nick in the room
@return: True if the user is the referee of the game in this room
"""
if not self._gameExists(room_jid_s):
if not self._gameExists(room_jid):
return False
return room_jid_s + '/' + nick == self.games[room_jid_s]['referee']
return jid.JID(room_jid.userhost() + '/' + nick) == self.games[room_jid]['referee']
def isPlayer(self, room_jid_s, nick):
def isPlayer(self, room_jid, nick):
"""Checks if the user with this nick is a player for the game in this room.
@param room_jid_s: room userhost
@param room_jid (jid.JID): JID of the room
@param nick: user nick in the room
@return: True if the user is a player of the game in this room
"""
if not self._gameExists(room_jid_s):
if not self._gameExists(room_jid):
return False
# Important: the referee is not in the 'players' list right after
# the game initialization, that's why we do also check with isReferee
return nick in self.games[room_jid_s]['players'] or self.isReferee(room_jid_s, nick)
return nick in self.games[room_jid]['players'] or self.isReferee(room_jid, nick)
def _checkWaitAuth(self, room, other_players, verbose=False):
"""Check if we must wait for other players before starting the game.
@param room: wokkel.muc.Room instance
@param other_players: list of players JID userhosts without the referee
@param verbose: display debug message
@param room (wokkel.muc.Room): the room
@param other_players (list[jid.JID]): list of the players without the referee
@param verbose (bool): display debug message
@return: (x, y, z) with:
x: False if we must wait, True otherwise
y: the nicks of the players that have been checked and confirmed
z: the players that have not been checked or that are missing
x: False if we must wait, True otherwise
y: the nicks of the players that have been checked and confirmed
z: the JID of the players that have not been checked or that are missing
"""
if self.wait_mode == self.FOR_NONE or other_players == []:
result = (True, [], other_players)
@ -388,20 +393,25 @@ class RoomGame(object):
def getUniqueName(self, muc_service=None, profile_key=C.PROF_KEY_NONE):
"""Generate unique room name
@param muc_service: you can leave empty to autofind the muc service
@param profile_key: %(doc_profile_key)s
@return: a unique name for a new room to be created
@param muc_service (jid.JID): you can leave empty to autofind the muc service
@param profile_key (unicode): %(doc_profile_key)s
@return: jid.JID (unique name for a new room to be created)
"""
# FIXME: jid.JID must be used instead of strings
room = self.host.plugins["XEP-0045"].getUniqueName(muc_service, profile_key=profile_key)
return "sat_%s_%s" % (self.name.lower(), room.full())
return jid.JID("sat_%s_%s" % (self.name.lower(), room.userhost()))
def prepareRoom(self, other_players=None, room_jid_s=None, profile_key=C.PROF_KEY_NONE):
def _prepareRoom(self, other_players=None, room_jid_s='', profile_key=C.PROF_KEY_NONE):
room_jid = jid.JID(room_jid_s) if room_jid_s else None
other_players = [jid.JID(player).userhostJID() for player in other_players]
return self.prepareRoom(other_players, room_jid, profile_key)
def prepareRoom(self, other_players=None, room_jid=None, profile_key=C.PROF_KEY_NONE):
"""Prepare the room for a game: create it if it doesn't exist and invite players.
@param other_players: list for other players JID userhosts
@param room_jid_s: JID userhost of the room, or None to generate a unique name
@param profile_key
@param other_players (list[JID]): list of other players JID (bare)
@param room_jid (jid.JID): JID of the room, or None to generate a unique name
@param profile_key (unicode): %(doc_profile_key)s
"""
log.debug(_('Preparing room for %s game') % self.name)
profile = self.host.memory.getProfileName(profile_key)
@ -413,19 +423,18 @@ class RoomGame(object):
def roomJoined(room):
"""@param room: instance of wokkel.muc.Room"""
self._createOrInvite(room, [JID(player).userhost() for player in other_players], profile)
self._createOrInvite(room, other_players, profile)
# Create/join the given room, or a unique generated one if no room is specified.
if room_jid_s is not None and room_jid_s != "": # a room name has been specified
if room_jid_s in self.host.plugins["XEP-0045"].clients[profile].joined_rooms:
roomJoined(self.host.plugins["XEP-0045"].clients[profile].joined_rooms[room_jid_s])
return defer.succeed(None)
if room_jid is None:
room_jid = self.getUniqueName(profile_key=profile_key)
else:
room_jid_s = self.getUniqueName(profile_key=profile_key)
if room_jid_s == "":
if room_jid in self.host.plugins["XEP-0045"].clients[profile].joined_rooms:
roomJoined(self.host.plugins["XEP-0045"].clients[profile].joined_rooms[room_jid])
return defer.succeed(None)
user_jid = self.host.getJidNStream(profile)[0]
d = self.host.plugins["XEP-0045"].join(JID(room_jid_s), user_jid.user, {}, profile)
d = self.host.plugins["XEP-0045"].join(room_jid, user_jid.user, {}, profile)
return d.addCallback(roomJoined)
def userJoinedTrigger(self, room, user, profile):
@ -435,29 +444,29 @@ class RoomGame(object):
@user: wokkel.muc.User object. user.nick is a unicode and user.entity a JID
@return: True to not interrupt the main process.
"""
room_jid_s = room.occupantJID.userhost()
room_jid = room.occupantJID.userhostJID()
profile_nick = room.occupantJID.resource
if not self.isReferee(room_jid_s, profile_nick):
if not self.isReferee(room_jid, profile_nick):
return True # profile is not the referee
if not self._checkJoinAuth(room_jid_s, user.entity.userhost() if user.entity else None, user.nick):
if not self._checkJoinAuth(room_jid, user.entity if user.entity else None, user.nick):
# user not allowed but let him know that we are playing :p
self._synchronizeRoom(room_jid_s, [JID(room_jid_s + '/' + user.nick)], profile)
self._synchronizeRoom(room_jid, [jid.JID(room_jid.userhost() + '/' + user.nick)], profile)
return True
if self.wait_mode == self.FOR_ALL:
# considering the last batch of invitations
batch = len(self.invitations[room_jid_s]) - 1
batch = len(self.invitations[room_jid]) - 1
if batch < 0:
log.error("Invitations from %s to play %s in %s have been lost!" % (profile_nick, self.name, room_jid_s))
log.error("Invitations from %s to play %s in %s have been lost!" % (profile_nick, self.name, room_jid.userhost()))
return True
other_players = self.invitations[room_jid_s][batch][1]
other_players = self.invitations[room_jid][batch][1]
(auth, nicks, dummy) = self._checkWaitAuth(room, other_players)
if auth:
del self.invitations[room_jid_s][batch]
del self.invitations[room_jid][batch]
nicks.insert(0, profile_nick) # add the referee
self.createGame(room_jid_s, nicks, profile_key=profile)
self.createGame(room_jid, nicks, profile_key=profile)
return True
# let the room know that a new player joined
self._updatePlayers(room_jid_s, [user.nick], True, profile)
self._updatePlayers(room_jid, [user.nick], True, profile)
return True
def userLeftTrigger(self, room, user, profile):
@ -467,89 +476,95 @@ class RoomGame(object):
@user: wokkel.muc.User object. user.nick is a unicode and user.entity a JID
@return: True to not interrupt the main process.
"""
room_jid_s = room.occupantJID.userhost()
room_jid = room.occupantJID.userhostJID()
profile_nick = room.occupantJID.resource
if not self.isReferee(room_jid_s, profile_nick):
if not self.isReferee(room_jid, profile_nick):
return True # profile is not the referee
if self.isPlayer(room_jid_s, user.nick):
if self.isPlayer(room_jid, user.nick):
try:
self.games[room_jid_s]['players'].remove(user.nick)
self.games[room_jid]['players'].remove(user.nick)
except ValueError:
pass
if len(self.games[room_jid_s]['players']) == 0:
del self.games[room_jid_s] # finish the game
if len(self.games[room_jid]['players']) == 0:
return True
if self.wait_mode == self.FOR_ALL:
# allow this user to join the game again
user_jid_s = user.entity.userhost()
if len(self.invitations[room_jid_s]) == 0:
self.invitations[room_jid_s].append((time(), [user_jid_s]))
user_jid = user.entity.userhostJID()
if len(self.invitations[room_jid]) == 0:
self.invitations[room_jid].append((time(), [user_jid]))
else:
batch = 0 # add to the first batch of invitations
if user_jid_s not in self.invitations[room_jid_s][batch][1]:
self.invitations[room_jid_s][batch][1].append(user_jid_s)
if user_jid not in self.invitations[room_jid][batch][1]:
self.invitations[room_jid][batch][1].append(user_jid)
return True
def _checkCreateGameAndInit(self, room_jid_s, profile):
"""Check if that profile can create the game. If the game can be created but is not initialized yet, this method will also do the initialization.
def _checkCreateGameAndInit(self, room_jid, profile):
"""Check if that profile can create the game. If the game can be created
but is not initialized yet, this method will also do the initialization.
@param room_jid_s: room userhost
@param room_jid (jid.JID): JID of the room
@param profile
@return: a couple (create, sync) with:
- create: set to True to allow the game creation
- sync: set to True to advice a game synchronization
"""
user_nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid_s, profile)
user_nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid, profile)
if not user_nick:
log.error('Internal error: profile %s has not joined the room %s' % (profile, room_jid_s))
log.error('Internal error: profile %s has not joined the room %s' % (profile, room_jid.userhost()))
return False, False
if self._gameExists(room_jid_s):
is_referee = self.isReferee(room_jid_s, user_nick)
if self._gameExists(room_jid_s, started=True):
log.warning(_("%(game)s game already created in room %(room)s") % {'game': self.name, 'room': room_jid_s})
if self._gameExists(room_jid):
is_referee = self.isReferee(room_jid, user_nick)
if self._gameExists(room_jid, started=True):
log.warning(_("%(game)s game already created in room %(room)s") % {'game': self.name, 'room': room_jid.userhost()})
return False, is_referee
elif not is_referee:
log.warning(_("%(game)s game in room %(room)s can only be created by %(user)s") % {'game': self.name, 'room': room_jid_s, 'user': user_nick})
log.warning(_("%(game)s game in room %(room)s can only be created by %(user)s") % {'game': self.name, 'room': room_jid.userhost(), 'user': user_nick})
return False, False
else:
self._initGame(room_jid_s, user_nick)
self._initGame(room_jid, user_nick)
return True, False
def createGame(self, room_jid_s, nicks=None, profile_key=C.PROF_KEY_NONE):
def _createGame(self, room_jid_s, nicks=None, profile_key=C.PROF_KEY_NONE):
self.createGame(jid.JID(room_jid_s), nicks, profile_key)
def createGame(self, room_jid, nicks=None, profile_key=C.PROF_KEY_NONE):
"""Create a new game.
This can be called directly from a frontend and skips all the checks and invitation system,
but the game must not exist and all the players must be in the room already.
@param room_jid: JID userhost of the room
@param nicks: list of players nicks in the room (referee included, in first position)
@param profile_key: %(doc_profile_key)s
@param room_jid (jid.JID): JID of the room
@param nicks (list[unicode]): list of players nicks in the room (referee included, in first position)
@param profile_key (unicode): %(doc_profile_key)s
"""
log.debug(_("Creating %(game)s game in room %(room)s") % {'game': self.name, 'room': room_jid_s})
log.debug(_("Creating %(game)s game in room %(room)s") % {'game': self.name, 'room': room_jid})
profile = self.host.memory.getProfileName(profile_key)
if not profile:
log.error(_("profile %s is unknown") % profile_key)
return
(create, sync) = self._checkCreateGameAndInit(room_jid_s, profile)
(create, sync) = self._checkCreateGameAndInit(room_jid, profile)
if nicks is None:
nicks = []
if not create:
if sync:
self._updatePlayers(room_jid_s, nicks, True, profile)
self._updatePlayers(room_jid, nicks, True, profile)
return
self.games[room_jid_s]['started'] = True
self._updatePlayers(room_jid_s, nicks, False, profile)
self.games[room_jid]['started'] = True
self._updatePlayers(room_jid, nicks, False, profile)
if self.player_init:
# specific data to each player (score, private data)
self.games[room_jid_s].setdefault('players_data', {})
self.games[room_jid].setdefault('players_data', {})
for nick in nicks:
# The dict must be COPIED otherwise it is shared between all users
self.games[room_jid_s]['players_data'][nick] = copy.deepcopy(self.player_init)
self.games[room_jid]['players_data'][nick] = copy.deepcopy(self.player_init)
def playerReady(self, player, referee, profile_key=C.PROF_KEY_NONE):
def _playerReady(self, player_nick, referee_jid_s, profile_key=C.PROF_KEY_NONE):
self.playerReady(player_nick, jid.JID(referee_jid_s), profile_key)
def playerReady(self, player_nick, referee_jid, profile_key=C.PROF_KEY_NONE):
"""Must be called when player is ready to start a new game
@param player: the player nick in the room
@param referee: referee userhost
@param referee_jid (jid.JID): JID of the referee
"""
profile = self.host.memory.getProfileName(profile_key)
if not profile:
@ -557,7 +572,7 @@ class RoomGame(object):
return
log.debug('new player ready: %s' % profile)
# TODO: we probably need to add the game and room names in the sent message
self.send(JID(referee), 'player_ready', {'player': player}, profile=profile)
self.send(referee_jid, 'player_ready', {'player': player_nick}, profile=profile)
def newRound(self, room_jid, data, profile):
"""Launch a new round (reinit the user data)
@ -569,7 +584,7 @@ class RoomGame(object):
@param profile
"""
log.debug(_('new round for %s game') % self.name)
game_data = self.games[room_jid.userhost()]
game_data = self.games[room_jid]
players = game_data['players']
players_data = game_data['players_data']
game_data['stage'] = "init"
@ -578,7 +593,7 @@ class RoomGame(object):
if isinstance(msg_elts, dict):
for player in players:
to_jid = JID(room_jid.userhost() + "/" + player) # FIXME: gof:
to_jid = jid.JID(room_jid.userhost() + "/" + player) # FIXME: gof:
elem = msg_elts[player] if isinstance(msg_elts[player], domish.Element) else None
self.send(to_jid, elem, profile=profile)
elif isinstance(msg_elts, domish.Element):

View file

@ -59,12 +59,12 @@ class Tarot(object):
self._sessions = memory.Sessions()
self.inheritFromRoomGame(host)
RoomGame._init_(self, host, PLUGIN_INFO, (NS_CG, CG_TAG),
game_init={'hand_size': 18, 'init_player': 0, 'current_player': None, 'contrat': None, 'stage': None},
player_init={'score': 0})
game_init={'hand_size': 18, 'init_player': 0, 'current_player': None, 'contrat': None, 'stage': None},
player_init={'score': 0})
self.contrats = [_('Passe'), _('Petite'), _('Garde'), _('Garde Sans'), _('Garde Contre')]
host.bridge.addMethod("tarotGameLaunch", ".plugin", in_sign='asss', out_sign='', method=self.prepareRoom, async=True) # args: players, room_jid, profile
host.bridge.addMethod("tarotGameCreate", ".plugin", in_sign='sass', out_sign='', method=self.createGame) # args: room_jid, players, profile
host.bridge.addMethod("tarotGameReady", ".plugin", in_sign='sss', out_sign='', method=self.playerReady) # args: player, referee, profile
host.bridge.addMethod("tarotGameLaunch", ".plugin", in_sign='asss', out_sign='', method=self._prepareRoom, async=True) # args: players, room_jid, profile
host.bridge.addMethod("tarotGameCreate", ".plugin", in_sign='sass', out_sign='', method=self._createGame) # args: room_jid, players, profile
host.bridge.addMethod("tarotGameReady", ".plugin", in_sign='sss', out_sign='', method=self._playerReady) # args: player, referee, profile
host.bridge.addMethod("tarotGamePlayCards", ".plugin", in_sign='ssa(ss)s', out_sign='', method=self.play_cards) # args: player, referee, cards, profile
host.bridge.addSignal("tarotGamePlayers", ".plugin", signature='ssass') # args: room_jid, referee, players, profile
host.bridge.addSignal("tarotGameStarted", ".plugin", signature='ssass') # args: room_jid, referee, players, profile
@ -84,7 +84,6 @@ class Tarot(object):
self.__choose_contrat_id = host.registerCallback(self._contratChoosed, with_data=True)
self.__score_id = host.registerCallback(self._scoreShowed, with_data=True)
def __card_list_to_xml(self, cards_list, elt_name):
"""Convert a card list to domish element"""
cards_list_elt = domish.Element((None, elt_name))
@ -183,17 +182,17 @@ class Tarot(object):
@param game_data: data of the game
@param played: cards currently on the table
@param winner: nick of the trick winner"""
#TODO: manage the case where excuse is played on the last trick (and lost)
# TODO: manage the case where excuse is played on the last trick (and lost)
players_data = game_data['players_data']
excuse = TarotCard(("atout", "excuse"))
#we first check if the Excuse was already played
#and if somebody is waiting for a card
# we first check if the Excuse was already played
# and if somebody is waiting for a card
for player in game_data['players']:
if players_data[player]['wait_for_low']:
#the excuse owner has to give a card to somebody
# the excuse owner has to give a card to somebody
if winner == player:
#the excuse owner win the trick, we check if we have something to give
# the excuse owner win the trick, we check if we have something to give
for card in played:
if card.points == 0.5:
pl_waiting = players_data[player]['wait_for_low']
@ -203,8 +202,8 @@ class Tarot(object):
return
return
if not excuse in played:
#the Excuse is not on the table, nothing to do
if excuse not in played:
# the Excuse is not on the table, nothing to do
return
excuse_player = None # Who has played the Excuse ?
@ -216,15 +215,15 @@ class Tarot(object):
if excuse_player == winner:
return # the excuse player win the trick, nothing to do
#first we remove the excuse from played cards
# first we remove the excuse from played cards
played.remove(excuse)
#then we give it back to the original owner
# then we give it back to the original owner
owner_levees = players_data[excuse_player]['levees']
owner_levees.append(excuse)
#finally we give a low card to the trick winner
# finally we give a low card to the trick winner
low_card = None
#We look backward in cards won by the Excuse owner to
#find a low value card
# We look backward in cards won by the Excuse owner to
# find a low value card
for card_idx in range(len(owner_levees) - 1, -1, -1):
if owner_levees[card_idx].points == 0.5:
low_card = owner_levees[card_idx]
@ -233,7 +232,7 @@ class Tarot(object):
log.debug(_('Player %(excuse_owner)s give %(card_waited)s to %(player_waiting)s for Excuse compensation') % {"excuse_owner": excuse_player, "card_waited": low_card, "player_waiting": winner})
break
if not low_card: # The player has no low card yet
#TODO: manage case when player never win a trick with low card
# TODO: manage case when player never win a trick with low card
players_data[excuse_player]['wait_for_low'] = winner
log.debug(_("%(excuse_owner)s keep the Excuse but has not card to give, %(winner)s is waiting for one") % {'excuse_owner': excuse_player, 'winner': winner})
@ -265,7 +264,7 @@ class Tarot(object):
bouts.append(card.value)
score += card.points
#We we do a basic check on score calculation
# We do a basic check on score calculation
check_score = 0
defenseurs = game_data['players'][:]
defenseurs.remove(game_data['attaquant'])
@ -305,8 +304,8 @@ class Tarot(object):
loosers = []
player_score = {}
for player in game_data['players']:
#TODO: adjust this for 3 and 5 players variants
#TODO: manage bonuses (petit au bout, poignée, chelem)
# TODO: adjust this for 3 and 5 players variants
# TODO: manage bonuses (petit au bout, poignée, chelem)
player_score[player] = points_defenseur if player != game_data['attaquant'] else points_defenseur * -3
players_data[player]['score'] += player_score[player] # we add score of this game to the global score
if player_score[player] > 0:
@ -314,7 +313,7 @@ class Tarot(object):
else:
loosers.append(player)
scores_str = _('The attacker (%(attaquant)s) makes %(points)i and needs to make %(point_limit)i (%(nb_bouts)s oulder%(plural)s%(separator)s%(bouts)s): he %(victory)s') % {'attaquant': game_data['attaquant'], 'points': score, 'point_limit': point_limit, 'nb_bouts': nb_bouts, 'plural': 's' if nb_bouts > 1 else '', 'separator': ': ' if nb_bouts != 0 else '', 'bouts': ','.join(map(str, bouts)), 'victory': 'win' if victory else 'loose'}
scores_str = _('The attacker (%(attaquant)s) makes %(points)i and needs to make %(point_limit)i (%(nb_bouts)s oulder%(plural)s%(separator)s%(bouts)s): (s)he %(victory)s') % {'attaquant': game_data['attaquant'], 'points': score, 'point_limit': point_limit, 'nb_bouts': nb_bouts, 'plural': 's' if nb_bouts > 1 else '', 'separator': ': ' if nb_bouts != 0 else '', 'bouts': ','.join(map(str, bouts)), 'victory': 'wins' if victory else 'looses'}
scores_str += '\n'
for player in game_data['players']:
scores_str += _("\n--\n%(player)s:\nscore for this game ==> %(score_game)i\ntotal score ==> %(total_score)i") % {'player': player, 'score_game': player_score[player], 'total_score': players_data[player]['score']}
@ -332,7 +331,7 @@ class Tarot(object):
for card in cards:
if card.bout or card.value == "roi":
forbidden_cards.append(card)
#TODO: manage case where atouts (trumps) are in the dog
# TODO: manage case where atouts (trumps) are in the dog
elif game_data['stage'] == 'play':
biggest_atout = None
suit_asked = None
@ -342,7 +341,7 @@ class Tarot(object):
current_idx = game_data['current_player']
current_player = players[current_idx]
if idx == current_idx:
#the player is the first to play, he can play what he wants
# the player is the first to play, he can play what he wants
return forbidden_cards
while (idx != current_idx):
player = players[idx]
@ -398,13 +397,13 @@ class Tarot(object):
# TODO: send error dialog
return defer.succeed({})
room_jid_s = session_data['room_jid'].userhost()
referee = self.games[room_jid_s]['referee']
player = self.host.plugins["XEP-0045"].getRoomNick(room_jid_s, profile)
room_jid = session_data['room_jid']
referee_jid = self.games[room_jid]['referee']
player = self.host.plugins["XEP-0045"].getRoomNick(room_jid, profile)
data = xml_tools.XMLUIResult2DataFormResult(raw_data)
contrat = data['contrat']
log.debug(_('contrat [%(contrat)s] choosed by %(profile)s') % {'contrat': contrat, 'profile': profile})
d = self.send(jid.JID(referee), ('', 'contrat_choosed'), {'player': player}, content=contrat, profile=profile)
d = self.send(referee_jid, ('', 'contrat_choosed'), {'player': player}, content=contrat, profile=profile)
d.addCallback(lambda ignore: {})
del self._sessions[raw_data["session_id"]]
return d
@ -443,7 +442,7 @@ class Tarot(object):
self.send(jid.JID(referee), elem, {'player': player}, profile=profile)
def newRound(self, room_jid, profile):
game_data = self.games[room_jid.userhost()]
game_data = self.games[room_jid]
players = game_data['players']
game_data['first_player'] = None # first player for the current trick
game_data['contrat'] = None
@ -480,11 +479,11 @@ class Tarot(object):
"""
from_jid = jid.JID(mess_elt['from'])
room_jid = jid.JID(from_jid.userhost())
nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid.userhost(), profile)
nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid, profile)
game_elt = mess_elt.firstChildElement()
game_data = self.games[room_jid.userhost()]
is_player = self.isPlayer(room_jid.userhost(), nick)
game_data = self.games[room_jid]
is_player = self.isPlayer(room_jid, nick)
if 'players_data' in game_data:
players_data = game_data['players_data']
@ -501,8 +500,8 @@ class Tarot(object):
elif elt.name == 'player_ready': # ready to play
player = elt['player']
status = self.games[room_jid.userhost()]['status']
nb_players = len(self.games[room_jid.userhost()]['players'])
status = self.games[room_jid]['status']
nb_players = len(self.games[room_jid]['players'])
status[player] = 'ready'
log.debug(_('Player %(player)s is ready to start [status: %(status)s]') % {'player': player, 'status': status})
if status.values().count('ready') == nb_players: # everybody is ready, we can start the game
@ -519,13 +518,13 @@ class Tarot(object):
self.host.bridge.tarotGameChooseContrat(room_jid.userhost(), xml_data, profile)
elif elt.name == 'contrat_choosed':
#TODO: check we receive the contrat from the right person
#TODO: use proper XEP-0004 way for answering form
# TODO: check we receive the contrat from the right person
# TODO: use proper XEP-0004 way for answering form
player = elt['player']
players_data[player]['contrat'] = unicode(elt)
contrats = [players_data[player]['contrat'] for player in game_data['players']]
if contrats.count(None):
#not everybody has choosed his contrat, it's next one turn
# not everybody has choosed his contrat, it's next one turn
player = self.__next_player(game_data)
to_jid = jid.JID(room_jid.userhost() + "/" + player) # FIXME: gof:
self.send(to_jid, self.__ask_contrat(), profile=profile)
@ -553,16 +552,16 @@ class Tarot(object):
self.__start_play(room_jid, game_data, profile)
game_data['attaquant'] = best_contrat[0]
else:
#Time to show the chien to everybody
# Time to show the chien to everybody
to_jid = jid.JID(room_jid.userhost()) # FIXME: gof:
elem = self.__card_list_to_xml(game_data['chien'], 'chien')
self.send(to_jid, elem, {'attaquant': best_contrat[0]}, profile=profile)
#the attacker (attaquant) get the chien
# the attacker (attaquant) get the chien
game_data['hand'][best_contrat[0]].extend(game_data['chien'])
del game_data['chien'][:]
if game_data['contrat'] == "Garde Sans":
#The chien go into attaquant's (attacker) levees
# The chien go into attaquant's (attacker) levees
players_data[best_contrat[0]]['levees'].extend(game_data['chien'])
del game_data['chien'][:]
@ -575,17 +574,17 @@ class Tarot(object):
elif elt.name == 'cards_played':
if game_data['stage'] == "ecart":
#TODO: show atouts (trumps) if player put some in écart
# TODO: show atouts (trumps) if player put some in écart
assert (game_data['attaquant'] == elt['player']) # TODO: throw an xml error here
list_cards = TarotCard.from_tuples(self.__xml_to_list(elt))
#we now check validity of card
# we now check validity of card
invalid_cards = self.__invalid_cards(game_data, list_cards)
if invalid_cards:
elem = self.__invalid_cards_elt(list_cards, invalid_cards, game_data['stage'])
self.send(jid.JID(room_jid.userhost() + '/' + elt['player']), elem, profile=profile)
return
#FIXME: gof: manage Garde Sans & Garde Contre cases
# FIXME: gof: manage Garde Sans & Garde Contre cases
players_data[elt['player']]['levees'].extend(list_cards) # we add the chien to attaquant's levées
for card in list_cards:
game_data['hand'][elt['player']].remove(card)
@ -599,46 +598,46 @@ class Tarot(object):
if mess_elt['type'] == 'groupchat':
self.host.bridge.tarotGameCardsPlayed(room_jid.userhost(), elt['player'], self.__xml_to_list(elt), profile)
else:
#we first check validity of card
# we first check validity of card
invalid_cards = self.__invalid_cards(game_data, cards)
if invalid_cards:
elem = self.__invalid_cards_elt(cards, invalid_cards, game_data['stage'])
self.send(jid.JID(room_jid.userhost() + '/' + current_player), elem, profile=profile)
return
#the card played is ok, we forward it to everybody
#first we remove it from the hand and put in on the table
# the card played is ok, we forward it to everybody
# first we remove it from the hand and put in on the table
game_data['hand'][current_player].remove(cards[0])
players_data[current_player]['played'] = cards[0]
#then we forward the message
# then we forward the message
self.send(room_jid, elt, profile=profile)
#Did everybody played ?
# Did everybody played ?
played = [players_data[player]['played'] for player in game_data['players']]
if all(played):
#everybody has played
# everybody has played
winner = self.__winner(game_data)
log.debug(_('The winner of this trick is %s') % winner)
#the winner win the trick
# the winner win the trick
self.__excuse_hack(game_data, played, winner)
players_data[elt['player']]['levees'].extend(played)
#nothing left on the table
# nothing left on the table
for player in game_data['players']:
players_data[player]['played'] = None
if len(game_data['hand'][current_player]) == 0:
#no card lef: the game is finished
# no card left: the game is finished
elem = self.__give_scores(*self.__calculate_scores(game_data))
self.send(room_jid, elem, profile=profile)
game_data['init_player'] = (game_data['init_player'] + 1) % len(game_data['players']) # we change the dealer
for player in game_data['players']:
game_data['status'][player] = "init"
return
#next player is the winner
# next player is the winner
next_player = game_data['first_player'] = self.__next_player(game_data, winner)
else:
next_player = self.__next_player(game_data)
#finally, we tell to the next player to play
# finally, we tell to the next player to play
to_jid = jid.JID(room_jid.userhost() + "/" + next_player)
self.send(to_jid, 'your_turn', profile=profile)
@ -668,5 +667,5 @@ class Tarot(object):
else:
log.error(_('Unmanaged card game element: %s') % elt.name)
def getSyncDataForPlayer(self, room_jid_s, nick):
def getSyncDataForPlayer(self, room_jid, nick):
return []

View file

@ -209,7 +209,7 @@ class TextCommands(object):
return False
if not target_jid.resource:
target_jid.resource = self.host.memory.getLastResource(target_jid, profile)
target_jid.resource = self.host.memory.getMainResource(target_jid, profile)
whois_msg = [_(u"whois for %(jid)s") % {'jid': target_jid}]

View file

@ -29,8 +29,9 @@ from sat.tools import xml_tools
from twisted.words.protocols.jabber import jid
from twisted.python import failure
from twisted.internet import defer
import potr
from sat.memory import persistent
import potr
import copy
NS_OTR = "otr_plugin"
PRIVATE_KEY = "PRIVATE KEY"
@ -269,7 +270,7 @@ class OTR(object):
try:
to_jid = jid.JID(menu_data['jid'])
if not to_jid.resource:
to_jid.resource = self.host.memory.getLastResource(to_jid, profile) # FIXME: temporary and unsecure, must be changed when frontends are refactored
to_jid.resource = self.host.memory.getMainResource(to_jid, profile) # FIXME: temporary and unsecure, must be changed when frontends are refactored
except KeyError:
log.error(_("jid key is not present !"))
return defer.fail(exceptions.DataError)
@ -287,7 +288,7 @@ class OTR(object):
try:
to_jid = jid.JID(menu_data['jid'])
if not to_jid.resource:
to_jid.resource = self.host.memory.getLastResource(to_jid, profile) # FIXME: temporary and unsecure, must be changed when frontends are refactored
to_jid.resource = self.host.memory.getMainResource(to_jid, profile) # FIXME: temporary and unsecure, must be changed when frontends are refactored
except KeyError:
log.error(_("jid key is not present !"))
return defer.fail(exceptions.DataError)
@ -304,7 +305,7 @@ class OTR(object):
try:
to_jid = jid.JID(menu_data['jid'])
if not to_jid.resource:
to_jid.resource = self.host.memory.getLastResource(to_jid, profile) # FIXME: temporary and unsecure, must be changed when frontends are refactored
to_jid.resource = self.host.memory.getMainResource(to_jid, profile) # FIXME: temporary and unsecure, must be changed when frontends are refactored
except KeyError:
log.error(_("jid key is not present !"))
return defer.fail(exceptions.DataError)
@ -374,7 +375,7 @@ class OTR(object):
try:
to_jid = jid.JID(menu_data['jid'])
if not to_jid.resource:
to_jid.resource = self.host.memory.getLastResource(to_jid, profile) # FIXME: temporary and unsecure, must be changed when frontends are refactored
to_jid.resource = self.host.memory.getMainResource(to_jid, profile) # FIXME: temporary and unsecure, must be changed when frontends are refactored
except KeyError:
log.error(_("jid key is not present !"))
return defer.fail(exceptions.DataError)
@ -447,9 +448,9 @@ class OTR(object):
def sendMessageTrigger(self, mess_data, pre_xml_treatments, post_xml_treatments, profile):
if profile in self.skipped_profiles:
return True
to_jid = mess_data['to']
to_jid = copy.copy(mess_data['to'])
if mess_data['type'] != 'groupchat' and not to_jid.resource:
to_jid.resource = self.host.memory.getLastResource(to_jid, profile) # FIXME: it's dirty, but frontends don't manage resources correctly now, refactoring is planed
to_jid.resource = self.host.memory.getMainResource(to_jid, profile) # FIXME: it's dirty, but frontends don't manage resources correctly now, refactoring is planed
otrctx = self.context_managers[profile].getContextForUser(to_jid)
if mess_data['type'] != 'groupchat' and otrctx.state != potr.context.STATE_PLAINTEXT:
if otrctx.state == potr.context.STATE_ENCRYPTED:
@ -475,7 +476,7 @@ class OTR(object):
if show != "unavailable":
return True
if not entity.resource:
entity.resource = self.host.memory.getLastResource(entity, profile) # FIXME: temporary and unsecure, must be changed when frontends are refactored
entity.resource = self.host.memory.getMainResource(entity, profile) # FIXME: temporary and unsecure, must be changed when frontends are refactored
otrctx = self.context_managers[profile].getContextForUser(entity)
otrctx.disconnect()
return True

View file

@ -65,7 +65,7 @@ class XEP_0045(object):
self.host = host
self.clients = {}
self._sessions = memory.Sessions()
host.bridge.addMethod("joinMUC", ".plugin", in_sign='ssa{ss}s', out_sign='s', method=self._join)
host.bridge.addMethod("joinMUC", ".plugin", in_sign='ssa{ss}s', out_sign='s', method=self._join, async=True)
host.bridge.addMethod("mucNick", ".plugin", in_sign='sss', out_sign='', method=self.mucNick)
host.bridge.addMethod("mucLeave", ".plugin", in_sign='ss', out_sign='', method=self.mucLeave, async=True)
host.bridge.addMethod("getRoomsJoined", ".plugin", in_sign='s', out_sign='a(sass)', method=self.getRoomsJoined)
@ -117,7 +117,7 @@ class XEP_0045(object):
profile = self.host.memory.getProfileName(profile_key)
if not self.checkClient(profile):
raise exceptions.ProfileUnknownError("Unknown or disconnected profile")
if room_jid.userhost() not in self.clients[profile].joined_rooms:
if room_jid not in self.clients[profile].joined_rooms:
raise UnknownRoom("This room has not been joined")
return profile
@ -127,12 +127,11 @@ class XEP_0045(object):
def _sendBridgeSignal(ignore=None):
self.host.bridge.roomJoined(room.roomJID.userhost(), [user.nick for user in room.roster.values()], room.nick, profile)
room_jid_s = room.roomJID.userhost()
self.clients[profile].joined_rooms[room_jid_s] = room
self.clients[profile].joined_rooms[room.roomJID] = room
if room.locked:
#FIXME: the current behaviour is to create an instant room
#and send the signal only when the room is unlocked
#a proper configuration management should be done
# FIXME: the current behaviour is to create an instant room
# and send the signal only when the room is unlocked
# a proper configuration management should be done
print "room locked !"
self.clients[profile].configure(room.roomJID, {}).addCallbacks(_sendBridgeSignal, lambda x: log.error(_('Error while configuring the room')))
else:
@ -164,27 +163,25 @@ class XEP_0045(object):
result.append((room.roomJID.userhost(), [user.nick for user in room.roster.values()], room.nick))
return result
def getRoomNick(self, room_jid_s, profile_key=C.PROF_KEY_NONE):
def getRoomNick(self, room_jid, profile_key=C.PROF_KEY_NONE):
"""return nick used in room by user
@param room_jid_s: unicode room id
@param room_jid (jid.JID): JID of the room
@profile_key: profile
@return: nick or empty string in case of error"""
profile = self.host.memory.getProfileName(profile_key)
if not self.checkClient(profile) or room_jid_s not in self.clients[profile].joined_rooms:
if not self.checkClient(profile) or room_jid not in self.clients[profile].joined_rooms:
return ''
return self.clients[profile].joined_rooms[room_jid_s].nick
return self.clients[profile].joined_rooms[room_jid].nick
def getRoomNickOfUser(self, room, user_jid, secure=True):
"""Returns the nick of the given user in the room.
@room: instance of wokkel.muc.Room
@user: JID or unicode (JID userhost).
@param secure: set to True for a secure check
@return: the nick or None if the user didn't join the room.
@param room (wokkel.muc.Room): the room
@param user (jid.JID): bare JID of the user
@param secure (bool): set to True for a secure check
@return: unicode or None if the user didn't join the room.
"""
if not isinstance(user_jid, jid.JID):
user_jid = jid.JID(user_jid).userhostJID()
for user in room.roster.values():
if user.entity is not None:
if user.entity.userhostJID() == user_jid.userhostJID():
@ -199,11 +196,12 @@ class XEP_0045(object):
def getRoomNicksOfUsers(self, room, users=[], secure=True):
"""Returns the nicks of the given users in the room.
@room: instance of wokkel.muc.Room
@users: list of JID or unicode (JID userhost).
@param secure: set to True for a secure check
@return: (x, y) with x a list containing the nicks of
the users who are in the room, and y the missing users.
@param room (wokkel.muc.Room): the room
@param users (list[jid.JID]): list of users
@param secure (True): set to True for a secure check
@return: a couple (x, y) with:
- x (list[unicode]): nicks of the users who are in the room
- y (list[jid.JID]): JID of the missing users.
"""
nicks = []
missing = []
@ -231,6 +229,7 @@ class XEP_0045(object):
except KeyError:
log.error(_("room_jid key is not present !"))
return defer.fail(exceptions.DataError)
def xmluiReceived(xmlui):
return {"xmlui": xmlui.toXml()}
return self.configureRoom(room_jid, profile).addCallback(xmluiReceived)
@ -277,7 +276,7 @@ class XEP_0045(object):
def isNickInRoom(self, room_jid, nick, profile):
"""Tell if a nick is currently present in a room"""
profile = self.getProfileAssertInRoom(room_jid, profile)
return self.clients[profile].joined_rooms[room_jid.userhost()].inRoster(muc.User(nick))
return self.clients[profile].joined_rooms[room_jid].inRoster(muc.User(nick))
def getRoomsSubjects(self, profile_key=C.PROF_KEY_NONE):
"""Return received subjects of rooms"""
@ -296,7 +295,7 @@ class XEP_0045(object):
muc_service = None
services = yield self.host.findServiceEntities("conference", "text", jid_, profile_key=profile_key)
for service in services:
if not ".irc." in service.userhost():
if ".irc." not in service.userhost():
# FIXME:
# This ugly hack is here to avoid an issue with openfire: the IRC gateway
# use "conference/text" identity (instead of "conference/irc")
@ -310,10 +309,10 @@ class XEP_0045(object):
def getUniqueName(self, muc_service=None, profile_key=C.PROF_KEY_NONE):
"""Return unique name for a room, avoiding collision
@param muc_service: leave empty string to use the default service
@return: unique room userhost, or '' if an error occured.
@param muc_service (jid.JID) : leave empty string to use the default service
@return: jid.JID (unique room bare JID)
"""
#TODO: we should use #RFC-0045 10.1.4 when available here
# TODO: we should use #RFC-0045 10.1.4 when available here
client = self.host.getClient(profile_key)
room_name = uuid.uuid1()
if muc_service is None:
@ -337,7 +336,7 @@ class XEP_0045(object):
profile = self.host.memory.getProfileName(profile_key)
if not self.checkClient(profile):
return _errDeferred()
if room_jid.userhost() in self.clients[profile].joined_rooms:
if room_jid in self.clients[profile].joined_rooms:
log.warning(_('%(profile)s is already in room %(room_jid)s') % {'profile': profile, 'room_jid': room_jid.userhost()})
return _errDeferred()
log.info(_("[%(profile)s] is joining room %(room)s with nick %(nick)s") % {'profile': profile, 'room': room_jid.userhost(), 'nick': nick})
@ -358,23 +357,24 @@ class XEP_0045(object):
def _join(self, room_jid_s, nick, options={}, profile_key=C.PROF_KEY_NONE):
"""join method used by bridge: use the join method, but doesn't return any deferred
@return the room userhost (given value or unique generated name)
@return: unicode (the room bare)
"""
profile = self.host.memory.getProfileName(profile_key)
if not self.checkClient(profile):
return
if room_jid_s == "":
room_jid_s = self.getUniqueName(profile_key=profile_key)
try:
room_jid = jid.JID(room_jid_s)
except:
mess = _("Invalid room jid: %s") % room_jid_s
log.warning(mess)
self.host.bridge.newAlert(mess, _("Group chat error"), "ERROR", profile)
return
self.join(room_jid, nick, options, profile)
if room_jid_s:
try:
room_jid = jid.JID(room_jid_s)
except (RuntimeError, jid.InvalidFormat, AttributeError):
mess = _("Invalid room JID: %s") % room_jid_s
log.warning(mess)
self.host.bridge.newAlert(mess, _("Group chat error"), "ERROR", profile)
return defer.succeed(None)
else:
room_jid = self.getUniqueName(profile_key=profile_key)
# TODO: error management + signal in bridge
return room_jid_s
d = self.join(room_jid, nick, options, profile)
return d.addCallback(lambda room: room.roomJID.userhost())
def nick(self, room_jid, nick, profile_key):
profile = self.getProfileAssertInRoom(room_jid, profile_key)
@ -471,10 +471,10 @@ class XEP_0045(object):
return False
if mess_data["unparsed"].strip():
room = self.host.plugins[C.TEXT_CMDS].getRoomJID(mess_data["unparsed"].strip(), mess_data["to"].host)
nick = (self.getRoomNick(mess_data["to"].userhost(), profile) or
room_jid = self.host.plugins[C.TEXT_CMDS].getRoomJID(mess_data["unparsed"].strip(), mess_data["to"].host)
nick = (self.getRoomNick(room_jid, profile) or
self.host.getClient(profile).jid.user)
self.join(room, nick, {}, profile)
self.join(room_jid, nick, {}, profile)
return False
@ -630,10 +630,10 @@ class XEP_0045(object):
""" Add MUC user information to whois """
if mess_data['type'] != "groupchat":
return
if target_jid.userhost() not in self.clients[profile].joined_rooms:
if target_jid.userhostJID() not in self.clients[profile].joined_rooms:
log.warning(_("This room has not been joined"))
return
user = self.clients[profile].joined_rooms[target_jid.userhost()].getUser(target_jid.resource)
user = self.clients[profile].joined_rooms[target_jid.userhostJID()].getUser(target_jid.resource)
whois_msg.append(_("Nickname: %s") % user.nick)
if user.entity:
whois_msg.append(_("Entity: %s") % user.entity)
@ -649,32 +649,35 @@ class XEP_0045(object):
def presenceTrigger(self, presence_elt, client):
# XXX: shouldn't it be done by the server ?!!
muc_client = self.clients[client.profile]
for room_jid_s, room in muc_client.joined_rooms.items():
for room_jid, room in muc_client.joined_rooms.iteritems():
elt = copy.deepcopy(presence_elt)
elt['to'] = room_jid_s + '/' + room.nick
elt['to'] = room_jid.userhost() + '/' + room.nick
client.presence.send(elt)
return True
class SatMUCClient (muc.MUCClient):
#implements(iwokkel.IDisco)
# implements(iwokkel.IDisco)
def __init__(self, plugin_parent):
self.plugin_parent = plugin_parent
self.host = plugin_parent.host
muc.MUCClient.__init__(self)
self.joined_rooms = {} # FIXME: seem to do the same thing as MUCClient's _rooms attribute, must be removed
self.rec_subjects = {}
self.__changing_nicks = set() # used to keep trace of who is changing nick,
# and to discard userJoinedRoom signal in this case
print "init SatMUCClient OK"
@property
def joined_rooms(self):
return self._rooms
def subject(self, room, subject):
return muc.MUCClientProtocol.subject(self, room, subject)
def unavailableReceived(self, presence):
#XXX: we override this method to manage nickname change
#TODO: feed this back to Wokkel
# XXX: we override this method to manage nickname change
# TODO: feed this back to Wokkel
"""
Unavailable presence was received.
@ -696,7 +699,7 @@ class SatMUCClient (muc.MUCClient):
self.userLeftRoom(room, user)
def userJoinedRoom(self, room, user):
self.host.memory.updateEntityData(room.roomJID, "type", "chatroom", self.parent.profile)
self.host.memory.updateEntityData(room.roomJID, "type", "chatroom", profile_key=self.parent.profile)
if user.nick in self.__changing_nicks:
self.__changing_nicks.remove(user.nick)
else:
@ -713,9 +716,8 @@ class SatMUCClient (muc.MUCClient):
# we left the room
room_jid_s = room.roomJID.userhost()
log.info(_("Room [%(room)s] left (%(profile)s))") % {"room": room_jid_s,
"profile": self.parent.profile})
"profile": self.parent.profile})
self.host.memory.delEntityCache(room.roomJID, profile_key=self.parent.profile)
del self.plugin_parent.clients[self.parent.profile].joined_rooms[room_jid_s]
self.host.bridge.roomLeft(room.roomJID.userhost(), self.parent.profile)
else:
log.debug(_("user %(nick)s left room (%(room_id)s)") % {'nick': user.nick, 'room_id': room.occupantJID.userhost()})
@ -733,7 +735,7 @@ class SatMUCClient (muc.MUCClient):
def receivedHistory(self, room, user, message):
# http://xmpp.org/extensions/xep-0045.html#enter-history
#log.debug('receivedHistory: room=%s user=%s body=%s' % (room.roomJID.full(), user, message))
# log.debug('receivedHistory: room=%s user=%s body=%s' % (room.roomJID.full(), user, message))
pass
def receivedSubject(self, room, user, subject):

Some files were not shown because too many files have changed in this diff Show more