changeset 466:448ce3c9e2ac

core: Roster cache refactoring: cache is now managed by client's SatRosterProtocol instance.
author Goffi <goffi@goffi.org>
date Mon, 26 Mar 2012 00:22:49 +0200
parents 78e67a59d51d
children 47af60767013
files src/core/sat_main.py src/core/xmpp.py src/memory/memory.py
diffstat 3 files changed, 73 insertions(+), 66 deletions(-) [+]
line wrap: on
line diff
--- a/src/core/sat_main.py	Sat Mar 24 17:48:12 2012 +0100
+++ b/src/core/sat_main.py	Mon Mar 26 00:22:49 2012 +0200
@@ -123,7 +123,7 @@
         self.bridge.register("connect", self.connect)
         self.bridge.register("asyncConnect", self.asyncConnect)
         self.bridge.register("disconnect", self.disconnect)
-        self.bridge.register("getContacts", self.memory.getContacts)
+        self.bridge.register("getContacts", self.getContacts)
         self.bridge.register("getLastResource", self.memory.getLastResource)
         self.bridge.register("getPresenceStatus", self.memory.getPresenceStatus)
         self.bridge.register("getWaitingSub", self.memory.getWaitingSub)
@@ -270,6 +270,18 @@
             if disconnected_cb:
                 disconnected_cb(profile)
 
+    def getContacts(self, profile_key):
+        client = self.getClient(profile_key)
+        if not client:
+            error(_('Asking contacts for a non-existant profile'))
+            return []
+        ret = []
+        for item in client.roster.getItems(): #we get all item for client's roster
+            #and convert them to expected format
+            attr = client.roster.getAttributes(item)
+            ret.append([item.jid.userhost(), attr, item.groups])
+        return ret
+
     def purgeClient(self, profile):
         """Remove reference to a profile client and purge cache
         the garbage collector can then free the memory"""
--- a/src/core/xmpp.py	Sat Mar 24 17:48:12 2012 +0100
+++ b/src/core/xmpp.py	Mon Mar 26 00:22:49 2012 +0200
@@ -120,7 +120,9 @@
     def __init__(self, host):
         xmppim.RosterClientProtocol.__init__(self)
         self.host = host
-        self._groups=set()
+        #XXX: the two following dicts keep a local copy of the roster
+        self._groups = {} #map from groups to bare jids: key=group value=set of bare jids
+        self._jids = {} #map from bare jids to RosterItem: key=jid value=RosterItem
     
     def rosterCb(self, roster):
         for raw_jid, item in roster.iteritems():
@@ -157,6 +159,18 @@
         for group in roster_item.groups:
             item.addElement('group', content=group)
         return iq.send()
+
+    def getAttributes(self, item):
+        """Return dictionary of attributes as used in bridge from a RosterItem
+        @param item: RosterItem
+        @return: dictionary of attributes"""
+        item_attr = {'to': str(item.subscriptionTo),
+                     'from': str(item.subscriptionFrom),
+                     'ask': str(item.ask)
+                     }
+        if item.name:
+            item_attr['name'] = item.name
+        return item_attr
     
     def onRosterSet(self, item):
         """Called when a new/update roster item is received"""
@@ -167,26 +181,53 @@
             #may change in the future
             self.removeItem(item.jid)
             return
-        item_attr = {'to': str(item.subscriptionTo),
-                     'from': str(item.subscriptionFrom),
-                     'ask': str(item.ask)
-                     }
-        if item.name:
-            item_attr['name'] = item.name
         info (_("new contact in roster list: %s"), item.jid.full())
-        self.host.memory.addContact(item.jid, item_attr, item.groups, self.parent.profile)
-        self.host.bridge.newContact(item.jid.full(), item_attr, item.groups, self.parent.profile)
-        self._groups.update(item.groups) 
+        #self.host.memory.addContact(item.jid, item_attr, item.groups, self.parent.profile)
+       
+        bare_jid = item.jid.userhost()
+        self._jids[bare_jid] = item
+        for group in item.groups:
+            self._groups.get(group,set()).add(bare_jid)
+        self.host.bridge.newContact(item.jid.full(), self.getAttributes(item), item.groups, self.parent.profile)
     
     def onRosterRemove(self, entity):
         """Called when a roster removal event is received"""
         print _("removing %s from roster list") % entity.full()
-        self.host.memory.delContact(entity, self.parent.profile)
+        bare_jid = entity.userhost()
+
+        #we first remove item from local cache (self._groups and self._jids)
+        try:
+            item = self._jids.pop(bare_jid)
+        except KeyError:
+            log.warning("Received a roster remove event for an item not in cache")
+            return
+        for group in item.groups:
+            try:
+                jids_set = self._groups[group]
+                jids_set.remove(bare_jid)
+                if not jids_set:
+                    del self._groups[group]
+            except KeyError:
+                log.warning("there is not cache for the group [%(groups)s] of the removed roster item [%(jid)s]" %
+                            {"group": group, "jid": bare_jid})
+                                                                                                                            
+        #then we send the bridge signal
         self.host.bridge.contactDeleted(entity.userhost(), self.parent.profile)
 
     def getGroups(self):
-        """Return a set of groups"""
-        return self._groups
+        """Return a list of groups"""
+        return self._groups.keys()
+
+    def getItem(self, jid):
+        """Return RosterItem for a given jid
+        @param jid: jid of the contact
+        @return: RosterItem or None if contact is not in cache"""
+        return self._jids.get(jid.userhost(), None)
+
+    def getItems(self):
+        """Return all items of the roster"""
+        return self._jids
+        
 
 class SatPresenceProtocol(xmppim.PresenceClientProtocol):
 
@@ -243,8 +284,8 @@
     def subscribed(self, entity):
         xmppim.PresenceClientProtocol.subscribed(self, entity)
         self.host.memory.delWaitingSub(entity.userhost(), self.parent.profile)
-        contact = self.host.memory.getContact(entity, self.parent.profile) 
-        if not contact or contact[0]['to'] == 'False': #we automatically subscribe to 'to' presence
+        item = self.parent.roster.getItem(entity)
+        if not item or not item.subscriptionTo: #we automatically subscribe to 'to' presence
             debug(_('sending automatic "from" subscription request'))
             self.subscribe(entity)
 
@@ -262,8 +303,8 @@
 
     def subscribeReceived(self, entity):
         debug (_("subscription request from [%s]") % entity.userhost())
-        contact = self.host.memory.getContact(entity, self.parent.profile) 
-        if contact and contact[0]['to'] == 'True':
+        item = self.parent.roster.getItem(entity)
+        if item and item.subscriptionTo:
             #We automatically accept subscription if we are already subscribed to contact presence
             debug(_('sending automatic subscription acceptance'))
             self.subscribed(entity)
@@ -273,8 +314,8 @@
 
     def unsubscribeReceived(self, entity):
         debug (_("unsubscription asked for [%s]") % entity.userhost())
-        contact = self.host.memory.getContact(entity, self.parent.profile) 
-        if contact and contact[0]['from'] == 'True': #we automatically remove contact
+        item = self.parent.roster.getItem(entity)
+        if item and item.subscriptionFrom: #we automatically remove contact
             debug(_('automatic contact deletion'))
             self.host.delContact(entity.userhost(), self.parent.profile)
         self.host.bridge.subscribe('unsubscribe', entity.userhost(), self.parent.profile)
--- a/src/memory/memory.py	Sat Mar 24 17:48:12 2012 +0100
+++ b/src/memory/memory.py	Mon Mar 26 00:22:49 2012 +0200
@@ -491,7 +491,6 @@
         info (_("Memory manager init"))
         self.initialized = defer.Deferred() 
         self.host = host
-        self.contacts={}
         self.presenceStatus={}
         self.lastResource={} #tmp, will be refactored with bdd integration
         self.subscriptions={}
@@ -655,51 +654,6 @@
         assert(self.server_features.has_key(profile))
         return feature in self.server_features[profile]
 
-
-    def addContact(self, contact_jid, attributes, groups, profile_key):
-        debug("Memory addContact: %s",contact_jid.userhost())
-        profile = self.getProfileName(profile_key)
-        if not profile:
-            error (_('Trying to add a contact to a non-existant profile'))
-            return
-        assert(isinstance(attributes,dict))
-        assert(isinstance(groups,set))
-        if not self.contacts.has_key(profile):
-            self.contacts[profile] = {}
-        self.contacts[profile][contact_jid.userhost()]=[attributes, groups]
-
-    def delContact(self, contact_jid, profile_key):
-        debug("Memory delContact: %s",contact_jid.userhost())
-        profile = self.getProfileName(profile_key)
-        if not profile:
-            error (_('Trying to delete a contact for a non-existant profile'))
-            return
-        if self.contacts.has_key(profile) and self.contacts[profile].has_key(contact_jid.userhost()):
-            del self.contacts[profile][contact_jid.userhost()]
-    
-    def getContact(self, contact_jid, profile_key):
-        profile = self.getProfileName(profile_key)
-        if not profile:
-            error(_('Asking a contact for a non-existant profile'))
-            return None
-        if self.contacts.has_key(profile) and self.contacts[profile].has_key(contact_jid.userhost()):
-            return self.contacts[profile][contact_jid.userhost()]
-    
-    def getContacts(self, profile_key):
-        """Return list of contacts for given profile
-        @param profile_key: profile key
-        @return list of [contact, attr, groups]"""
-        profile = self.getProfileName(profile_key)
-        if not profile:
-            error(_('Asking contacts for a non-existant profile'))
-            return []
-        ret=[]
-        if self.contacts.has_key(profile):
-            for contact in self.contacts[profile]:
-                attr, groups = self.contacts[profile][contact]
-                ret.append([contact, attr, groups ])
-        return ret
-
     def getLastResource(self, contact, profile_key):
         """Return the last resource used by a contact
         @param contact: contact jid (unicode)