changeset 1320:3012c2f15dae frontends_multi_profiles

merges souliane commits
author Goffi <goffi@goffi.org>
date Mon, 09 Feb 2015 21:40:45 +0100
parents 781ee3539252 (diff) 9e904f8a094e (current diff)
children e9888db0eb0c
files
diffstat 15 files changed, 235 insertions(+), 60 deletions(-) [+]
line wrap: on
line diff
--- a/frontends/src/bridge/DBus.py	Mon Feb 09 09:19:30 2015 +0100
+++ b/frontends/src/bridge/DBus.py	Mon Feb 09 21:40:45 2015 +0100
@@ -265,6 +265,20 @@
             kwargs['error_handler'] = error_handler
         return self.db_core_iface.getContactsFromGroup(group, profile_key, **kwargs)
 
+    def getEntitiesData(self, jids, 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.getEntitiesData(jids, keys, profile, **kwargs)
+
     def getEntityData(self, jid, keys, profile, callback=None, errback=None):
         if callback is None:
             error_handler = None
--- a/frontends/src/quick_frontend/constants.py	Mon Feb 09 09:19:30 2015 +0100
+++ b/frontends/src/quick_frontend/constants.py	Mon Feb 09 21:40:45 2015 +0100
@@ -30,3 +30,5 @@
     CONTACT_SPECIAL_GROUP = 'group' # group chat special entity
     CONTACT_SPECIAL_ALLOWED = (CONTACT_SPECIAL_GROUP,) # set of allowed values for special flag
     CONTACT_DATA_FORBIDDEN = {CONTACT_GROUPS, CONTACT_RESOURCES, CONTACT_MAIN_RESOURCE} # set of forbidden names for contact data
+
+    LISTENERS = {'avatar'}
--- a/frontends/src/quick_frontend/quick_app.py	Mon Feb 09 09:19:30 2015 +0100
+++ b/frontends/src/quick_frontend/quick_app.py	Mon Feb 09 21:40:45 2015 +0100
@@ -33,6 +33,7 @@
     """Class managing all data relative to one profile, and plugging in mechanism"""
     host = None
     bridge = None
+    cache_keys_to_get = ['avatar']
 
     def __init__(self, profile):
         self.profile = profile
@@ -65,9 +66,21 @@
 
     def _plug_profile_afterconnect(self):
         # Profile can be connected or not
+        # we get cached data
+        self.host.bridge.getEntitiesData([], ProfileManager.cache_keys_to_get, profile=self.profile, callback=self._plug_profile_gotCachedValues, errback=self._plug_profile_failedCachedValues)
+
+    def _plug_profile_failedCachedValues(self, failure):
+        log.error("Couldn't get cached values: {}".format(failure))
+        self._plug_profile_gotCachedValues({})
+
+    def _plug_profile_gotCachedValues(self, cached_values):
         # TODO: watched plugin
         contact_list = self.host.addContactList(self.profile)
 
+        for entity, data in cached_values.iteritems():
+            for key, value in data.iteritems():
+                self.host.contact_lists[self.profile].setCache(jid.JID(entity), key, value)
+
         if not self.bridge.isConnected(self.profile):
             self.host.setStatusOnline(False, profile=self.profile)
         else:
@@ -182,6 +195,9 @@
         # widgets
         self.selected_widget = None # widget currently selected (must be filled by frontend)
 
+        # listeners
+        self._listeners = {} # key: listerner type ("avatar", "selected", etc), value: list of callbacks
+
         ## bridge ##
         try:
             self.bridge = create_bridge()
@@ -271,6 +287,42 @@
             handler(*args, **kwargs)
         self.bridge.register(functionName, signalReceived, iface)
 
+    def addListerner(self, type_, callback):
+        """Add a listerner for an event
+
+        /!\ don't forget to remove listener when not used anymore (e.g. if you delete a widget)
+        @param type_: type of event, can be:
+            - avatar: called when avatar data is updated
+                      args: (entity, avatar file, profile)
+        @param callback: method to call on event
+        """
+        assert type_ in C.LISTENERS
+        self._listeners.setdefault(type_, []).append(callback)
+
+    def removeListener(self, type_, callback):
+        """Remove a callback from listeners
+
+        @param type_: same as for [addListerner]
+        @param callback: callback to remove
+        """
+        assert type_ in C.LISTENERS
+        self._listeners[type_].remove(callback)
+
+    def callListeners(self, type_, *args):
+        """Call all methods which listen of type_ event
+
+        @param type_: same as for [addListerner]
+        @param *args: arguments sent to callback
+        """
+        assert type_ in C.LISTENERS
+        try:
+            listeners = self._listeners[type_]
+        except KeyError:
+            pass
+        else:
+            for listener in listeners:
+                listener(*args)
+
     def check_profile(self, profile):
         """Tell if the profile is currently followed by the application"""
         return profile in self.profiles
@@ -598,6 +650,7 @@
             if entity in self.contact_lists[profile]:
                 def gotFilename(filename):
                     self.contact_lists[profile].setCache(entity, 'avatar', filename)
+                    self.callListeners('avatar', entity, filename, profile)
                 self.bridge.getAvatarFile(value, callback=gotFilename)
 
     def askConfirmationHandler(self, confirm_id, confirm_type, data, profile):
--- a/src/bridge/DBus.py	Mon Feb 09 09:19:30 2015 +0100
+++ b/src/bridge/DBus.py	Mon Feb 09 21:40:45 2015 +0100
@@ -271,6 +271,12 @@
         return self._callback("getContactsFromGroup", unicode(group), unicode(profile_key))
 
     @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
+                         in_signature='asass', out_signature='a{sa{ss}}',
+                         async_callbacks=None)
+    def getEntitiesData(self, jids, keys, profile):
+        return self._callback("getEntitiesData", jids, keys, unicode(profile))
+
+    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
                          in_signature='sass', out_signature='a{ss}',
                          async_callbacks=None)
     def getEntityData(self, jid, keys, profile):
--- a/src/bridge/bridge_constructor/bridge_template.ini	Mon Feb 09 09:19:30 2015 +0100
+++ b/src/bridge/bridge_constructor/bridge_template.ini	Mon Feb 09 21:40:45 2015 +0100
@@ -184,12 +184,24 @@
 category=core
 sig_in=sass
 sig_out=a{ss}
-doc=Get data for an entity
+doc=Get data in cache for an entity
 doc_param_0=jid: entity's bare jid
 doc_param_1=keys: list of keys to get
 doc_param_2=%(doc_profile)s
 doc_return=dictionary of asked key,
- if key doesn't exist, the resulting dictionary will neither have the key
+ if key doesn't exist, the resulting dictionary will not have the key
+
+[getEntitiesData]
+type=method
+category=core
+sig_in=asass
+sig_out=a{sa{ss}}
+doc=Get data in cache for several entities at once
+doc_param_0=jids: list of entities bare jid, or empty list to have all jids in cache
+doc_param_1=keys: list of keys to get
+doc_param_2=%(doc_profile)s
+doc_return=dictionary with jids as keys and dictionary of asked key as values
+ if key doesn't exist for a jid, the resulting dictionary will not have it
 
 [asyncCreateProfile]
 async=
--- a/src/core/sat_main.py	Mon Feb 09 09:19:30 2015 +0100
+++ b/src/core/sat_main.py	Mon Feb 09 21:40:45 2015 +0100
@@ -83,7 +83,8 @@
         self.bridge.register("getVersion", lambda: C.APP_VERSION)
         self.bridge.register("getProfileName", self.memory.getProfileName)
         self.bridge.register("getProfilesList", self.memory.getProfilesList)
-        self.bridge.register("getEntityData", lambda _jid, keys, profile: self.memory.getEntityData(jid.JID(_jid), keys, profile))
+        self.bridge.register("getEntityData", lambda jid_, keys, profile: self.memory.getEntityData(jid.JID(jid_), keys, profile))
+        self.bridge.register("getEntitiesData", self.memory._getEntitiesData)
         self.bridge.register("asyncCreateProfile", self.memory.asyncCreateProfile)
         self.bridge.register("asyncDeleteProfile", self.memory.asyncDeleteProfile)
         self.bridge.register("asyncConnect", self.asyncConnect)
@@ -264,9 +265,13 @@
         for plugin in self.plugins.iteritems():
             if plugin[1].is_handler:
                 plugin[1].getHandler(profile).setHandlerParent(current)
-            connected_cb = getattr(plugin[1], "profileConnected", None)
+            connected_cb = getattr(plugin[1], "profileConnected", None) # profile connected is called after client is ready and roster is got
             if connected_cb:
                 plugin_conn_cb.append((plugin[0], connected_cb))
+            try:
+                yield plugin[1].profileConnecting(profile) # profile connecting is called before actually starting client
+            except AttributeError:
+                pass
 
         current.startService()
 
@@ -289,7 +294,7 @@
                                   {'name': plugin_conn_cb[idx][0], 'failure': result})
 
         yield list_d.addCallback(logPluginResults) # FIXME: we should have a timeout here, and a way to know if a plugin freeze
-        # TODO: mesure time to launch of each plugin
+        # TODO: mesure launch time of each plugin
 
     def _authenticateProfile(self, password, profile):
         """Authenticate the profile.
--- a/src/core/xmpp.py	Mon Feb 09 09:19:30 2015 +0100
+++ b/src/core/xmpp.py	Mon Feb 09 21:40:45 2015 +0100
@@ -319,10 +319,6 @@
                                            int(priority), statuses,
                                            self.parent.profile)
 
-        # uncomment these two lines if you need the trigger
-        #if not self.host.trigger.point("presenceReceived", entity, "unavailable", 0, statuses, self.parent.profile):
-        #    return
-
         # now it's time to notify frontends
         self.host.bridge.presenceUpdate(entity.full(), show or "",
                                         int(priority), statuses,
--- a/src/memory/disco.py	Mon Feb 09 09:19:30 2015 +0100
+++ b/src/memory/disco.py	Mon Feb 09 21:40:45 2015 +0100
@@ -107,7 +107,7 @@
             def infosCb(disco_infos):
                 cap_hash = self.generateHash(disco_infos)
                 self.hashes[cap_hash] = disco_infos
-                self.host.memory.updateEntityData(jid_, C.ENTITY_CAP_HASH, cap_hash, client.profile)
+                self.host.memory.updateEntityData(jid_, C.ENTITY_CAP_HASH, cap_hash, profile_key=client.profile)
                 return disco_infos
             d = client.disco.requestInfo(jid_)
             d.addCallback(infosCb)
@@ -135,7 +135,7 @@
             except (KeyError, exceptions.UnknownEntityError):
                 log.debug("Caching [%s] disco items" % jid_.full())
                 items = yield client.disco.requestItems(jid_, nodeIdentifier)
-                self.host.memory.updateEntityData(jid_, "DISCO_ITEMS", items, client.profile)
+                self.host.memory.updateEntityData(jid_, "DISCO_ITEMS", items, profile_key=client.profile)
         else:
             items = yield client.disco.requestItems(jid_, nodeIdentifier)
 
--- a/src/memory/memory.py	Mon Feb 09 09:19:30 2015 +0100
+++ b/src/memory/memory.py	Mon Feb 09 21:40:45 2015 +0100
@@ -444,7 +444,7 @@
         @param profile_key: %(doc_profile_key)s
         """
         presence_data = PresenceTuple(show, priority, statuses)
-        self.updateEntityData(entity_jid, "presence", presence_data, profile_key)
+        self.updateEntityData(entity_jid, "presence", presence_data, profile_key=profile_key)
         if entity_jid.resource and show != C.PRESENCE_UNAVAILABLE:
             # If a resource is available, bare jid should not have presence information
             try:
@@ -580,7 +580,7 @@
                 full_jid.resource = resource
                 yield full_jid
 
-    def updateEntityData(self, entity_jid, key, value, profile_key):
+    def updateEntityData(self, entity_jid, key, value, silent=False, profile_key=C.PROF_KEY_NONE):
         """Set a misc data for an entity
 
         If key was registered with setSignalOnUpdate, a signal will be sent to frontends
@@ -588,6 +588,7 @@
                            C.ENTITY_ALL for all entities (all resources + bare jids)
         @param key: key to set (eg: "type")
         @param value: value for this key (eg: "chatroom")
+        @param silent(bool): if True, doesn't send signal to frontend, even there is a signal flag (see setSignalOnUpdate)
         @param profile_key: %(doc_profile_key)s
         """
         profile_cache = self._getProfileCache(profile_key)
@@ -600,7 +601,7 @@
             entity_data = profile_cache.setdefault(jid_.userhostJID(),{}).setdefault(jid_.resource, {})
 
             entity_data[key] = value
-            if key in self._key_signals:
+            if key in self._key_signals and not silent:
                 if not isinstance(value, basestring):
                     log.error(u"Setting a non string value ({}) for a key ({}) which has a signal flag".format(value, key))
                 else:
@@ -636,6 +637,54 @@
                 else:
                     raise e
 
+    def _getEntitiesData(self, entities_jids, keys_list, profile_key):
+        ret = self.getEntitiesData([jid.JID(jid_) for jid_ in entities_jids], keys_list, profile_key)
+        return {jid_.full(): data for jid_, data in ret.iteritems()}
+
+    def getEntitiesData(self, entities_jids, keys_list=None, profile_key=C.PROF_KEY_NONE):
+        """Get a list of cached values for several entities at once
+
+        @param entities_jids: jids of the entities, or empty list for all entities in cache
+        @param keys_list (iterable,None): list of keys to get, None for everything
+        @param profile_key: %(doc_profile_key)s
+        @return: dict withs values for each key in keys_list.
+                 if there is no value of a given key, resulting dict will
+                 have nothing with that key nether
+                 if an entity doesn't exist in cache, it will not appear
+                 in resulting dict
+
+        @raise exceptions.UnknownEntityError: if entity is not in cache
+        """
+        def fillEntityData(entity_cache_data):
+            entity_data = {}
+            if keys_list is None:
+                entity_data = entity_cache_data
+            else:
+                for key in keys_list:
+                    try:
+                        entity_data[key] = entity_cache_data[key]
+                    except KeyError:
+                        continue
+            return entity_data
+
+        profile_cache = self._getProfileCache(profile_key)
+        ret_data = {}
+        if entities_jids:
+            for entity in entities_jids:
+                try:
+                    entity_cache_data = profile_cache[entity.userhostJID()][entity.resource]
+                except KeyError:
+                    continue
+                ret_data[entity.full()] = fillEntityData(entity_cache_data, keys_list)
+        else:
+            for bare_jid, data in profile_cache.iteritems():
+                for resource, entity_cache_data in data.iteritems():
+                    full_jid = copy.copy(bare_jid)
+                    full_jid.resource = resource
+                    ret_data[full_jid] = fillEntityData(entity_cache_data)
+
+        return ret_data
+
     def getEntityData(self, entity_jid, keys_list=None, profile_key=C.PROF_KEY_NONE):
         """Get a list of cached values for entity
 
--- a/src/plugins/plugin_xep_0045.py	Mon Feb 09 09:19:30 2015 +0100
+++ b/src/plugins/plugin_xep_0045.py	Mon Feb 09 21:40:45 2015 +0100
@@ -696,7 +696,7 @@
             self.userLeftRoom(room, user)
 
     def userJoinedRoom(self, room, user):
-        self.host.memory.updateEntityData(room.roomJID, "type", "chatroom", self.parent.profile)
+        self.host.memory.updateEntityData(room.roomJID, "type", "chatroom", profile_key=self.parent.profile)
         if user.nick in self.__changing_nicks:
             self.__changing_nicks.remove(user.nick)
         else:
--- a/src/plugins/plugin_xep_0054.py	Mon Feb 09 09:19:30 2015 +0100
+++ b/src/plugins/plugin_xep_0054.py	Mon Feb 09 21:40:45 2015 +0100
@@ -79,9 +79,8 @@
         self.avatar_path = os.path.join(self.host.memory.getConfig('', 'local_dir'), AVATAR_PATH)
         if not os.path.exists(self.avatar_path):
             os.makedirs(self.avatar_path)
-        self.avatars_cache = PersistentDict(NS_VCARD)
-        self.initialised = self.avatars_cache.load()  # FIXME: resulting deferred must be correctly managed
-        host.bridge.addMethod("getCard", ".plugin", in_sign='ss', out_sign='s', method=self.getCard)
+        self.avatars_cache = {}
+        host.bridge.addMethod("getCard", ".plugin", in_sign='ss', out_sign='s', method=self._getCard)
         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)
@@ -91,9 +90,9 @@
         return XEP_0054_handler(self)
 
     def presenceTrigger(self, presence_elt, client):
-        if client.jid.userhost() in self.avatars_cache:
+        if client.jid.userhost() in self.avatars_cache[client.profile]:
             x_elt = domish.Element((NS_VCARD_UPDATE, 'x'))
-            x_elt.addElement('photo', content=self.avatars_cache[client.jid.userhost()])
+            x_elt.addElement('photo', content=self.avatars_cache[client.profile][client.jid.userhost()])
             presence_elt.addChild(x_elt)
 
         return True
@@ -103,28 +102,33 @@
         #       the current naive approach keeps a map between all jids of all profiles
         #       in persistent cache, then put avatar
         #       hashs in memory. Hashed should be shared between profiles
-        for jid_s, avatar_hash in self.avatars_cache.iteritems():
+        for jid_s, avatar_hash in self.avatars_cache[profile].iteritems():
             jid_ = jid.JID(jid_s)
-            self.host.memory.updateEntityData(jid_, "avatar", avatar_hash, profile)
+            self.host.memory.updateEntityData(jid_, "avatar", avatar_hash, silent=True, profile_key=profile)
 
     @defer.inlineCallbacks
-    def profileConnected(self, profile):
-        yield self.initialised
+    def profileConnecting(self, profile):
+        self.avatars_cache[profile] = PersistentDict(NS_VCARD, profile)
+        yield self.avatars_cache[profile].load()
         self._fillCachedValues(profile)
 
+    def profileDisconnected(self, profile):
+        log.debug(u"Deleting profile cache for avatars")
+        del self.avatars_cache[profile]
+
     def updateCache(self, jid_, name, value, profile):
         """update cache value
 
         save value in memory in case of change
-        @param jid_: jid of the owner of the vcard
-        @param name: name of the item which changed
-        @param value: new value of the item
-        @param profile: profile which received the update
+        @param jid_(jid.JID): jid of the owner of the vcard
+        @param name(str): name of the item which changed
+        @param value(unicode): new value of the item
+        @param profile(unicode): profile which received the update
         """
         assert not jid_.resource # VCard are retrieved with bare jid
-        self.host.memory.updateEntityData(jid_, name, value, profile)
+        self.host.memory.updateEntityData(jid_, name, value, profile_key=profile)
         if name == "avatar":
-            self.avatars_cache[jid_.userhost()] = value
+            self.avatars_cache[profile][jid_.userhost()] = value
 
     def getCache(self, entity_jid, name, profile):
         """return cached value for jid
@@ -140,6 +144,28 @@
             return None
         return data.get(name)
 
+    def _getFilename(self, hash_):
+        """Get filename from hash
+
+        @param hash_: hash of the avatar
+        @return (str): absolute filename of the avatar
+        """
+        return os.path.join(self.avatar_path, hash_)
+
+    def saveAvatarFile(self, data, hash_):
+        """Save the avatar picture if it doesn't already exists
+
+        @param data(str): binary image of the avatar
+        @param hash_(str): hash of the binary data (will be used for the filename)
+        """
+        filename = self._getFilename(hash_)
+        if not os.path.exists(filename):
+            with open(filename, 'wb') as file_:
+                file_.write(data)
+            log.debug(_("file saved to %s") % hash_)
+        else:
+            log.debug(_("file [%s] already in cache") % hash_)
+
     def savePhoto(self, photo_xml):
         """Parse a <PHOTO> elem and save the picture"""
         for elem in photo_xml.elements():
@@ -149,13 +175,7 @@
                 log.debug(_('Decoding binary'))
                 decoded = b64decode(str(elem))
                 image_hash = sha1(decoded).hexdigest()
-                filename = self.avatar_path + '/' + image_hash
-                if not os.path.exists(filename):
-                    with open(filename, 'wb') as file_:
-                        file_.write(decoded)
-                    log.debug(_("file saved to %s") % image_hash)
-                else:
-                    log.debug(_("file [%s] already in cache") % image_hash)
+                self.saveAvatarFile(decoded, image_hash)
                 return image_hash
 
     @defer.inlineCallbacks
@@ -212,15 +232,20 @@
         except AttributeError:  # 'ConnectionLost' object has no attribute 'stanza'
             log.warning(_("Can't find VCard: %s") % failure.getErrorMessage())
 
-    def getCard(self, target_s, profile_key=C.PROF_KEY_NONE):
+    def _getCard(self, target_s, profile_key=C.PROF_KEY_NONE):
+        return self.getCard(jid.JID(target_s), profile_key)
+
+    def getCard(self, target, profile_key=C.PROF_KEY_NONE):
         """Ask server for VCard
-        @param target_s: jid from which we want the VCard
-        @result: id to retrieve the profile"""
+
+        @param target(jid.JID): jid from which we want the VCard
+        @result: id to retrieve the profile
+        """
         current_jid, xmlstream = self.host.getJidNStream(profile_key)
         if not xmlstream:
             raise exceptions.ProfileUnknownError('Asking vcard for a non-existant or not connected profile ({})'.format(profile_key))
         profile = self.host.memory.getProfileName(profile_key)
-        to_jid = jid.JID(target_s)
+        to_jid = target.userhostJID()
         log.debug(_("Asking for %s's VCard") % to_jid.userhost())
         reg_request = IQ(xmlstream, 'get')
         reg_request["from"] = current_jid.full()
@@ -267,6 +292,7 @@
         photo_elt.addElement('TYPE', content='image/png')
         photo_elt.addElement('BINVAL', content=b64encode(img_buf.getvalue()))
         img_hash = sha1(img_buf.getvalue()).hexdigest()
+        self.saveAvatarFile(img_buf.getvalue(), img_hash)
         return (vcard_set, img_hash)
 
     def setAvatar(self, filepath, profile_key=C.PROF_KEY_NONE):
@@ -307,8 +333,10 @@
         return []
 
     def update(self, presence):
-        """Request for VCard's nickname
-        return the cached nickname if exists, else get VCard
+        """Called on <presence/> stanza with vcard data
+
+        Check for avatar information, and get VCard if needed
+        @param presend(domish.Element): <presence/> stanza
         """
         # FIXME: doesn't manage MUC correctly
         from_jid = jid.JID(presence['from']).userhostJID()
@@ -316,10 +344,20 @@
         x_elem = filter(lambda x: x.name == "x", presence.elements())[0]  # We only want the "x" element
         for elem in x_elem.elements():
             if elem.name == 'photo':
-                _hash = str(elem)
+                hash_ = str(elem)
                 old_avatar = self.plugin_parent.getCache(from_jid, 'avatar', self.parent.profile)
-                if not old_avatar or old_avatar != _hash:
-                    log.debug(_('New avatar found, requesting vcard'))
-                    self.plugin_parent.getCard(from_jid.userhost(), self.parent.profile)
+                filename = self.plugin_parent._getFilename(hash_)
+                if not old_avatar or old_avatar != hash_:
+                    if os.path.exists(filename):
+                        log.debug(u"New avatar found for [{}], it's already in cache, we use it".format(from_jid.full()))
+                        self.plugin_parent.updateCache(from_jid, 'avatar', hash_, self.parent.profile)
+                    else:
+                        log.debug(u'New avatar found for [{}], requesting vcard'.format(from_jid.full()))
+                        self.plugin_parent.getCard(from_jid, self.parent.profile)
                 else:
-                    log.debug("avatar for {} already in cache".format(from_jid))
+                    if os.path.exists(filename):
+                        log.debug(u"avatar for {} already in cache".format(from_jid.full()))
+                    else:
+                        log.error(u"Avatar for [{}] should be in cache but it is not ! We get it".format(from_jid.full()))
+                        self.plugin_parent.getCard(from_jid, self.parent.profile)
+
--- a/src/plugins/plugin_xep_0085.py	Mon Feb 09 09:19:30 2015 +0100
+++ b/src/plugins/plugin_xep_0085.py	Mon Feb 09 21:40:45 2015 +0100
@@ -137,7 +137,7 @@
         if value == DELETE_VALUE:
             self.host.memory.delEntityData(entity_jid, ENTITY_KEY, profile)
         else:
-            self.host.memory.updateEntityData(entity_jid, ENTITY_KEY, value, profile)
+            self.host.memory.updateEntityData(entity_jid, ENTITY_KEY, value, profile_key=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)
@@ -151,7 +151,7 @@
         @param type_: parameter type
         """
         if (category, name) == (PARAM_KEY, PARAM_NAME):
-            self.updateEntityData(C.ENTITY_ALL, True if bool("true") else DELETE_VALUE, profile)
+            self.updateEntityData(C.ENTITY_ALL, True if bool("true") else DELETE_VALUE, profile_key=profile)
             return False
         return True
 
@@ -173,11 +173,11 @@
                 try:
                     domish.generateElementsNamed(message.elements(), name="active").next()
                     # contact enabled Chat State Notifications
-                    self.updateEntityData(from_jid, True, profile)
+                    self.updateEntityData(from_jid, True, profile_key=profile)
                 except StopIteration:
                     if message.getAttribute('type') == 'chat':
                         # contact didn't enable Chat State Notifications
-                        self.updateEntityData(from_jid, False, profile)
+                        self.updateEntityData(from_jid, False, profile_key=profile)
                         return True
             except StopIteration:
                 pass
@@ -260,7 +260,7 @@
         except (exceptions.UnknownEntityError, KeyError):
             if forceEntityData:
                 # enable it for the first time
-                self.updateEntityData(to_jid, True, profile)
+                self.updateEntityData(to_jid, True, profile_key=profile)
                 return True
         # wait for the first message before sending states
         return False
--- a/src/plugins/plugin_xep_0115.py	Mon Feb 09 09:19:30 2015 +0100
+++ b/src/plugins/plugin_xep_0115.py	Mon Feb 09 21:40:45 2015 +0100
@@ -107,7 +107,7 @@
             client.caps_sent = False
         if cap_hash not in self.host.memory.disco.hashes:
             self.host.memory.disco.hashes[cap_hash] = disco_infos
-            self.host.memory.updateEntityData(client.jid, C.ENTITY_CAP_HASH, cap_hash, profile)
+            self.host.memory.updateEntityData(client.jid, C.ENTITY_CAP_HASH, cap_hash, profile_key=profile)
 
 
 class XEP_0115_handler(XMPPHandler):
@@ -146,7 +146,7 @@
         if c_ver in self.host.memory.disco.hashes:
             # we already know the hash, we update the jid entity
             log.debug ("hash [%(hash)s] already in cache, updating entity [%(jid)s]" % {'hash': c_ver, 'jid': from_jid.full()})
-            self.host.memory.updateEntityData(from_jid, C.ENTITY_CAP_HASH, c_ver, self.profile)
+            self.host.memory.updateEntityData(from_jid, C.ENTITY_CAP_HASH, c_ver, profile_key=self.profile)
             return
 
         if c_hash != 'sha-1': # unknown hash method
--- a/src/stdui/ui_profile_manager.py	Mon Feb 09 09:19:30 2015 +0100
+++ b/src/stdui/ui_profile_manager.py	Mon Feb 09 21:40:45 2015 +0100
@@ -48,7 +48,7 @@
         def gotProfileCipher(profile_cipher):
             if self.host.memory.auth_sessions.profileGetUnique(profile):
                 # case 1: profile already authenticated
-                return {'validated': C.str(True)}
+                return {'validated': C.boolConst(True)}
             self.profile_ciphers[profile] = profile_cipher
             if 'profile_password' in data:
                 # case 2: password is provided by the caller
@@ -81,7 +81,7 @@
     def getParamError(self, failure):
         _dialog = xml_tools.XMLUI('popup', title=D_('Error'))
         _dialog.addText(D_("Can't get profile parameter."))
-        return {'xmlui': _dialog.toXml(), 'validated': C.str(False)}
+        return {'xmlui': _dialog.toXml(), 'validated': C.boolConst(False)}
 
     @defer.inlineCallbacks
     def _verifyPassword(self, data, profile):
@@ -101,10 +101,10 @@
         if not verified:
             _dialog = xml_tools.XMLUI('popup', title=D_('Connection error'))
             _dialog.addText(D_("The provided profile password doesn't match."))
-            defer.returnValue({'xmlui': _dialog.toXml(), 'validated': C.str(False)})
+            defer.returnValue({'xmlui': _dialog.toXml(), 'validated': C.boolConst(False)})
 
         yield self.host.memory.newAuthSession(profile_password, profile)
-        defer.returnValue({'validated': C.str(True)})
+        defer.returnValue({'validated': C.boolConst(True)})
 
     def _changeXMPPPassword(self, data, profile):
         session_data = self._sessions.profileGetUnique(profile)
--- a/src/test/helpers.py	Mon Feb 09 09:19:30 2015 +0100
+++ b/src/test/helpers.py	Mon Feb 09 21:40:45 2015 +0100
@@ -286,7 +286,7 @@
     def delWaitingSub(self, contact_jid, profile_key):
         pass
 
-    def updateEntityData(self, entity_jid, key, value, profile_key):
+    def updateEntityData(self, entity_jid, key, value, silent=False, profile_key="@NONE@"):
         self.entities_data.setdefault(entity_jid, {})
         self.entities_data[entity_jid][key] = value