merged branch frontends_multi_profiles
This commit is contained in:
commit
6760c927d3
29 changed files with 4611 additions and 4514 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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##
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
134
src/core/xmpp.py
134
src/core/xmpp.py
|
|
@ -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 = {}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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'])
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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 []
|
||||
|
|
|
|||
|
|
@ -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}]
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue