changeset 1290:faa1129559b8 frontends_multi_profiles

core, frontends: refactoring to base Libervia on QuickFrontend (big mixed commit): /!\ not finished, everything is still instable ! - bridge: DBus bridge has been modified to allow blocking call to be called in the same way as asynchronous calls - bridge: calls with a callback and no errback are now possible, default errback log the error - constants: removed hack to manage presence without OrderedDict, as an OrderedDict like class has been implemented in Libervia - core: getLastResource has been removed and replaced by getMainResource (there is a global better management of resources) - various style improvments: use of constants when possible, fixed variable overlaps, import of module instead of direct class import - frontends: printInfo and printMessage methods in (Quick)Chat are more generic (use of extra instead of timestamp) - frontends: bridge creation and option parsing (command line arguments) are now specified by the frontend in QuickApp __init__ - frontends: ProfileManager manage a more complete plug sequence (some stuff formerly manage in contact_list have moved to ProfileManager) - quick_frontend (quick_widgets): QuickWidgetsManager is now iterable (all widgets are then returned), or can return an iterator on a specific class (return all widgets of this class) with getWidgets - frontends: tools.jid can now be used in Pyjamas, with some care - frontends (XMLUI): profile is now managed - core (memory): big improvment on entities cache management (and specially resource management) - core (params/exceptions): added PermissionError - various fixes and improvments, check diff for more details
author Goffi <goffi@goffi.org>
date Sat, 24 Jan 2015 01:00:29 +0100
parents 653f2e2eea31
children 0db0013c59dd
files frontends/src/bridge/DBus.py frontends/src/constants.py frontends/src/jp/base.py frontends/src/primitivus/chat.py frontends/src/primitivus/contact_list.py frontends/src/primitivus/primitivus frontends/src/quick_frontend/constants.py frontends/src/quick_frontend/quick_app.py frontends/src/quick_frontend/quick_chat.py frontends/src/quick_frontend/quick_contact_list.py frontends/src/quick_frontend/quick_utils.py frontends/src/quick_frontend/quick_widgets.py frontends/src/tools/jid.py frontends/src/tools/xmlui.py src/bridge/DBus.py src/bridge/bridge_constructor/bridge_constructor.py src/bridge/bridge_constructor/bridge_template.ini src/bridge/bridge_constructor/dbus_frontend_template.py src/core/constants.py src/core/exceptions.py src/core/sat_main.py src/memory/disco.py src/memory/memory.py src/memory/params.py src/plugins/plugin_misc_text_commands.py src/plugins/plugin_sec_otr.py src/plugins/plugin_xep_0054.py src/plugins/plugin_xep_0060.py src/plugins/plugin_xep_0085.py
diffstat 29 files changed, 1114 insertions(+), 441 deletions(-) [+]
line wrap: on
line diff
--- a/frontends/src/bridge/DBus.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/frontends/src/bridge/DBus.py	Sat Jan 24 01:00:29 2015 +0100
@@ -102,10 +102,10 @@
                 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 @@
                 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)
--- a/frontends/src/constants.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/frontends/src/constants.py	Sat Jan 24 01:00:29 2015 +0100
@@ -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 @@
     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 @@
 
     # Roster
     GROUP_NOT_IN_ROSTER = D_('Not in roster')
+
+    #Chats
+    CHAT_ONE2ONE = 'one2one'
+    CHAT_GROUP = 'group'
--- a/frontends/src/jp/base.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/frontends/src/jp/base.py	Sat Jan 24 01:00:29 2015 +0100
@@ -245,11 +245,11 @@
         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
--- a/frontends/src/primitivus/chat.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/frontends/src/primitivus/chat.py	Sat Jan 24 01:00:29 2015 +0100
@@ -30,7 +30,7 @@
 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 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 @@
         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 @@
 
     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 @@
     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 @@
             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 @@
     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 @@
             # 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 @@
             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 @@
             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 @@
             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)
--- a/frontends/src/primitivus/contact_list.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/frontends/src/primitivus/contact_list.py	Sat Jan 24 01:00:29 2015 +0100
@@ -148,6 +148,10 @@
         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):
--- a/frontends/src/primitivus/primitivus	Sat Jan 24 00:15:01 2015 +0100
+++ b/frontends/src/primitivus/primitivus	Sat Jan 24 01:00:29 2015 +0100
@@ -27,8 +27,9 @@
 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 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)
 
@@ -298,6 +299,10 @@
         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 @@
         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 @@
         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 @@
 
         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)
 
--- a/frontends/src/quick_frontend/constants.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/frontends/src/quick_frontend/constants.py	Sat Jan 24 01:00:29 2015 +0100
@@ -22,10 +22,6 @@
 
 class Const(constants.Const):
 
-    #Chats
-    CHAT_ONE2ONE = 'one2one'
-    CHAT_GROUP = 'group'
-
     #Contact list
     CONTACT_GROUPS = 'groups'
     CONTACT_RESOURCES = 'resources'
--- a/frontends/src/quick_frontend/quick_app.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/frontends/src/quick_frontend/quick_app.py	Sat Jan 24 01:00:29 2015 +0100
@@ -22,11 +22,9 @@
 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 @@
             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)
+
+    def _plug_profile_gotWaitingSub(self, waiting_sub):
+        for sub in waiting_sub:
+            self.host.subscribeHandler(waiting_sub[sub], sub, self.profile)
+
+        self.bridge.getRoomsJoined(self.profile, callback=self._plug_profile_gotRoomsJoined)
+
+    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)
+
 
-            #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_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 subject_args in self.bridge.getRoomsSubjects(self.profile):
-                self.host.roomNewSubjectHandler(*subject_args, profile=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
-            for confirm_id, confirm_type, data in self.bridge.getWaitingConf(self.profile):
-                self.host.askConfirmationHandler(confirm_id, confirm_type, data, self.profile)
+        #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 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 @@
         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 @@
         @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 @@
 
         @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 @@
 
         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 @@
     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 @@
 
     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 @@
         @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 @@
                 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
--- a/frontends/src/quick_frontend/quick_chat.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/frontends/src/quick_frontend/quick_chat.py	Sat Jan 24 01:00:29 2015 +0100
@@ -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 @@
 
         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 @@
 
         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 @@
             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
 
--- a/frontends/src/quick_frontend/quick_contact_list.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/frontends/src/quick_frontend/quick_contact_list.py	Sat Jan 24 01:00:29 2015 +0100
@@ -22,7 +22,18 @@
 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 @@
             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 @@
                 - 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 @@
             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 @@
 
             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"""
--- a/frontends/src/quick_frontend/quick_utils.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/frontends/src/quick_frontend/quick_utils.py	Sat Jan 24 01:00:29 2015 +0100
@@ -17,7 +17,9 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+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 @@
             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
+
--- a/frontends/src/quick_frontend/quick_widgets.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/frontends/src/quick_frontend/quick_widgets.py	Sat Jan 24 01:00:29 2015 +0100
@@ -25,6 +25,13 @@
 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 @@
         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 @@
             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 @@
             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 @@
 
             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)
--- a/frontends/src/tools/jid.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/frontends/src/tools/jid.py	Sat Jan 24 01:00:29 2015 +0100
@@ -18,20 +18,84 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
-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 @@
             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
--- a/frontends/src/tools/xmlui.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/frontends/src/tools/xmlui.py	Sat Jan 24 01:00:29 2015 +0100
@@ -210,7 +210,7 @@
         @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 @@
         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 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:
--- a/src/bridge/DBus.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/src/bridge/DBus.py	Sat Jan 24 01:00:29 2015 +0100
@@ -285,8 +285,8 @@
     @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',
--- a/src/bridge/bridge_constructor/bridge_constructor.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/src/bridge/bridge_constructor/bridge_constructor.py	Sat Jan 24 01:00:29 2015 +0100
@@ -446,17 +446,35 @@
                 '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,
--- a/src/bridge/bridge_constructor/bridge_template.ini	Sat Jan 24 00:15:01 2015 +0100
+++ b/src/bridge/bridge_constructor/bridge_template.ini	Sat Jan 24 01:00:29 2015 +0100
@@ -279,7 +279,7 @@
 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 @@
 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
--- a/src/bridge/bridge_constructor/dbus_frontend_template.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/src/bridge/bridge_constructor/dbus_frontend_template.py	Sat Jan 24 01:00:29 2015 +0100
@@ -102,10 +102,10 @@
                 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 @@
                 return method(*args, **kwargs)
 
             return getPluginMethod
+
 ##METHODS_PART##
--- a/src/core/constants.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/src/core/constants.py	Sat Jan 24 01:00:29 2015 +0100
@@ -76,7 +76,8 @@
     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'
 
 
--- a/src/core/exceptions.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/src/core/exceptions.py	Sat Jan 24 01:00:29 2015 +0100
@@ -94,5 +94,9 @@
     pass
 
 
+class PermissionError(Exception):
+    pass
+
+
 class SkipHistory(Exception): # used in MessageReceivedTrigger to avoid history writting
     pass
--- a/src/core/sat_main.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/src/core/sat_main.py	Sat Jan 24 01:00:29 2015 +0100
@@ -90,7 +90,7 @@
         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 @@
                 # 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 @@
             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"""
--- a/src/memory/disco.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/src/memory/disco.py	Sat Jan 24 01:00:29 2015 +0100
@@ -102,9 +102,7 @@
             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 @@
             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 @@
             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)
--- a/src/memory/memory.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/src/memory/memory.py	Sat Jan 24 01:00:29 2015 +0100
@@ -19,11 +19,14 @@
 
 from sat.core.i18n import _
 
+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 sat.core.log import getLogger
-log = getLogger(__name__)
 from twisted.internet import defer, reactor
 from twisted.words.protocols.jabber import jid
 from sat.core import exceptions
@@ -36,6 +39,8 @@
 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 @@
         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 @@
         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 @@
                 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 @@
         @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 @@
         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 @@
         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 @@
             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.JID of the entity
+        @param show: show status
+        @param priority: priority
+        @param statuses: dictionary of statuses
+        @param profile_key: %(doc_profile_key)s
+        """
+        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
 
-        @param entity_jid (JID): the entity to check
+        @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: boolean
+        @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
+            raise exceptions.ProfileNotInCacheError
+        return profile_cache
 
-    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
+    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
         """
-        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)
+        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
 
-    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
+        @param with_bare: if True, include bare jids
+        @param profile_key: %(doc_profile_key)s
+        @return (list[unicode]): list of jids
         """
-        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
-
-    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
-        """
-        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)
+        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:
+            entity_data = profile_cache.setdefault(jid_.userhostJID(),{}).setdefault(jid_.resource, {})
 
-        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)
+            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 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)
+    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 @@
         @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])
+            try:
+                del profile_cache[entity_jid]
+            except KeyError:
+                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))
 
-        for entity in to_delete:
-            try:
-                del self._entities_cache[profile][entity]
-            except KeyError:
-                log.warning(_("Can't delete entity [{}]: not in cache").format(entity.full()))
+    ## Encryption ##
 
     def encryptValue(self, value, profile):
         """Encrypt a value for the given profile. The personal key must be loaded
@@ -644,6 +753,8 @@
         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 @@
 
         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 @@
 
     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
--- a/src/memory/params.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/src/memory/params.py	Sat Jan 24 01:00:29 2015 +0100
@@ -49,14 +49,14 @@
             <param name="%(show_empty_groups)s" label="%(show_empty_groups_label)s" value="true" type="bool" security="0" />
         </category>
         <category name="Connection" label="%(category_connection)s">
-            <param name="JabberID" value="name@example.org/SàT" type="string" />
+            <param name="JabberID" value="name@example.org/SàT" type="string" security="0" />
             <param name="Password" value="" type="password" />
             <param name="Priority" value="50" type="int" constraint="-128;127"/>
             <param name="%(force_server_param)s" value="" type="string" />
             <param name="%(force_port_param)s" value="" type="int" constraint="1;65535" />
             <param name="NewAccount" label="%(new_account_label)s" type="button" callback_id="registerNewAccount"/>
-            <param name="autoconnect" label="%(autoconnect_label)s" value="true" type="bool" />
-            <param name="autodisconnect" label="%(autodisconnect_label)s" value="false"  type="bool" />
+            <param name="autoconnect" label="%(autoconnect_label)s" value="true" type="bool" security="0" />
+            <param name="autodisconnect" label="%(autodisconnect_label)s" value="false"  type="bool" security="0" />
         </category>
         <category name="Misc" label="%(category_misc)s">
             <param name="Watched" value="test@Jabber.goffi.int" type="string" />
@@ -473,12 +473,12 @@
         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)
--- a/src/plugins/plugin_misc_text_commands.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/src/plugins/plugin_misc_text_commands.py	Sat Jan 24 01:00:29 2015 +0100
@@ -209,7 +209,7 @@
                 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}]
 
--- a/src/plugins/plugin_sec_otr.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/src/plugins/plugin_sec_otr.py	Sat Jan 24 01:00:29 2015 +0100
@@ -29,8 +29,9 @@
 from twisted.words.protocols.jabber import jid
 from twisted.python import failure
 from twisted.internet import defer
+from sat.memory import persistent
 import potr
-from sat.memory import persistent
+import copy
 
 NS_OTR = "otr_plugin"
 PRIVATE_KEY = "PRIVATE KEY"
@@ -269,7 +270,7 @@
         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 @@
         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 @@
         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 @@
         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 @@
     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 @@
         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
--- a/src/plugins/plugin_xep_0054.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/src/plugins/plugin_xep_0054.py	Sat Jan 24 01:00:29 2015 +0100
@@ -85,6 +85,7 @@
         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)
--- a/src/plugins/plugin_xep_0060.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/src/plugins/plugin_xep_0060.py	Sat Jan 24 01:00:29 2015 +0100
@@ -55,7 +55,7 @@
     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 @@
         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 @@
         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 @@
 
     def deleteReceived(self, event):
         #TODO: manage delete event
-        log.debug(_("Publish node deleted"))
+        log.debug(_(u"Publish node deleted"))
 
     # def purgeReceived(self, event):
 
--- a/src/plugins/plugin_xep_0085.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/src/plugins/plugin_xep_0085.py	Sat Jan 24 01:00:29 2015 +0100
@@ -39,6 +39,7 @@
 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 @@
         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 @@
             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 @@
         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 @@
         """
         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 @@
         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 @@
         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 @@
         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':