diff --git a/frontends/src/bridge/DBus.py b/frontends/src/bridge/DBus.py
index d5a402b1..493132c1 100644
--- a/frontends/src/bridge/DBus.py
+++ b/frontends/src/bridge/DBus.py
@@ -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,141 +122,497 @@ class DBusBridgeFrontend(BridgeFrontend):
return method(*args, **kwargs)
return getPluginMethod
- def addContact(self, entity_jid, profile_key="@DEFAULT@"):
- return self.db_core_iface.addContact(entity_jid, profile_key)
+
+ def addContact(self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return self.db_core_iface.addContact(entity_jid, profile_key, **kwargs)
def asyncConnect(self, profile_key="@DEFAULT@", password='', callback=None, errback=None):
- error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
+ 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))
return self.db_core_iface.asyncConnect(profile_key, password, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
def asyncCreateProfile(self, profile, password='', callback=None, errback=None):
- error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
+ 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))
return self.db_core_iface.asyncCreateProfile(profile, password, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
def asyncDeleteProfile(self, profile, callback=None, errback=None):
- error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
+ 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))
return self.db_core_iface.asyncDeleteProfile(profile, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
def asyncGetParamA(self, name, category, attribute="value", security_limit=-1, profile_key="@DEFAULT@", callback=None, errback=None):
- error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
+ 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))
return unicode(self.db_core_iface.asyncGetParamA(name, category, attribute, security_limit, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler))
- def confirmationAnswer(self, id, accepted, data, profile):
- return self.db_core_iface.confirmationAnswer(id, accepted, data, profile)
+ def confirmationAnswer(self, id, accepted, data, profile, callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return self.db_core_iface.confirmationAnswer(id, accepted, data, profile, **kwargs)
def delContact(self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None):
- error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
+ 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))
return self.db_core_iface.delContact(entity_jid, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
def discoInfos(self, entity_jid, profile_key, callback=None, errback=None):
- error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
+ 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))
return self.db_core_iface.discoInfos(entity_jid, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
def discoItems(self, entity_jid, profile_key, callback=None, errback=None):
- error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
+ 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))
return self.db_core_iface.discoItems(entity_jid, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
- def disconnect(self, profile_key="@DEFAULT@"):
- return self.db_core_iface.disconnect(profile_key)
+ def disconnect(self, profile_key="@DEFAULT@", callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return self.db_core_iface.disconnect(profile_key, **kwargs)
- def getConfig(self, section, name):
- return unicode(self.db_core_iface.getConfig(section, name))
+ def getConfig(self, section, name, callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return unicode(self.db_core_iface.getConfig(section, name, **kwargs))
def getContacts(self, profile_key="@DEFAULT@", callback=None, errback=None):
- error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
+ 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))
return self.db_core_iface.getContacts(profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
- def getContactsFromGroup(self, group, profile_key="@DEFAULT@"):
- return self.db_core_iface.getContactsFromGroup(group, profile_key)
+ def getContactsFromGroup(self, group, profile_key="@DEFAULT@", callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return self.db_core_iface.getContactsFromGroup(group, profile_key, **kwargs)
- def getEntityData(self, jid, keys, profile):
- return self.db_core_iface.getEntityData(jid, keys, profile)
+ def getEntityData(self, jid, keys, profile, callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return self.db_core_iface.getEntityData(jid, keys, profile, **kwargs)
def getHistory(self, from_jid, to_jid, limit, between=True, search='', profile="@NONE@", callback=None, errback=None):
- error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
+ 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))
return self.db_core_iface.getHistory(from_jid, to_jid, limit, between, search, profile, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
- def getLastResource(self, contact_jid, profile_key="@DEFAULT@"):
- return unicode(self.db_core_iface.getLastResource(contact_jid, profile_key))
+ def getMainResource(self, contact_jid, profile_key="@DEFAULT@", callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return unicode(self.db_core_iface.getMainResource(contact_jid, profile_key, **kwargs))
- def getMenuHelp(self, menu_id, language):
- return unicode(self.db_core_iface.getMenuHelp(menu_id, language))
+ def getMenuHelp(self, menu_id, language, callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return unicode(self.db_core_iface.getMenuHelp(menu_id, language, **kwargs))
- def getMenus(self, language, security_limit):
- return self.db_core_iface.getMenus(language, security_limit)
+ def getMenus(self, language, security_limit, callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return self.db_core_iface.getMenus(language, security_limit, **kwargs)
- def getParamA(self, name, category, attribute="value", profile_key="@DEFAULT@"):
- return unicode(self.db_core_iface.getParamA(name, category, attribute, profile_key))
+ def getParamA(self, name, category, attribute="value", profile_key="@DEFAULT@", callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return unicode(self.db_core_iface.getParamA(name, category, attribute, profile_key, **kwargs))
def getParams(self, security_limit=-1, app='', profile_key="@DEFAULT@", callback=None, errback=None):
- error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
+ 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))
return unicode(self.db_core_iface.getParams(security_limit, app, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler))
- def getParamsCategories(self, ):
- return self.db_core_iface.getParamsCategories()
+ def getParamsCategories(self, callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return self.db_core_iface.getParamsCategories(**kwargs)
def getParamsForCategory(self, category, security_limit=-1, app='', profile_key="@DEFAULT@", callback=None, errback=None):
- error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
+ 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))
return unicode(self.db_core_iface.getParamsForCategory(category, security_limit, app, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler))
def getParamsUI(self, security_limit=-1, app='', profile_key="@DEFAULT@", callback=None, errback=None):
- error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
+ 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))
return unicode(self.db_core_iface.getParamsUI(security_limit, app, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler))
- def getPresenceStatuses(self, profile_key="@DEFAULT@"):
- return self.db_core_iface.getPresenceStatuses(profile_key)
+ def getPresenceStatuses(self, profile_key="@DEFAULT@", callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return self.db_core_iface.getPresenceStatuses(profile_key, **kwargs)
- def getProfileName(self, profile_key="@DEFAULT@"):
- return unicode(self.db_core_iface.getProfileName(profile_key))
+ def getProfileName(self, profile_key="@DEFAULT@", callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return unicode(self.db_core_iface.getProfileName(profile_key, **kwargs))
- def getProfilesList(self, ):
- return self.db_core_iface.getProfilesList()
+ def getProfilesList(self, callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return self.db_core_iface.getProfilesList(**kwargs)
- def getProgress(self, id, profile):
- return self.db_core_iface.getProgress(id, profile)
+ def getProgress(self, id, profile, callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return self.db_core_iface.getProgress(id, profile, **kwargs)
def getReady(self, callback=None, errback=None):
- error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
+ 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))
return self.db_core_iface.getReady(timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
- def getVersion(self, ):
- return unicode(self.db_core_iface.getVersion())
+ def getVersion(self, callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return unicode(self.db_core_iface.getVersion(**kwargs))
- def getWaitingConf(self, profile_key):
- return self.db_core_iface.getWaitingConf(profile_key)
+ def getWaitingConf(self, profile_key, callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return self.db_core_iface.getWaitingConf(profile_key, **kwargs)
- def getWaitingSub(self, profile_key="@DEFAULT@"):
- return self.db_core_iface.getWaitingSub(profile_key)
+ def getWaitingSub(self, profile_key="@DEFAULT@", callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return self.db_core_iface.getWaitingSub(profile_key, **kwargs)
- def isConnected(self, profile_key="@DEFAULT@"):
- return self.db_core_iface.isConnected(profile_key)
+ def isConnected(self, profile_key="@DEFAULT@", callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return self.db_core_iface.isConnected(profile_key, **kwargs)
def launchAction(self, callback_id, data, profile_key="@DEFAULT@", callback=None, errback=None):
- error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
+ 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))
return self.db_core_iface.launchAction(callback_id, data, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
- def loadParamsTemplate(self, filename):
- return self.db_core_iface.loadParamsTemplate(filename)
+ def loadParamsTemplate(self, filename, callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return self.db_core_iface.loadParamsTemplate(filename, **kwargs)
- def paramsRegisterApp(self, xml, security_limit=-1, app=''):
- return self.db_core_iface.paramsRegisterApp(xml, security_limit, app)
+ def paramsRegisterApp(self, xml, security_limit=-1, app='', callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return self.db_core_iface.paramsRegisterApp(xml, security_limit, app, **kwargs)
- def saveParamsTemplate(self, filename):
- return self.db_core_iface.saveParamsTemplate(filename)
+ def saveParamsTemplate(self, filename, callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return self.db_core_iface.saveParamsTemplate(filename, **kwargs)
def sendMessage(self, to_jid, message, subject='', mess_type="auto", extra={}, profile_key="@NONE@", callback=None, errback=None):
- error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
+ 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))
return self.db_core_iface.sendMessage(to_jid, message, subject, mess_type, extra, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
- def setParam(self, name, value, category, security_limit=-1, profile_key="@DEFAULT@"):
- return self.db_core_iface.setParam(name, value, category, security_limit, profile_key)
+ def setParam(self, name, value, category, security_limit=-1, profile_key="@DEFAULT@", callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return self.db_core_iface.setParam(name, value, category, security_limit, profile_key, **kwargs)
- def setPresence(self, to_jid='', show='', statuses={}, profile_key="@DEFAULT@"):
- return self.db_core_iface.setPresence(to_jid, show, statuses, profile_key)
+ def setPresence(self, to_jid='', show='', statuses={}, profile_key="@DEFAULT@", callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return self.db_core_iface.setPresence(to_jid, show, statuses, profile_key, **kwargs)
- def subscription(self, sub_type, entity, profile_key="@DEFAULT@"):
- return self.db_core_iface.subscription(sub_type, entity, profile_key)
+ def subscription(self, sub_type, entity, profile_key="@DEFAULT@", callback=None, errback=None):
+ 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))
+ kwargs={}
+ if callback is not None:
+ kwargs['timeout'] = const_TIMEOUT
+ kwargs['reply_handler'] = callback
+ kwargs['error_handler'] = error_handler
+ return self.db_core_iface.subscription(sub_type, entity, profile_key, **kwargs)
def updateContact(self, entity_jid, name, groups, profile_key="@DEFAULT@", callback=None, errback=None):
- error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
+ 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))
return self.db_core_iface.updateContact(entity_jid, name, groups, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
diff --git a/frontends/src/constants.py b/frontends/src/constants.py
index 7f10831b..5ae08e90 100644
--- a/frontends/src/constants.py
+++ b/frontends/src/constants.py
@@ -20,14 +20,7 @@
from sat.core import constants
from sat.core.i18n import _, D_
-
-try:
- from collections import OrderedDict # only available from python 2.7
-except ImportError:
- try:
- from ordereddict import OrderedDict
- except ImportError:
- pass # libervia can not import external libraries
+from collections import OrderedDict # only available from python 2.7
def getPresence():
@@ -35,25 +28,15 @@ def getPresence():
in a method we get a JS runtime SyntaxError: "missing ) in parenthetical".
# TODO: merge this definition with those in primitivus.constants and wix.constants
"""
- try:
- presence = OrderedDict([("", _("Online")),
- ("chat", _("Free for chat")),
- ("away", _("Away from keyboard")),
- ("dnd", _("Do not disturb")),
- ("xa", _("Extended away"))])
- except TypeError:
- presence = {"": _("Online"),
- "chat": _("Free for chat"),
- "away": _("Away from keyboard"),
- "dnd": _("Do not disturb"),
- "xa": _("Extended away")
- }
- return presence
class Const(constants.Const):
- PRESENCE = getPresence()
+ PRESENCE = OrderedDict([("", _("Online")),
+ ("chat", _("Free for chat")),
+ ("away", _("Away from keyboard")),
+ ("dnd", _("Do not disturb")),
+ ("xa", _("Extended away"))])
# from plugin_misc_text_syntaxes
SYNTAX_XHTML = "XHTML"
@@ -78,3 +61,7 @@ class Const(constants.Const):
# Roster
GROUP_NOT_IN_ROSTER = D_('Not in roster')
+
+ #Chats
+ CHAT_ONE2ONE = 'one2one'
+ CHAT_GROUP = 'group'
diff --git a/frontends/src/jp/base.py b/frontends/src/jp/base.py
index 365ea544..401ae954 100644
--- a/frontends/src/jp/base.py
+++ b/frontends/src/jp/base.py
@@ -245,11 +245,11 @@ class Jp(object):
callback()
def get_full_jid(self, param_jid):
- """Return the full jid if possible (add last resource when find a bare jid)"""
+ """Return the full jid if possible (add main resource when find a bare jid)"""
_jid = JID(param_jid)
if not _jid.resource:
#if the resource is not given, we try to add the last known resource
- last_resource = self.bridge.getLastResource(param_jid, self.profile)
+ last_resource = self.bridge.getMainResource(param_jid, self.profile)
if last_resource:
return "%s/%s" % (_jid.bare, last_resource)
return param_jid
diff --git a/frontends/src/primitivus/chat.py b/frontends/src/primitivus/chat.py
index 0b7c9d56..20ec0989 100644
--- a/frontends/src/primitivus/chat.py
+++ b/frontends/src/primitivus/chat.py
@@ -30,7 +30,7 @@ from sat_frontends.primitivus.constants import Const as C
from sat_frontends.primitivus.keys import action_key_map as a_key
from sat_frontends.primitivus.widget import PrimitivusWidget
import time
-from sat_frontends.tools.jid import JID
+from sat_frontends.tools import jid
class ChatText(urwid.FlowWidget):
@@ -83,7 +83,7 @@ class ChatText(urwid.FlowWidget):
class Chat(PrimitivusWidget, QuickChat):
- def __init__(self, host, target, type_='one2one', profiles=None):
+ def __init__(self, host, target, type_=C.CHAT_ONE2ONE, profiles=None):
QuickChat.__init__(self, host, target, type_, profiles=profiles)
self.content = urwid.SimpleListWalker([])
self.text_list = urwid.ListBox(self.content)
@@ -93,10 +93,10 @@ class Chat(PrimitivusWidget, QuickChat):
self.pile = urwid.Pile([self.chat_colums])
PrimitivusWidget.__init__(self, self.pile, self.target)
- # we must adapt the behavious with the type
- if type_ == 'one2one':
+ # we must adapt the behaviour with the type
+ if type_ == C.CHAT_ONE2ONE:
self.historyPrint(profile=self.profile)
- elif type_ == 'group':
+ elif type_ == C.CHAT_GROUP:
if len(self.chat_colums.contents) == 1:
present_widget = self._buildPresentList()
self.present_panel = sat_widgets.VerticalSeparator(present_widget)
@@ -110,7 +110,7 @@ class Chat(PrimitivusWidget, QuickChat):
def keypress(self, size, key):
if key == a_key['OCCUPANTS_HIDE']: #user wants to (un)hide the presents panel
- if self.type == 'group':
+ if self.type == C.CHAT_GROUP:
widgets = [widget for (widget, options) in self.chat_colums.contents]
if self.present_panel in widgets:
self._removePresentPanel()
@@ -140,11 +140,11 @@ class Chat(PrimitivusWidget, QuickChat):
def getMenu(self):
"""Return Menu bar"""
menu = sat_widgets.Menu(self.host.loop)
- if self.type == 'group':
+ if self.type == C.CHAT_GROUP:
self.host.addMenus(menu, C.MENU_ROOM, {'room_jid': self.target.bare})
game = _("Game")
menu.addMenu(game, "Tarot", self.onTarotRequest)
- elif self.type == 'one2one':
+ elif self.type == C.CHAT_ONE2ONE:
self.host.addMenus(menu, C.MENU_SINGLE, {'jid': self.target})
menu.addMenu(_("Action"), _("Send file"), self.onSendFileRequest)
return menu
@@ -170,13 +170,13 @@ class Chat(PrimitivusWidget, QuickChat):
self.title_dynamic = '({})'.format(state)
def _presentClicked(self, list_wid, clicked_wid):
- assert self.type == 'group'
+ assert self.type == C.CHAT_GROUP
nick = clicked_wid.getValue().value
if nick == self.getUserNick():
#We ignore clicks on our own nick
return
contact_list = self.host.contact_lists[self.profile]
- full_jid = JID("%s/%s" % (self.target.bare, nick))
+ full_jid = jid.JID("%s/%s" % (self.target.bare, nick))
#we have a click on a nick, we need to create the widget if it doesn't exists
self.getOrCreatePrivateWidget(full_jid)
@@ -258,10 +258,16 @@ class Chat(PrimitivusWidget, QuickChat):
def onPrivateCreated(self, widget):
self.host.contact_lists[widget.profile].specialResourceVisible(widget.target)
- def printMessage(self, from_jid, msg, profile, timestamp=None):
- assert isinstance(from_jid, JID)
+ def printMessage(self, from_jid, msg, extra=None, profile=C.PROF_KEY_NONE):
+ assert isinstance(from_jid, jid.JID)
+ if extra is None:
+ extra = {}
try:
- jid, nick, mymess = QuickChat.printMessage(self, from_jid, msg, profile, timestamp)
+ timestamp = float(extra['timestamp'])
+ except KeyError:
+ timestamp=None
+ try:
+ nick, mymess = QuickChat.printMessage(self, from_jid, msg, extra, profile)
except TypeError:
# None is returned, the message is managed
return
@@ -291,7 +297,7 @@ class Chat(PrimitivusWidget, QuickChat):
# all messages and not only with the messages coming from the history.
self._notify(from_jid, msg)
- def printInfo(self, msg, type_='normal', timestamp=None):
+ def printInfo(self, msg, type_='normal', extra=None):
"""Print general info
@param msg: message to print
@type_: one of:
@@ -299,6 +305,12 @@ class Chat(PrimitivusWidget, QuickChat):
me: "/me" information like "/me clenches his fist" ==> "toto clenches his fist"
@param timestamp (float): number of seconds since epoch
"""
+ if extra is None:
+ extra = {}
+ try:
+ timestamp = float(extra['timestamp'])
+ except KeyError:
+ timestamp=None
_widget = ChatText(self, timestamp, None, False, msg, is_info=True)
self.content.append(_widget)
self._notify(msg=msg)
@@ -316,7 +328,7 @@ class Chat(PrimitivusWidget, QuickChat):
self.text_list.focus_position = len(self.content) - 1
self.host.redraw()
if not self.host.x_notify.hasFocus():
- if self.type == "one2one":
+ if self.type == C.CHAT_ONE2ONE:
self.host.x_notify.sendNotification(_("Primitivus: %s is talking to you") % from_jid)
elif self.getUserNick().lower() in msg.lower():
self.host.x_notify.sendNotification(_("Primitivus: %(user)s mentioned you in room '%(room)s'") % {'user': from_jid, 'room': self.target})
@@ -356,9 +368,9 @@ class Chat(PrimitivusWidget, QuickChat):
self.host.showDialog(_(u"File has a unicode error in its name, it's not yet managed by SàT"), title=_("Can't send file"), type_="error")
return
#FIXME: check last_resource: what if self.target.resource exists ?
- last_resource = self.host.bridge.getLastResource(unicode(self.target.bare), self.profile)
+ last_resource = self.host.bridge.getMainResource(unicode(self.target.bare), self.profile)
if last_resource:
- full_jid = JID("%s/%s" % (self.target.bare, last_resource))
+ full_jid = jid.JID("%s/%s" % (self.target.bare, last_resource))
else:
full_jid = self.target
progress_id = self.host.bridge.sendFile(full_jid, filepath, {}, self.profile)
diff --git a/frontends/src/primitivus/contact_list.py b/frontends/src/primitivus/contact_list.py
index f7c39ca2..c5345546 100644
--- a/frontends/src/primitivus/contact_list.py
+++ b/frontends/src/primitivus/contact_list.py
@@ -148,6 +148,10 @@ class ContactList(PrimitivusWidget, QuickContactList):
self.update()
self._emit('click', entity)
+ def updatePresence(self, entity, show, priority, statuses):
+ super(ContactList, self).updatePresence(entity, show, priority, statuses)
+ self.update()
+
# Methods to build the widget
def _buildEntityWidget(self, entity, keys=None, use_bare_jid=False, with_alert=True, with_show_attr=True, markup_prepend=None, markup_append = None):
diff --git a/frontends/src/primitivus/primitivus b/frontends/src/primitivus/primitivus
index 8a85d6de..bbb9b326 100755
--- a/frontends/src/primitivus/primitivus
+++ b/frontends/src/primitivus/primitivus
@@ -27,8 +27,9 @@ log = logging.getLogger(__name__)
import urwid
from urwid_satext import sat_widgets
from urwid_satext.files_management import FileDialog
+from sat_frontends.bridge.DBus import DBusBridgeFrontend
from sat_frontends.quick_frontend.quick_app import QuickApp
-from sat_frontends.quick_frontend.quick_utils import getNewPath
+from sat_frontends.quick_frontend import quick_utils
from sat_frontends.quick_frontend import quick_chat
from sat_frontends.primitivus.profile_manager import ProfileManager
from sat_frontends.primitivus.contact_list import ContactList
@@ -277,14 +278,14 @@ class PrimitivusTopWidget(sat_widgets.FocusPile):
class PrimitivusApp(QuickApp, InputHistory):
def __init__(self):
- QuickApp.__init__(self)
+ QuickApp.__init__(self, create_bridge=DBusBridgeFrontend, check_options=quick_utils.check_options)
## main loop setup ##
self.main_widget = ProfileManager(self)
self.loop = urwid.MainLoop(self.main_widget, C.PALETTE, event_loop=urwid.GLibEventLoop(), input_filter=self.inputFilter, unhandled_input=self.keyHandler)
-
##misc setup##
+ self._visible_widgets = set()
self.notif_bar = sat_widgets.NotificationBar()
urwid.connect_signal(self.notif_bar, 'change', self.onNotification)
@@ -297,6 +298,10 @@ class PrimitivusApp(QuickApp, InputHistory):
# we already manage exit with a_key['APP_QUIT'], so we don't want C-c
signal.signal(signal.SIGINT, signal.SIG_IGN)
+ @property
+ def visible_widgets(self):
+ return self._visible_widgets
+
@property
def mode(self):
return self.editBar.mode
@@ -496,14 +501,14 @@ class PrimitivusApp(QuickApp, InputHistory):
self.redraw()
def newWidget(self, widget):
+ self.selectWidget(widget)
+
+ def selectWidget(self, widget):
"""Display a widget if possible,
else add it in the notification bar queue
@param widget: BoxWidget
"""
- self.selectWidget(widget)
-
- def selectWidget(self, widget):
assert len(self.center_part.widget_list)<=2
wid_idx = len(self.center_part.widget_list)-1
self.center_part.widget_list[wid_idx] = widget
@@ -512,7 +517,7 @@ class PrimitivusApp(QuickApp, InputHistory):
except KeyError:
log.debug("No menu to delete")
self.selected_widget = widget
- self.visible_widgets = set([widget]) # XXX: we can only have one widget visible at the time for now
+ self._visible_widgets = set([widget]) # XXX: we can only have one widget visible at the time for now
for contact_list in self.contact_lists.itervalues():
contact_list.unselectAll()
@@ -642,7 +647,7 @@ class PrimitivusApp(QuickApp, InputHistory):
def dir_selected_cb(path):
dest_path = join(path, data['filename'])
- answer_data["dest_path"] = getNewPath(dest_path)
+ answer_data["dest_path"] = quick_utils.getNewPath(dest_path)
self.addProgress(confirmation_id, dest_path)
accept_cb(None)
diff --git a/frontends/src/quick_frontend/constants.py b/frontends/src/quick_frontend/constants.py
index 18ca2893..32f3d6f6 100644
--- a/frontends/src/quick_frontend/constants.py
+++ b/frontends/src/quick_frontend/constants.py
@@ -22,10 +22,6 @@ from sat_frontends import constants
class Const(constants.Const):
- #Chats
- CHAT_ONE2ONE = 'one2one'
- CHAT_GROUP = 'group'
-
#Contact list
CONTACT_GROUPS = 'groups'
CONTACT_RESOURCES = 'resources'
diff --git a/frontends/src/quick_frontend/quick_app.py b/frontends/src/quick_frontend/quick_app.py
index 67398580..e8cf66f7 100644
--- a/frontends/src/quick_frontend/quick_app.py
+++ b/frontends/src/quick_frontend/quick_app.py
@@ -22,11 +22,9 @@ import sys
from sat.core.log import getLogger
log = getLogger(__name__)
from sat.core import exceptions
-from sat_frontends.bridge.DBus import DBusBridgeFrontend
from sat_frontends.tools import jid
from sat_frontends.quick_frontend.quick_widgets import QuickWidgetsManager
from sat_frontends.quick_frontend import quick_chat
-from optparse import OptionParser
from sat_frontends.quick_frontend.constants import Const as C
@@ -78,20 +76,54 @@ class ProfileManager(object):
contact_list.fill()
#The waiting subscription requests
- waitingSub = self.bridge.getWaitingSub(self.profile)
- for sub in waitingSub:
- self.host.subscribeHandler(waitingSub[sub], sub, self.profile)
+ self.bridge.getWaitingSub(self.profile, callback=self._plug_profile_gotWaitingSub)
- #Now we open the MUC window where we already are:
- for room_args in self.bridge.getRoomsJoined(self.profile):
- self.host.roomJoinedHandler(*room_args, profile=self.profile)
+ def _plug_profile_gotWaitingSub(self, waiting_sub):
+ for sub in waiting_sub:
+ self.host.subscribeHandler(waiting_sub[sub], sub, self.profile)
- for subject_args in self.bridge.getRoomsSubjects(self.profile):
- self.host.roomNewSubjectHandler(*subject_args, profile=self.profile)
+ self.bridge.getRoomsJoined(self.profile, callback=self._plug_profile_gotRoomsJoined)
- #Finaly, we get the waiting confirmation requests
- for confirm_id, confirm_type, data in self.bridge.getWaitingConf(self.profile):
- self.host.askConfirmationHandler(confirm_id, confirm_type, data, self.profile)
+ def _plug_profile_gotRoomsJoined(self, rooms_args):
+ #Now we open the MUC window where we already are:
+ for room_args in rooms_args:
+ self.host.roomJoinedHandler(*room_args, profile=self.profile)
+
+ self.bridge.getRoomsSubjects(self.profile, callback=self._plug_profile_gotRoomsSubjects)
+
+ def _plug_profile_gotRoomsSubjects(self, subjects_args):
+ for subject_args in subjects_args:
+ self.host.roomNewSubjectHandler(*subject_args, profile=self.profile)
+
+ #Presence must be requested after rooms are filled
+ self.host.bridge.getPresenceStatuses(self.profile, callback=self._plug_profile_gotPresences)
+
+
+ def _plug_profile_gotPresences(self, presences):
+ def gotEntityData(data, contact):
+ for key in ('avatar', 'nick'):
+ if key in data:
+ self.host.entityDataUpdatedHandler(contact, key, data[key], self.profile)
+
+ for contact in presences:
+ for res in presences[contact]:
+ jabber_id = ('%s/%s' % (jid.JID(contact).bare, res)) if res else contact
+ show = presences[contact][res][0]
+ priority = presences[contact][res][1]
+ statuses = presences[contact][res][2]
+ self.host.presenceUpdateHandler(jabber_id, show, priority, statuses, self.profile)
+ self.host.bridge.getEntityData(contact, ['avatar', 'nick'], self.profile, callback=lambda data, contact=contact: gotEntityData(data, contact), errback=lambda failure, contact=contact: log.debug("No cache data for {}".format(contact)))
+
+ #Finaly, we get the waiting confirmation requests
+ self.bridge.getWaitingConf(self.profile, callback=self._plug_profile_gotWaitingConf)
+
+ def _plug_profile_gotWaitingConf(self, waiting_confs):
+ for confirm_id, confirm_type, data in waiting_confs:
+ self.host.askConfirmationHandler(confirm_id, confirm_type, data, self.profile)
+
+ # At this point, profile should be fully plugged
+ # and we launch frontend specific method
+ self.host.profilePlugged(self.profile)
def _getParamError(self, ignore):
log.error(_("Can't get profile parameter"))
@@ -132,20 +164,27 @@ class ProfilesManager(object):
class QuickApp(object):
"""This class contain the main methods needed for the frontend"""
- def __init__(self):
+ def __init__(self, create_bridge, check_options=None):
+ """Create a frontend application
+
+ @param create_bridge: method to use to create the Bridge
+ @param check_options: method to call to check options (usually command line arguments)
+ """
ProfileManager.host = self
self.profiles = ProfilesManager()
self.contact_lists = {}
self.widgets = QuickWidgetsManager(self)
- self.check_options()
+ if check_options is not None:
+ self.options = check_options()
+ else:
+ self.options = None
# widgets
- self.visible_widgets = set() # widgets currently visible (must be filled by frontend)
self.selected_widget = None # widget currently selected (must be filled by frontend)
## bridge ##
try:
- self.bridge = DBusBridgeFrontend()
+ self.bridge = create_bridge()
except exceptions.BridgeExceptionNoService:
print(_(u"Can't connect to SàT backend, are you sure it's launched ?"))
sys.exit(1)
@@ -202,6 +241,11 @@ class QuickApp(object):
except (TypeError, AttributeError):
return self.profiles.chooseOneProfile()
+ @property
+ def visible_widgets(self):
+ """widgets currently visible (must be implemented by frontend)"""
+ raise NotImplementedError
+
def registerSignal(self, functionName, handler=None, iface="core", with_profile=True):
"""Register a handler for a signal
@@ -211,7 +255,7 @@ class QuickApp(object):
@param with_profile (boolean): True if the signal concerns a specific profile, in that case the profile name has to be passed by the caller
"""
if handler is None:
- handler = getattr(self, "%s%s" % (functionName, 'Handler'))
+ handler = getattr(self, "{}{}".format(functionName, 'Handler'))
if not with_profile:
self.bridge.register(functionName, handler, iface)
return
@@ -236,24 +280,15 @@ class QuickApp(object):
@param profile_manager: instance of a subclass of Quick_frontend.QuickProfileManager
"""
- if self.options.profile:
+ if self.options and self.options.profile:
profile_manager.autoconnect([self.options.profile])
- def check_options(self):
- """Check command line options"""
- usage = _("""
- %prog [options]
+ def profilePlugged(self, profile):
+ """Method called when the profile is fully plugged, to launch frontend specific workflow
- %prog --help for options list
- """)
- parser = OptionParser(usage=usage) # TODO: use argparse
-
- parser.add_option("-p", "--profile", help=_("Select the profile to use"))
-
- (self.options, args) = parser.parse_args()
- if self.options.profile:
- self.options.profile = self.options.profile.decode('utf-8')
- return args
+ @param profile(unicode): %(doc_profile)s
+ """
+ pass
def asyncConnect(self, profile, callback=None, errback=None):
if not callback:
@@ -281,7 +316,7 @@ class QuickApp(object):
will be called when profiles are choosen and are to be plugged soon
"""
- raise NotImplementedError
+ pass
def unplug_profile(self, profile):
"""Tell the application to not follow anymore the profile"""
@@ -292,6 +327,14 @@ class QuickApp(object):
def clear_profile(self):
self.profiles.clear()
+ def addContactList(self, profile):
+ """Method to subclass to add a contact list widget
+
+ will be called on each profile session build
+ @return: a ContactList widget
+ """
+ return NotImplementedError
+
def newWidget(self, widget):
raise NotImplementedError
@@ -331,7 +374,7 @@ class QuickApp(object):
def sendMessage(self, to_jid, message, subject='', mess_type="auto", extra={}, callback=None, errback=None, profile_key=C.PROF_KEY_NONE):
if callback is None:
- callback = lambda: None
+ callback = lambda dummy=None: None # FIXME: optional argument is here because pyjamas doesn't support callback without arg with json proxy
if errback is None:
errback = lambda failure: self.showDialog(failure.fullname, failure.message, "error")
self.bridge.sendMessage(to_jid, message, subject, mess_type, extra, profile_key, callback=callback, errback=errback)
@@ -500,10 +543,9 @@ class QuickApp(object):
@profile: current profile
"""
from_jid = jid.JID(from_jid_s) if from_jid_s != C.ENTITY_ALL else C.ENTITY_ALL
- for widget in self.visible_widgets:
- if isinstance(widget, quick_chat.QuickChat):
- if from_jid == C.ENTITY_ALL or from_jid.bare == widget.target.bare:
- widget.updateChatState(from_jid, state)
+ for widget in self.widgets.getWidgets(quick_chat.QuickChat):
+ if from_jid == C.ENTITY_ALL or from_jid.bare == widget.target.bare:
+ widget.updateChatState(from_jid, state)
def _subscribe_cb(self, answer, data):
entity, profile = data
@@ -554,8 +596,9 @@ class QuickApp(object):
self.contact_lists[profile].setCache(entity, 'nick', value)
elif key == "avatar":
if entity in self.contact_lists[profile]:
- filename = self.bridge.getAvatarFile(value)
- self.contact_lists[profile].setCache(entity, 'avatar', filename)
+ def gotFilename(filename):
+ self.contact_lists[profile].setCache(entity, 'avatar', filename)
+ self.bridge.getAvatarFile(value, callback=gotFilename)
def askConfirmationHandler(self, confirm_id, confirm_type, data, profile):
raise NotImplementedError
diff --git a/frontends/src/quick_frontend/quick_chat.py b/frontends/src/quick_frontend/quick_chat.py
index 5a22ffab..cd976b5d 100644
--- a/frontends/src/quick_frontend/quick_chat.py
+++ b/frontends/src/quick_frontend/quick_chat.py
@@ -20,11 +20,18 @@
from sat.core.i18n import _
from sat.core.log import getLogger
log = getLogger(__name__)
-from sat_frontends.tools.jid import JID
+from sat_frontends.tools import jid
from sat_frontends.quick_frontend import quick_widgets
from sat_frontends.quick_frontend.constants import Const as C
+try:
+ # FIXME: to be removed when an acceptable solution is here
+ unicode('') # XXX: unicode doesn't exist in pyjamas
+except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options
+ unicode = lambda x: str(x)
+
+
class QuickChat(quick_widgets.QuickWidget):
def __init__(self, host, target, type_=C.CHAT_ONE2ONE, profiles=None):
@@ -153,11 +160,11 @@ class QuickChat(quick_widgets.QuickWidget):
def onHistory(history):
for line in history:
- timestamp, from_jid, to_jid, message, _type, extra = line
- if ((self.type == C.CHAT_GROUP and _type != C.MESS_TYPE_GROUPCHAT) or
- (self.type == C.CHAT_ONE2ONE and _type == C.MESS_TYPE_GROUPCHAT)):
+ timestamp, from_jid, to_jid, message, type_, extra = line # FIXME: extra is unused !
+ if ((self.type == C.CHAT_GROUP and type_ != C.MESS_TYPE_GROUPCHAT) or
+ (self.type == C.CHAT_ONE2ONE and type_ == C.MESS_TYPE_GROUPCHAT)):
continue
- self.printMessage(JID(from_jid), message, profile, timestamp)
+ self.printMessage(jid.JID(from_jid), message, {'timestamp':timestamp}, profile)
self.afterHistoryPrint()
def onHistoryError(err):
@@ -165,7 +172,7 @@ class QuickChat(quick_widgets.QuickWidget):
target = self.target.bare
- return self.host.bridge.getHistory(self.host.profiles[profile].whoami.bare, target, size, search=search, profile=profile, callback=onHistory, errback=onHistoryError)
+ self.host.bridge.getHistory(unicode(self.host.profiles[profile].whoami.bare), unicode(target), size, True, search, profile, callback=onHistory, errback=onHistoryError)
def _get_nick(self, entity):
"""Return nick of this entity when possible"""
@@ -193,29 +200,27 @@ class QuickChat(quick_widgets.QuickWidget):
chat_widget = self.getOrCreatePrivateWidget(target)
chat_widget.newMessage(from_jid, target, msg, type_, extra, profile)
else:
- timestamp = extra.get('archive')
if type_ == C.MESS_TYPE_INFO:
- self.printInfo(msg, timestamp=float(timestamp) if timestamp else None)
+ self.printInfo(msg, extra=extra)
else:
- self.printMessage(from_jid, msg, profile, float(timestamp) if timestamp else None)
+ self.printMessage(from_jid, msg, extra, profile)
- def printMessage(self, from_jid, msg, profile, timestamp=None):
+ def printMessage(self, from_jid, msg, extra=None, profile=C.PROF_KEY_NONE):
"""Print message in chat window. Must be implemented by child class"""
- jid = JID(from_jid)
- nick = self._get_nick(jid)
- mymess = (jid.resource == self.nick) if self.type == C.CHAT_GROUP else (jid.bare == self.host.profiles[profile].whoami.bare) #mymess = True if message comes from local user
+ nick = self._get_nick(from_jid)
+ mymess = (from_jid.resource == self.nick) if self.type == C.CHAT_GROUP else (from_jid.bare == self.host.profiles[profile].whoami.bare) #mymess = True if message comes from local user
if msg.startswith('/me '):
- self.printInfo('* %s %s' % (nick, msg[4:]), type_='me', timestamp=timestamp)
+ self.printInfo('* %s %s' % (nick, msg[4:]), type_='me', extra=extra)
return
- return jid, nick, mymess
+ return nick, mymess
- def printInfo(self, msg, type_='normal', timestamp=None):
+ def printInfo(self, msg, type_='normal', extra=None):
"""Print general info
@param msg: message to print
@type_: one of:
normal: general info like "toto has joined the room"
me: "/me" information like "/me clenches his fist" ==> "toto clenches his fist"
- @param timestamp (float): number of seconds since epoch
+ @param extra (dict): message data
"""
raise NotImplementedError
diff --git a/frontends/src/quick_frontend/quick_contact_list.py b/frontends/src/quick_frontend/quick_contact_list.py
index b6cd3ae5..b39e1f80 100644
--- a/frontends/src/quick_frontend/quick_contact_list.py
+++ b/frontends/src/quick_frontend/quick_contact_list.py
@@ -22,7 +22,18 @@ from sat.core.log import getLogger
log = getLogger(__name__)
from sat_frontends.quick_frontend.quick_widgets import QuickWidget
from sat_frontends.quick_frontend.constants import Const as C
-from sat_frontends.tools import jid
+
+
+try:
+ # FIXME: to be removed when an acceptable solution is here
+ unicode('') # XXX: unicode doesn't exist in pyjamas
+except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options
+ # XXX: pyjamas' max doesn't support key argument, so we implement it ourself
+ pyjamas_max = max
+ def max(iterable, key):
+ iter_cpy = list(iterable)
+ iter_cpy.sort(key=key)
+ return pyjamas_max(iter_cpy)
class QuickContactList(QuickWidget):
@@ -78,18 +89,6 @@ class QuickContactList(QuickWidget):
for contact in contacts:
self.host.newContactHandler(*contact, profile=self.profile)
- presences = self.host.bridge.getPresenceStatuses(self.profile)
- for contact in presences:
- for res in presences[contact]:
- jabber_id = ('%s/%s' % (jid.JID(contact).bare, res)) if res else contact
- show = presences[contact][res][0]
- priority = presences[contact][res][1]
- statuses = presences[contact][res][2]
- self.host.presenceUpdateHandler(jabber_id, show, priority, statuses, self.profile)
- data = self.host.bridge.getEntityData(contact, ['avatar', 'nick'], self.profile)
- for key in ('avatar', 'nick'):
- if key in data:
- self.host.entityDataUpdatedHandler(contact, key, data[key], self.profile)
self.host.bridge.getContacts(self.profile, callback=gotContacts)
def update(self):
@@ -105,7 +104,12 @@ class QuickContactList(QuickWidget):
- if resource is given, it is used
@param name(unicode): name the data to get, or None to get everything
"""
- cache = self._cache[entity.bare]
+ try:
+ cache = self._cache[entity.bare]
+ except KeyError:
+ self.setContact(entity)
+ cache = self._cache[entity.bare]
+
if name is None:
return cache
try:
@@ -280,10 +284,6 @@ class QuickContactList(QuickWidget):
set_.difference_update(to_remove)
self.update()
- def add(self, jid, param_groups=None):
- """add a contact to the list"""
- raise NotImplementedError
-
def updatePresence(self, entity, show, priority, statuses):
"""Update entity's presence status
@@ -311,7 +311,6 @@ class QuickContactList(QuickWidget):
priority_resource = max(resources_data, key=lambda res: resources_data[res][C.PRESENCE_PRIORITY])
cache[C.CONTACT_MAIN_RESOURCE] = priority_resource
- self.update()
def unselectAll(self):
"""Unselect all contacts"""
diff --git a/frontends/src/quick_frontend/quick_utils.py b/frontends/src/quick_frontend/quick_utils.py
index 18f9c430..31aae3bf 100644
--- a/frontends/src/quick_frontend/quick_utils.py
+++ b/frontends/src/quick_frontend/quick_utils.py
@@ -17,7 +17,9 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+from sat.core.i18n import _
from os.path import exists, splitext
+from optparse import OptionParser
def getNewPath(path):
""" Check if path exists, and find a non existant path if needed """
@@ -31,3 +33,19 @@ def getNewPath(path):
return new_path
idx+=1
+def check_options():
+ """Check command line options"""
+ usage = _("""
+ %prog [options]
+
+ %prog --help for options list
+ """)
+ parser = OptionParser(usage=usage) # TODO: use argparse
+
+ parser.add_option("-p", "--profile", help=_("Select the profile to use"))
+
+ (options, args) = parser.parse_args()
+ if options.profile:
+ options.profile = options.profile.decode('utf-8')
+ return options
+
diff --git a/frontends/src/quick_frontend/quick_widgets.py b/frontends/src/quick_frontend/quick_widgets.py
index 72142cd7..3d4ee4ed 100644
--- a/frontends/src/quick_frontend/quick_widgets.py
+++ b/frontends/src/quick_frontend/quick_widgets.py
@@ -25,6 +25,13 @@ from sat.core import exceptions
classes_map = {}
+try:
+ # FIXME: to be removed when an acceptable solution is here
+ unicode('') # XXX: unicode doesn't exist in pyjamas
+except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options
+ unicode = str
+
+
def register(base_cls, child_cls=None):
"""Register a child class to use by default when a base class is needed
@@ -43,6 +50,40 @@ class QuickWidgetsManager(object):
self.host = host
self._widgets = {}
+ def __iter__(self):
+ """Iterate throught all widgets"""
+ for widget_map in self._widgets.itervalues():
+ for widget in widget_map.itervalues():
+ yield widget
+
+ def getRealClass(self, class_):
+ """Return class registered for given class_
+
+ @param class_: subclass of QuickWidget
+ @return: class actually used to create widget
+ """
+ try:
+ cls = classes_map[class_]
+ except KeyError:
+ cls = class_
+ if cls is None:
+ raise exceptions.InternalError("There is not class registered for {}".format(class_))
+ return cls
+
+ def getWidgets(self, class_):
+ """Get all subclassed widgets
+
+ @param class_: subclass of QuickWidget, same parameter as used in [getOrCreateWidget]
+ @return: iterator on widgets
+ """
+ class_ = self.getRealClass(class_)
+ try:
+ widgets_map = self._widgets[class_]
+ except KeyError:
+ return iter([])
+ else:
+ return widgets_map.itervalues()
+
def getOrCreateWidget(self, class_, target, *args, **kwargs):
"""Get an existing widget or create a new one when necessary
@@ -60,13 +101,7 @@ class QuickWidgetsManager(object):
if 'force_hash' is present, the hash given in value will be used instead of the one returned by class_.getWidgetHash
@return: a class_ instance, either new or already existing
"""
- # class management
- try:
- cls = classes_map[class_]
- except KeyError:
- cls = class_
- if cls is None:
- raise exceptions.InternalError("There is not class registered for {}".format(class_))
+ cls = self.getRealClass(class_)
# arguments management
_args = [self.host, target] + list(args) or [] # FIXME: check if it's really necessary to use optional args
@@ -86,12 +121,12 @@ class QuickWidgetsManager(object):
hash_ = cls.getWidgetHash(target, _kwargs['profiles'])
# widget creation or retrieval
- widgets_list = self._widgets.setdefault(cls, {}) # we sorts widgets by classes
+ widgets_map = self._widgets.setdefault(cls, {}) # we sorts widgets by classes
if not cls.SINGLE:
widget = None # if the class is not SINGLE, we always create a new widget
else:
try:
- widget = widgets_list[hash_]
+ widget = widgets_map[hash_]
widget.addTarget(target)
except KeyError:
widget = None
@@ -106,7 +141,7 @@ class QuickWidgetsManager(object):
log.debug(u"Creating new widget for target {} {}".format(target, cls))
widget = cls(*_args, **_kwargs)
- widgets_list[hash_] = widget
+ widgets_map[hash_] = widget
if on_new_widget == 'NEW_WIDGET':
self.host.newWidget(widget)
diff --git a/frontends/src/tools/jid.py b/frontends/src/tools/jid.py
index c1d5e6a1..40facee7 100644
--- a/frontends/src/tools/jid.py
+++ b/frontends/src/tools/jid.py
@@ -18,20 +18,84 @@
# along with this program. If not, see .
-class JID(unicode):
+# hack to use this module with pyjamas
+try:
+ unicode('') # XXX: unicode doesn't exist in pyjamas
+
+ # normal version
+ class BaseJID(unicode):
+ def __new__(cls, jid_str):
+ self = unicode.__new__(cls, cls._normalize(jid_str))
+ return self
+
+ def __init__(self, jid_str):
+ pass
+
+ def _parse(self):
+ """Find node domain and resource"""
+ node_end = self.find('@')
+ if node_end < 0:
+ node_end = 0
+ domain_end = self.find('/')
+ if domain_end == 0:
+ raise ValueError("a jid can't start with '/'")
+ if domain_end == -1:
+ domain_end = len(self)
+ self.node = self[:node_end] or None
+ self.domain = self[(node_end + 1) if node_end else 0:domain_end]
+ self.resource = self[domain_end + 1:] or None
+
+except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options
+ # pyjamas version
+ class BaseJID(object):
+ def __init__(self, jid_str):
+ self.__internal_str = JID._normalize(jid_str)
+
+ def __str__(self):
+ return self.__internal_str
+
+ def __getattr__(self, name):
+ return getattr(self.__internal_str, name)
+
+ def __eq__(self, other):
+ return (self.node == other.node
+ and self.domain == other.domain
+ and self.resource == other.resource)
+
+ def __hash__(self):
+ return hash(self.__internal_str)
+
+ def find(self, *args):
+ return self.__internal_str.find(*args)
+
+ def _parse(self):
+ """Find node domain and resource"""
+ node_end = self.__internal_str.find('@')
+ if node_end < 0:
+ node_end = 0
+ domain_end = self.__internal_str.find('/')
+ if domain_end == 0:
+ raise ValueError("a jid can't start with '/'")
+ if domain_end == -1:
+ domain_end = len(self.__internal_str)
+ self.node = self.__internal_str[:node_end] or None
+ self.domain = self.__internal_str[(node_end + 1) if node_end else 0:domain_end]
+ self.resource = self.__internal_str[domain_end + 1:] or None
+
+
+class JID(BaseJID):
"""This class help manage JID (Node@Domaine/Resource)"""
- def __new__(cls, jid):
- self = unicode.__new__(cls, cls._normalize(jid))
+ def __init__(self, jid_str):
+ super(JID, self).__init__(jid_str)
self._parse()
- return self
- @classmethod
- def _normalize(cls, jid):
+ @staticmethod
+ def _normalize(jid_str):
"""Naive normalization before instantiating and parsing the JID"""
- if not jid:
- return jid
- tokens = jid.split('/')
+ if not jid_str:
+ return jid_str
+ tokens = jid_str.split('/')
tokens[0] = tokens[0].lower() # force node and domain to lower-case
return '/'.join(tokens)
@@ -41,20 +105,6 @@ class JID(unicode):
return JID(self.domain)
return JID(u"{}@{}".format(self.node, self.domain))
- def _parse(self):
- """Find node domain and resource"""
- node_end = self.find('@')
- if node_end < 0:
- node_end = 0
- domain_end = self.find('/')
- if domain_end == 0:
- raise ValueError("a jid can't start with '/'")
- if domain_end == -1:
- domain_end = len(self)
- self.node = self[:node_end] or None
- self.domain = self[(node_end + 1) if node_end else 0:domain_end]
- self.resource = self[domain_end + 1:] or None
-
def is_valid(self):
"""
@return: True if the JID is XMPP compliant
diff --git a/frontends/src/tools/xmlui.py b/frontends/src/tools/xmlui.py
index 46319ada..2573f25d 100644
--- a/frontends/src/tools/xmlui.py
+++ b/frontends/src/tools/xmlui.py
@@ -210,7 +210,7 @@ class XMLUIBase(object):
@param title: force the title, or use XMLUI one if None
@param flags: list of string which can be:
- NO_CANCEL: the UI can't be cancelled
- @param callback: if present, will be use with launchAction
+ @param callback: if present, will be used with launchAction
"""
self.host = host
top=parsed_dom.documentElement
@@ -289,7 +289,7 @@ class XMLUIPanel(XMLUIBase):
self._main_cont = value
def _parseChilds(self, _xmlui_parent, current_node, wanted = ('container',), data = None):
- """Recursively parse childNodes of an elemen
+ """Recursively parse childNodes of an element
@param _xmlui_parent: widget container with '_xmluiAppend' method
@param current_node: element from which childs will be parsed
@@ -695,7 +695,7 @@ def registerClass(type_, class_):
def create(host, xml_data, title=None, flags=None, dom_parse=None, dom_free=None, callback=None, profile=C.PROF_KEY_NONE):
"""
- @param dom_parse: methode equivalent to minidom.parseString (but which must manage unicode), or None to use default one
+ @param dom_parse: methode equivalent to minidom.parseString (but which must manage unicode), or None to use default one
@param dom_free: method used to free the parsed DOM
"""
if dom_parse is None:
diff --git a/src/bridge/DBus.py b/src/bridge/DBus.py
index 430844ae..58307bb9 100755
--- a/src/bridge/DBus.py
+++ b/src/bridge/DBus.py
@@ -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',
diff --git a/src/bridge/bridge_constructor/bridge_constructor.py b/src/bridge/bridge_constructor/bridge_constructor.py
index 2eb1abbf..d9bd34ec 100755
--- a/src/bridge/bridge_constructor/bridge_constructor.py
+++ b/src/bridge/bridge_constructor/bridge_constructor.py
@@ -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,
diff --git a/src/bridge/bridge_constructor/bridge_template.ini b/src/bridge/bridge_constructor/bridge_template.ini
index e6cb7b4e..7ccff187 100644
--- a/src/bridge/bridge_constructor/bridge_template.ini
+++ b/src/bridge/bridge_constructor/bridge_template.ini
@@ -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
diff --git a/src/bridge/bridge_constructor/dbus_frontend_template.py b/src/bridge/bridge_constructor/dbus_frontend_template.py
index edafce05..88d86f5e 100644
--- a/src/bridge/bridge_constructor/dbus_frontend_template.py
+++ b/src/bridge/bridge_constructor/dbus_frontend_template.py
@@ -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##
diff --git a/src/core/constants.py b/src/core/constants.py
index 295fc2c5..ebf2a872 100644
--- a/src/core/constants.py
+++ b/src/core/constants.py
@@ -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'
diff --git a/src/core/exceptions.py b/src/core/exceptions.py
index d17cbd8b..653aaeea 100644
--- a/src/core/exceptions.py
+++ b/src/core/exceptions.py
@@ -94,5 +94,9 @@ class PasswordError(Exception):
pass
+class PermissionError(Exception):
+ pass
+
+
class SkipHistory(Exception): # used in MessageReceivedTrigger to avoid history writting
pass
diff --git a/src/core/sat_main.py b/src/core/sat_main.py
index 12aba47c..3b855cd2 100644
--- a/src/core/sat_main.py
+++ b/src/core/sat_main.py
@@ -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"""
diff --git a/src/memory/disco.py b/src/memory/disco.py
index 8d1c7c30..63eb0605 100755
--- a/src/memory/disco.py
+++ b/src/memory/disco.py
@@ -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)
diff --git a/src/memory/memory.py b/src/memory/memory.py
index b5dff8d8..3928cb1b 100644
--- a/src/memory/memory.py
+++ b/src/memory/memory.py
@@ -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
diff --git a/src/memory/params.py b/src/memory/params.py
index 840e8d26..f97c2874 100644
--- a/src/memory/params.py
+++ b/src/memory/params.py
@@ -49,14 +49,14 @@ class Params(object):
-
+
-
-
+
+
@@ -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)
diff --git a/src/plugins/plugin_misc_text_commands.py b/src/plugins/plugin_misc_text_commands.py
index e3551b73..6d99d81b 100755
--- a/src/plugins/plugin_misc_text_commands.py
+++ b/src/plugins/plugin_misc_text_commands.py
@@ -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}]
diff --git a/src/plugins/plugin_sec_otr.py b/src/plugins/plugin_sec_otr.py
index d12b0cd5..b5ea5d17 100644
--- a/src/plugins/plugin_sec_otr.py
+++ b/src/plugins/plugin_sec_otr.py
@@ -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
diff --git a/src/plugins/plugin_xep_0054.py b/src/plugins/plugin_xep_0054.py
index 1893e0d1..b7d30d6e 100755
--- a/src/plugins/plugin_xep_0054.py
+++ b/src/plugins/plugin_xep_0054.py
@@ -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)
diff --git a/src/plugins/plugin_xep_0060.py b/src/plugins/plugin_xep_0060.py
index e7b475c4..08127f13 100755
--- a/src/plugins/plugin_xep_0060.py
+++ b/src/plugins/plugin_xep_0060.py
@@ -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):
diff --git a/src/plugins/plugin_xep_0085.py b/src/plugins/plugin_xep_0085.py
index 71f5322f..cb558afb 100755
--- a/src/plugins/plugin_xep_0085.py
+++ b/src/plugins/plugin_xep_0085.py
@@ -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':