changeset 943:71926ec2114d

core (memory): entities cache improvments: - entities cache is no more limited to bare jid - resources are now automatically updated in bare jid cache
author Goffi <goffi@goffi.org>
date Fri, 28 Mar 2014 18:07:17 +0100
parents 598fc223cf59
children e1842ebcb2f3
files frontends/src/bridge/DBus.py frontends/src/quick_frontend/quick_app.py src/bridge/DBus.py src/bridge/bridge_constructor/bridge_template.ini src/core/constants.py src/core/sat_main.py src/memory/memory.py
diffstat 7 files changed, 123 insertions(+), 75 deletions(-) [+]
line wrap: on
line diff
--- a/frontends/src/bridge/DBus.py	Fri Mar 28 18:07:13 2014 +0100
+++ b/frontends/src/bridge/DBus.py	Fri Mar 28 18:07:17 2014 +0100
@@ -161,8 +161,8 @@
     def getParamsUI(self, security_limit=-1, app='', profile_key="@DEFAULT@", callback=None, errback=None):
         return unicode(self.db_core_iface.getParamsUI(security_limit, app, profile_key, reply_handler=callback, error_handler=lambda err:errback(err._dbus_error_name[len(const_ERROR_PREFIX)+1:])))
 
-    def getPresenceStatus(self, profile_key="@DEFAULT@"):
-        return self.db_core_iface.getPresenceStatus(profile_key)
+    def getPresenceStatuses(self, profile_key="@DEFAULT@"):
+        return self.db_core_iface.getPresenceStatuses(profile_key)
 
     def getProfileName(self, profile_key="@DEFAULT@"):
         return unicode(self.db_core_iface.getProfileName(profile_key))
--- a/frontends/src/quick_frontend/quick_app.py	Fri Mar 28 18:07:13 2014 +0100
+++ b/frontends/src/quick_frontend/quick_app.py	Fri Mar 28 18:07:17 2014 +0100
@@ -171,7 +171,7 @@
             for contact in self.bridge.getContacts(profile):
                 self.newContact(*contact, profile=profile)
 
-            presences = self.bridge.getPresenceStatus(profile)
+            presences = self.bridge.getPresenceStatuses(profile)
             for contact in presences:
                 for res in presences[contact]:
                     jabber_id = contact + ('/' + res if res else '')
@@ -560,7 +560,7 @@
             self.showDialog(_("The contact %s has refused your subscription") % entity.bare, _('Subscription refusal'), 'error')
         elif type == "subscribe":
             # this is a subscriptionn request, we have to ask for user confirmation
-            answer = self.showDialog(_("The contact %s wants to subscribe to your presence.\nDo you accept ?") % entity.bare, _('Subscription confirmation'), 'yes/no', answer_cb=self._subscribe_cb, answer_data=(entity, profile))
+            self.showDialog(_("The contact %s wants to subscribe to your presence.\nDo you accept ?") % entity.bare, _('Subscription confirmation'), 'yes/no', answer_cb=self._subscribe_cb, answer_data=(entity, profile))
 
     def showDialog(self, message, title, type="info", answer_cb=None):
         raise NotImplementedError
--- a/src/bridge/DBus.py	Fri Mar 28 18:07:13 2014 +0100
+++ b/src/bridge/DBus.py	Fri Mar 28 18:07:17 2014 +0100
@@ -320,8 +320,8 @@
     @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
                          in_signature='s', out_signature='a{sa{s(sia{ss})}}',
                          async_callbacks=None)
-    def getPresenceStatus(self, profile_key="@DEFAULT@"):
-        return self._callback("getPresenceStatus", unicode(profile_key))
+    def getPresenceStatuses(self, profile_key="@DEFAULT@"):
+        return self._callback("getPresenceStatuses", unicode(profile_key))
 
     @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
                          in_signature='s', out_signature='s',
--- a/src/bridge/bridge_constructor/bridge_template.ini	Fri Mar 28 18:07:13 2014 +0100
+++ b/src/bridge/bridge_constructor/bridge_template.ini	Fri Mar 28 18:07:17 2014 +0100
@@ -305,7 +305,7 @@
 doc_param_1=%(doc_profile_key)s
 doc_return=the last resource connected of the contact, or ""
 
-[getPresenceStatus]
+[getPresenceStatuses]
 type=method
 category=core
 sig_in=s
--- a/src/core/constants.py	Fri Mar 28 18:07:13 2014 +0100
+++ b/src/core/constants.py	Fri Mar 28 18:07:17 2014 +0100
@@ -40,6 +40,7 @@
     PROF_KEY_NONE = '@NONE@'
     PROF_KEY_DEFAULT = '@DEFAULT@'
     ENTITY_ALL = '@ALL@'
+    ENTITY_LAST_RESOURCE = 'last_resource'
 
 
     ## Configuration ##
--- a/src/core/sat_main.py	Fri Mar 28 18:07:13 2014 +0100
+++ b/src/core/sat_main.py	Fri Mar 28 18:07:17 2014 +0100
@@ -115,7 +115,7 @@
         self.bridge.register("getContacts", self.getContacts)
         self.bridge.register("getContactsFromGroup", self.getContactsFromGroup)
         self.bridge.register("getLastResource", self.memory._getLastResource)
-        self.bridge.register("getPresenceStatus", self.memory.getPresenceStatus)
+        self.bridge.register("getPresenceStatuses", self.memory._getPresenceStatuses)
         self.bridge.register("getWaitingSub", self.memory.getWaitingSub)
         self.bridge.register("getWaitingConf", self.getWaitingConf)
         self.bridge.register("sendMessage", self._sendMessage)
--- a/src/memory/memory.py	Fri Mar 28 18:07:13 2014 +0100
+++ b/src/memory/memory.py	Fri Mar 28 18:07:17 2014 +0100
@@ -112,8 +112,8 @@
         info(_("Memory manager init"))
         self.initialized = defer.Deferred()
         self.host = host
-        self.entitiesCache = {}  # XXX: keep presence/last resource/other data in cache
-                                 #     /!\ an entity is not necessarily in roster
+        self._entities_cache = {} # XXX: keep presence/last resource/other data in cache
+                                  #     /!\ an entity is not necessarily in roster
         self.subscriptions = {}
         self.server_features = {}  # used to store discovery's informations
         self.server_identities = {}
@@ -220,7 +220,7 @@
         """"Iniatialise session for a profile
         @param profile: %(doc_profile)s"""
         info(_("[%s] Profile session started" % profile))
-        self.entitiesCache[profile] = {}
+        self._entities_cache[profile] = {}
 
     def purgeProfileSession(self, profile):
         """Delete cache of data of profile
@@ -228,7 +228,7 @@
         info(_("[%s] Profile session purge" % profile))
         self.params.purgeProfile(profile)
         try:
-            del self.entitiesCache[profile]
+            del self._entities_cache[profile]
         except KeyError:
             error(_("Trying to purge roster status cache for a profile not in memory: [%s]") % profile)
 
@@ -369,80 +369,118 @@
 
     def _getLastResource(self, jid_s, profile_key):
         jid_ = jid.JID(jid_s)
-        return self.getLastResource(jid_, profile_key)
+        return self.getLastResource(jid_, profile_key) or ""
 
 
-    def getLastResource(self, jid_, profile_key):
-        """Return the last resource used by a jid_
-        @param jid_: bare jid
+    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"""
-        profile = self.getProfileName(profile_key)
-        if not profile or not self.host.isConnected(profile):
-            error(_('Asking jid_s for a non-existant or not connected profile'))
-            return ""
-        entity = jid_.userhost()
-        if not entity in self.entitiesCache[profile]:
-            info(_("Entity not in cache"))
-            return ""
+        data = self.getEntityData(entity_jid.userhostJID(), [C.ENTITY_LAST_RESOURCE], profile_key)
         try:
-            return self.entitiesCache[profile][entity]["last_resource"]
+            return data[C.ENTITY_LAST_RESOURCE]
         except KeyError:
-            return ""
+            return None
+
+    def _getPresenceStatuses(self, profile_key):
+        ret = self.getPresenceStatuses(profile_key)
+        return {entity.full():data for entity, data in ret.iteritems()}
 
-    def getPresenceStatus(self, profile_key):
+    def getPresenceStatuses(self, profile_key):
+        """Get all the presence status 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:
-            error(_('Asking contacts for a non-existant profile'))
-            return {}
+            raise exceptions.ProfileUnknownError(_('Trying to get entity data for a non-existant profile'))
         entities_presence = {}
-        for entity in self.entitiesCache[profile]:
-            if "presence" in self.entitiesCache[profile][entity]:
-                entities_presence[entity] = self.entitiesCache[profile][entity]["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"]
 
         debug("Memory getPresenceStatus (%s)", entities_presence)
         return entities_presence
 
     def setPresenceStatus(self, entity_jid, show, priority, statuses, profile_key):
-        """Change the presence status of an entity"""
-        profile = self.getProfileName(profile_key)
-        if not profile:
-            error(_('Trying to add presence status to a non-existant profile'))
-            return
-        entity_data = self.entitiesCache[profile].setdefault(entity_jid.userhost(), {})
-        resource = jid.parse(entity_jid.full())[2] or ''
-        if resource:
-            entity_data["last_resource"] = resource
-        if not "last_resource" in entity_data:
-            entity_data["last_resource"] = ''
-
-        entity_data.setdefault("presence", {})[resource] = (show, priority, statuses)
-
-    def updateEntityData(self, entity_jid, key, value, profile_key):
-        """Set a misc data for an entity
-        @param entity_jid: JID of the entity, or '@ALL@' to update all entities)
-        @param key: key to set (eg: "type")
-        @param value: value for this key (eg: "chatroom"), or C.PROF_KEY_NONE to delete
+        """Change the presence status of an entity
+        @param entity_jid: jid.JID of the entity
+        @param show: show status
+        @param priotity: priotity
+        @param statuses: dictionary of statuses
         @param profile_key: %(doc_profile_key)s
         """
         profile = self.getProfileName(profile_key)
         if not profile:
             raise exceptions.ProfileUnknownError(_('Trying to get entity data for a non-existant profile'))
-        if not profile in self.entitiesCache:
+        entity_data = self._getEntitiesData(entity_jid, profile)[entity_jid]
+        resource = entity_jid.resource
+        if resource:
+            entity_data[C.ENTITY_LAST_RESOURCE] = resource
+        entity_data.setdefault("presence", {})[resource or ''] = (show, priority, statuses)
+
+    def _getEntitiesData(self, entity_jid, profile):
+        """Get data dictionary for entities
+        @param entity_jid: JID of the entity, or C.ENTITY_ALL for all entities)
+        @param profile: %(doc_profile)s
+        @return: entities_data (key=jid, value=entity_data)
+        @raise: exceptions.ProfileNotInCacheError if profile is not in cache
+        """
+        if not profile in self._entities_cache:
             raise exceptions.ProfileNotInCacheError
-        if entity_jid == "@ALL@":
-            entities_map = self.entitiesCache[profile]
+        if entity_jid == C.ENTITY_ALL:
+            entities_data = self._entities_cache[profile]
         else:
-            entity = entity_jid.userhost()
-            self.entitiesCache[profile].setdefault(entity, {})
-            entities_map = {entity: self.entitiesCache[profile][entity]}
-        for entity in entities_map:
-            entity_map = entities_map[entity]
-            if value == C.PROF_KEY_NONE and key in entity_map:
-                del entity_map[key]
+            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)
+
+    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)
+        @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.resource and entity_jid != C.ENTITY_ALL:
+            self._updateEntityResources(entity_jid, profile)
+
+        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_map[key] = value
+                entity_data[key] = value
             if isinstance(value, basestring):
-                self.host.bridge.entityDataUpdated(entity, key, value, profile)
+                self.host.bridge.entityDataUpdated(jid_.full(), key, value, profile)
+
+    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)
+        @param key: key to delete (eg: "type")
+        @param profile_key: %(doc_profile_key)s
+        """
+        entities_data = self._getEntitiesData(entity_jid, profile_key)
+        for entity_jid in entities_data:
+            entity_data = entities_data[entity_jid]
+            try:
+                del entity_data[key]
+            except KeyError:
+                debug("Key [%s] doesn't exist for [%s] in entities_cache" % (key, entity_jid.full()))
 
     def getEntityData(self, entity_jid, keys_list, profile_key):
         """Get a list of cached values for entity
@@ -453,16 +491,11 @@
                  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
-                exceptions.ProfileNotInCacheError if profile 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'))
-        if not profile in self.entitiesCache:
-            raise exceptions.ProfileNotInCacheError
-        if not entity_jid.userhost() in self.entitiesCache[profile]:
-            raise exceptions.UnknownEntityError(entity_jid.userhost())
-        entity_data = self.entitiesCache[profile][entity_jid.userhost()]
+        entity_data = self._getEntitiesData(entity_jid, profile)[entity_jid]
         if not keys_list:
             return entity_data
         ret = {}
@@ -471,15 +504,29 @@
                 ret[key] = entity_data[key]
         return ret
 
-    def delEntityCache(self, entity_jid, profile_key):
+    def delEntityCache(self, entity_jid, delete_all_resources=True, profile_key=C.PROF_KEY_NONE):
         """Remove cached data for entity
-        @param entity_jid: JID of the entity
+        @param entity_jid: JID of the entity to delete
+        @param delete_all_resources: if True also delete all known resources form cache
+        @param profile_key: %(doc_profile_key)s
         """
         profile = self.getProfileName(profile_key)
-        try:
-            del self.entitiesCache[profile][entity_jid.userhost()]
-        except KeyError:
-            pass
+        if not profile:
+            raise exceptions.ProfileUnknownError(_('Trying to get entity data for a non-existant profile'))
+        to_delete = set([entity_jid])
+
+        if delete_all_resources:
+            if entity_jid.resource:
+                raise ValueError(_("Need a bare jid to delete all resources"))
+            entity_data = self._getEntitiesData(entity_jid, profile)[entity_jid]
+            resources = entity_data.setdefault('resources', set())
+            to_delete.update([jid.JID("%s/%s" % (entity_jid.userhost(), resource)) for resource in resources])
+
+        for entity in to_delete:
+            try:
+                del self._entities_cache[profile][entity]
+            except KeyError:
+                debug("Can't delete entity [%s]: not in cache" % entity.full())
 
     def addWaitingSub(self, type_, entity_jid, profile_key):
         """Called when a subcription request is received"""