core, frontends: refactoring to base Libervia on QuickFrontend (big mixed commit):
/!\ not finished, everything is still instable !
- bridge: DBus bridge has been modified to allow blocking call to be called in the same way as asynchronous calls
- bridge: calls with a callback and no errback are now possible, default errback log the error
- constants: removed hack to manage presence without OrderedDict, as an OrderedDict like class has been implemented in Libervia
- core: getLastResource has been removed and replaced by getMainResource (there is a global better management of resources)
- various style improvments: use of constants when possible, fixed variable overlaps, import of module instead of direct class import
- frontends: printInfo and printMessage methods in (Quick)Chat are more generic (use of extra instead of timestamp)
- frontends: bridge creation and option parsing (command line arguments) are now specified by the frontend in QuickApp __init__
- frontends: ProfileManager manage a more complete plug sequence (some stuff formerly manage in contact_list have moved to ProfileManager)
- quick_frontend (quick_widgets): QuickWidgetsManager is now iterable (all widgets are then returned), or can return an iterator on a specific class (return all widgets of this class) with getWidgets
- frontends: tools.jid can now be used in Pyjamas, with some care
- frontends (XMLUI): profile is now managed
- core (memory): big improvment on entities cache management (and specially resource management)
- core (params/exceptions): added PermissionError
- various fixes and improvments, check diff for more details
This commit is contained in:
parent
f45e55388f
commit
8ce5c1862b
15 changed files with 1125 additions and 452 deletions
|
|
@ -285,8 +285,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',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -279,7 +279,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
|
||||
|
|
@ -288,7 +288,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
|
||||
|
|
|
|||
|
|
@ -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##
|
||||
|
|
|
|||
|
|
@ -76,7 +76,8 @@ class Const(object):
|
|||
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'
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -94,5 +94,9 @@ class PasswordError(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class PermissionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SkipHistory(Exception): # used in MessageReceivedTrigger to avoid history writting
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ class SAT(service.Service):
|
|||
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)
|
||||
|
|
@ -482,7 +482,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"
|
||||
|
||||
|
|
@ -500,7 +500,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"""
|
||||
|
|
|
|||
|
|
@ -102,9 +102,7 @@ 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)
|
||||
|
|
@ -114,6 +112,9 @@ class Discovery(object):
|
|||
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,7 +132,7 @@ 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)
|
||||
|
|
|
|||
|
|
@ -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,24 +339,6 @@ 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()
|
||||
|
||||
|
|
@ -365,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:
|
||||
|
|
@ -393,163 +407,256 @@ 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)
|
||||
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 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, profile_key):
|
||||
"""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 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:
|
||||
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):
|
||||
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): list of keys to get, empty list for everything
|
||||
@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
|
||||
|
||||
@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:
|
||||
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
|
||||
ret = {}
|
||||
for key in keys_list:
|
||||
if key in entity_data:
|
||||
ret[key] = entity_data[key]
|
||||
return ret
|
||||
|
||||
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
|
||||
|
|
@ -559,34 +666,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.warning(_("Can't delete entity [{}]: not in cache").format(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
|
||||
|
|
@ -644,6 +753,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)
|
||||
|
|
@ -670,6 +781,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)
|
||||
|
||||
|
|
@ -705,3 +818,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" />
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ class XEP_0054(object):
|
|||
host.bridge.addMethod("getAvatarFile", ".plugin", in_sign='s', out_sign='s', method=self.getAvatarFile)
|
||||
host.bridge.addMethod("setAvatar", ".plugin", in_sign='ss', out_sign='', method=self.setAvatar, async=True)
|
||||
host.trigger.add("presence_available", self.presenceTrigger)
|
||||
host.memory.setSignalOnUpdate("avatar")
|
||||
|
||||
def getHandler(self, profile):
|
||||
return XEP_0054_handler(self)
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class XEP_0060(object):
|
|||
OPT_PUBLISH_MODEL = 'pubsub#publish_model'
|
||||
|
||||
def __init__(self, host):
|
||||
log.info(_("PubSub plugin initialization"))
|
||||
log.info(_(u"PubSub plugin initialization"))
|
||||
self.host = host
|
||||
self.managedNodes = []
|
||||
self.clients = {}
|
||||
|
|
@ -176,7 +176,7 @@ class XEP_0060(object):
|
|||
d_dict = {}
|
||||
for publisher, node in data.items():
|
||||
if node not in found_nodes:
|
||||
log.debug("Skip the items retrieval for [{node}]: node doesn't exist".format(node=node))
|
||||
log.debug(u"Skip the items retrieval for [{node}]: node doesn't exist".format(node=node))
|
||||
continue # avoid pubsub "item-not-found" error
|
||||
d_dict[publisher] = client.items(service, node, max_items, item_ids, sub_id, client.parent.jid)
|
||||
defer.returnValue(d_dict)
|
||||
|
|
@ -222,7 +222,7 @@ class XEP_0060(object):
|
|||
d_list = []
|
||||
for nodeIdentifier in (set(nodeIdentifiers) - set(subscribed_nodes)):
|
||||
if nodeIdentifier not in found_nodes:
|
||||
log.debug("Skip the subscription to [{node}]: node doesn't exist".format(node=nodeIdentifier))
|
||||
log.debug(u"Skip the subscription to [{node}]: node doesn't exist".format(node=nodeIdentifier))
|
||||
continue # avoid sat-pubsub "SubscriptionExists" error
|
||||
d_list.append(client.subscribe(service, nodeIdentifier, sub_jid or client.parent.jid.userhostJID(), options=options))
|
||||
defer.returnValue(d_list)
|
||||
|
|
@ -318,7 +318,7 @@ class SatPubSubClient(pubsub.PubSubClient):
|
|||
|
||||
def deleteReceived(self, event):
|
||||
#TODO: manage delete event
|
||||
log.debug(_("Publish node deleted"))
|
||||
log.debug(_(u"Publish node deleted"))
|
||||
|
||||
# def purgeReceived(self, event):
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ MESSAGE_TYPES = ["chat", "groupchat"]
|
|||
PARAM_KEY = "Notifications"
|
||||
PARAM_NAME = "Enable chat state notifications"
|
||||
ENTITY_KEY = PARAM_KEY + "_" + PARAM_NAME
|
||||
DELETE_VALUE = "DELETE"
|
||||
|
||||
PLUGIN_INFO = {
|
||||
"name": "Chat State Notifications Protocol Plugin",
|
||||
|
|
@ -126,28 +127,31 @@ class XEP_0085(object):
|
|||
del self.map[profile]
|
||||
|
||||
def updateEntityData(self, entity_jid, value, profile):
|
||||
"""
|
||||
Update the entity data of the given profile for one or all contacts.
|
||||
"""Update the entity data of the given profile for one or all contacts.
|
||||
|
||||
Reset the chat state(s) display if the notification has been disabled.
|
||||
@param entity_jid: contact's JID, or '@ALL@' to update all contacts.
|
||||
@param value: True, False or C.PROF_KEY_NONE to delete the entity data
|
||||
@param entity_jid: contact's JID, or C.ENTITY_ALL to update all contacts.
|
||||
@param value: True, False or DELETE_VALUE to delete the entity data
|
||||
@param profile: current profile
|
||||
"""
|
||||
self.host.memory.updateEntityData(entity_jid, ENTITY_KEY, value, profile)
|
||||
if not value or value == C.PROF_KEY_NONE:
|
||||
# disable chat state for this or these contact(s)
|
||||
self.host.bridge.chatStateReceived(unicode(entity_jid), "", profile)
|
||||
if value == DELETE_VALUE:
|
||||
self.host.memory.delEntityData(entity_jid, ENTITY_KEY, profile)
|
||||
else:
|
||||
self.host.memory.updateEntityData(entity_jid, ENTITY_KEY, value, profile)
|
||||
if not value or value == DELETE_VALUE:
|
||||
# reinit chat state UI for this or these contact(s)
|
||||
self.host.bridge.chatStateReceived(entity_jid.full(), "", profile)
|
||||
|
||||
def paramUpdateTrigger(self, name, value, category, type_, profile):
|
||||
"""
|
||||
Reset all the existing chat state entity data associated with this profile after a parameter modification.
|
||||
"""Reset all the existing chat state entity data associated with this profile after a parameter modification.
|
||||
|
||||
@param name: parameter name
|
||||
@param value: "true" to activate the notifications, or any other value to delete it
|
||||
@param category: parameter category
|
||||
@param type_: parameter type
|
||||
"""
|
||||
if (category, name) == (PARAM_KEY, PARAM_NAME):
|
||||
self.updateEntityData("@ALL@", True if value == "true" else C.PROF_KEY_NONE, profile)
|
||||
self.updateEntityData(C.ENTITY_ALL, True if bool("true") else DELETE_VALUE, profile)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
|
@ -160,7 +164,7 @@ class XEP_0085(object):
|
|||
return True
|
||||
|
||||
from_jid = JID(message.getAttribute("from"))
|
||||
if self.__isMUC(from_jid, profile):
|
||||
if self._isMUC(from_jid, profile):
|
||||
from_jid = from_jid.userhostJID()
|
||||
else: # update entity data for one2one chat
|
||||
# assert from_jid.resource # FIXME: assert doesn't work on normal message from server (e.g. server announce), because there is no resource
|
||||
|
|
@ -200,27 +204,27 @@ class XEP_0085(object):
|
|||
def treatment(mess_data):
|
||||
message = mess_data['xml']
|
||||
to_jid = JID(message.getAttribute("to"))
|
||||
if not self.__checkActivation(to_jid, forceEntityData=True, profile=profile):
|
||||
if not self._checkActivation(to_jid, forceEntityData=True, profile=profile):
|
||||
return mess_data
|
||||
try:
|
||||
# message with a body always mean active state
|
||||
domish.generateElementsNamed(message.elements(), name="body").next()
|
||||
message.addElement('active', NS_CHAT_STATES)
|
||||
# launch the chat state machine (init the timer)
|
||||
if self.__isMUC(to_jid, profile):
|
||||
if self._isMUC(to_jid, profile):
|
||||
to_jid = to_jid.userhostJID()
|
||||
self.__chatStateActive(to_jid, mess_data["type"], profile)
|
||||
except StopIteration:
|
||||
if "chat_state" in mess_data["extra"]:
|
||||
state = mess_data["extra"].pop("chat_state")
|
||||
assert(state in CHAT_STATES)
|
||||
assert state in CHAT_STATES
|
||||
message.addElement(state, NS_CHAT_STATES)
|
||||
return mess_data
|
||||
|
||||
post_xml_treatments.addCallback(treatment)
|
||||
return True
|
||||
|
||||
def __isMUC(self, to_jid, profile):
|
||||
def _isMUC(self, to_jid, profile):
|
||||
"""Tell if that JID is a MUC or not
|
||||
|
||||
@param to_jid (JID): full or bare JID to check
|
||||
|
|
@ -229,13 +233,13 @@ class XEP_0085(object):
|
|||
"""
|
||||
try:
|
||||
type_ = self.host.memory.getEntityDatum(to_jid.userhostJID(), 'type', profile)
|
||||
if type_ == 'chatroom':
|
||||
if type_ == 'chatroom': # FIXME: should not use disco instead ?
|
||||
return True
|
||||
except (exceptions.UnknownEntityError, KeyError):
|
||||
pass
|
||||
return False
|
||||
|
||||
def __checkActivation(self, to_jid, forceEntityData, profile):
|
||||
def _checkActivation(self, to_jid, forceEntityData, profile):
|
||||
"""
|
||||
@param to_jid: the contact's full JID (or bare if you know it's a MUC)
|
||||
@param forceEntityData: if set to True, a non-existing
|
||||
|
|
@ -247,9 +251,10 @@ class XEP_0085(object):
|
|||
if not self.host.memory.getParamA(PARAM_NAME, PARAM_KEY, profile_key=profile):
|
||||
return False
|
||||
# check if notifications should be sent to this contact
|
||||
if self.__isMUC(to_jid, profile):
|
||||
if self._isMUC(to_jid, profile):
|
||||
return True
|
||||
assert to_jid.resource or not self.host.memory.isContactConnected(to_jid, profile)
|
||||
# FIXME: this assertion crash when we want to send a message to an online bare jid
|
||||
# assert to_jid.resource or not self.host.memory.isEntityAvailable(to_jid, profile) # must either have a resource, or talk to an offline contact
|
||||
try:
|
||||
return self.host.memory.getEntityDatum(to_jid, ENTITY_KEY, profile)
|
||||
except (exceptions.UnknownEntityError, KeyError):
|
||||
|
|
@ -305,11 +310,11 @@ class XEP_0085(object):
|
|||
if profile is None:
|
||||
raise exceptions.ProfileUnknownError
|
||||
to_jid = JID(to_jid_s)
|
||||
if self.__isMUC(to_jid, profile):
|
||||
if self._isMUC(to_jid, profile):
|
||||
to_jid = to_jid.userhostJID()
|
||||
elif not to_jid.resource:
|
||||
to_jid.resource = self.host.memory.getLastResource(to_jid, profile)
|
||||
if not self.__checkActivation(to_jid, forceEntityData=False, profile=profile):
|
||||
to_jid.resource = self.host.memory.getMainResource(to_jid, profile)
|
||||
if not self._checkActivation(to_jid, forceEntityData=False, profile=profile):
|
||||
return
|
||||
try:
|
||||
self.map[profile][to_jid]._onEvent("composing")
|
||||
|
|
@ -347,9 +352,9 @@ class ChatStateMachine:
|
|||
notification to the contact (the "active" state is
|
||||
automatically sent with each message) and set the timer.
|
||||
"""
|
||||
assert(state in TRANSITIONS)
|
||||
assert state in TRANSITIONS
|
||||
transition = TRANSITIONS[state]
|
||||
assert("next_state" in transition and "delay" in transition)
|
||||
assert "next_state" in transition and "delay" in transition
|
||||
|
||||
if state != self.state and state != "active":
|
||||
if state != 'gone' or self.mess_type != 'groupchat':
|
||||
|
|
|
|||
Loading…
Reference in a new issue