# HG changeset patch # User Goffi # Date 1396026437 -3600 # Node ID 71926ec2114d3f3626fc6beaed627de3d9cb7f3a # Parent 598fc223cf593e4ea83118399045434f16addbbd core (memory): entities cache improvments: - entities cache is no more limited to bare jid - resources are now automatically updated in bare jid cache diff -r 598fc223cf59 -r 71926ec2114d frontends/src/bridge/DBus.py --- 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)) diff -r 598fc223cf59 -r 71926ec2114d frontends/src/quick_frontend/quick_app.py --- 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 diff -r 598fc223cf59 -r 71926ec2114d src/bridge/DBus.py --- 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', diff -r 598fc223cf59 -r 71926ec2114d src/bridge/bridge_constructor/bridge_template.ini --- 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 diff -r 598fc223cf59 -r 71926ec2114d src/core/constants.py --- 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 ## diff -r 598fc223cf59 -r 71926ec2114d src/core/sat_main.py --- 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) diff -r 598fc223cf59 -r 71926ec2114d src/memory/memory.py --- 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"""