core: Roster cache refactoring: cache is now managed by client's SatRosterProtocol instance.

This commit is contained in:
Goffi 2012-03-26 00:22:49 +02:00
commit 972b4c2555
3 changed files with 73 additions and 66 deletions

View file

@ -123,7 +123,7 @@ class SAT(service.Service):
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 @@ class SAT(service.Service):
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"""

View file

@ -120,7 +120,9 @@ class SatRosterProtocol(xmppim.RosterClientProtocol):
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 @@ class SatRosterProtocol(xmppim.RosterClientProtocol):
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 @@ class SatRosterProtocol(xmppim.RosterClientProtocol):
#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 @@ class SatPresenceProtocol(xmppim.PresenceClientProtocol):
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 @@ class SatPresenceProtocol(xmppim.PresenceClientProtocol):
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 @@ class SatPresenceProtocol(xmppim.PresenceClientProtocol):
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)

View file

@ -491,7 +491,6 @@ class Memory:
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 @@ class Memory:
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)