core: improved roster management + misc:

- updated methods to no use anymore methods deprecated in Wokkel
    - use of full jid when it make sense instead of bare jid
    - getContacts, updateContact and delContact are now asynchronous
This commit is contained in:
Goffi 2014-12-10 18:32:33 +01:00
commit 6e144c6458
7 changed files with 102 additions and 98 deletions

View file

@ -230,9 +230,9 @@ class DbusObject(dbus.service.Object):
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='ss', out_signature='',
async_callbacks=None)
def delContact(self, entity_jid, profile_key="@DEFAULT@"):
return self._callback("delContact", unicode(entity_jid), unicode(profile_key))
async_callbacks=('callback', 'errback'))
def delContact(self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None):
return self._callback("delContact", unicode(entity_jid), unicode(profile_key), callback=callback, errback=errback)
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='ss', out_signature='(asa(sss))',
@ -260,9 +260,9 @@ class DbusObject(dbus.service.Object):
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='s', out_signature='a(sa{ss}as)',
async_callbacks=None)
def getContacts(self, profile_key="@DEFAULT@"):
return self._callback("getContacts", unicode(profile_key))
async_callbacks=('callback', 'errback'))
def getContacts(self, profile_key="@DEFAULT@", callback=None, errback=None):
return self._callback("getContacts", unicode(profile_key), callback=callback, errback=errback)
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='ss', out_signature='as',
@ -434,9 +434,9 @@ class DbusObject(dbus.service.Object):
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='ssass', out_signature='',
async_callbacks=None)
def updateContact(self, entity_jid, name, groups, profile_key="@DEFAULT@"):
return self._callback("updateContact", unicode(entity_jid), unicode(name), groups, unicode(profile_key))
async_callbacks=('callback', 'errback'))
def updateContact(self, entity_jid, name, groups, profile_key="@DEFAULT@", callback=None, errback=None):
return self._callback("updateContact", unicode(entity_jid), unicode(name), groups, unicode(profile_key), callback=callback, errback=errback)
def __attributes(self, in_sign):
"""Return arguments to user given a in_sign

View file

@ -255,12 +255,13 @@ doc=Tell if a profile is connected
doc_param_0=%(doc_profile_key)s
[getContacts]
async=
type=method
category=core
sig_in=s
sig_out=a(sa{ss}as)
param_0_default="@DEFAULT@"
doc=Return information about all contacts
doc=Return information about all contacts (the roster)
doc_param_0=%(doc_profile_key)s
doc_return=array of tuples with the following values:
- JID of the contact
@ -508,6 +509,7 @@ doc_param_0=entity_jid: JID to add to roster
doc_param_1=%(doc_profile_key)s
[updateContact]
async=
type=method
category=core
sig_in=ssass
@ -520,6 +522,7 @@ doc_param_2=groups: list of group where the entity is
doc_param_3=%(doc_profile_key)s
[delContact]
async=
type=method
category=core
sig_in=ss

View file

@ -93,5 +93,6 @@ class DatabaseError(Exception):
class PasswordError(Exception):
pass
class SkipHistory(Exception): # used in MessageReceivedTrigger to avoid history writting
pass

View file

@ -331,12 +331,15 @@ class SAT(service.Service):
def getContacts(self, profile_key):
client = self.getClient(profile_key)
ret = []
for item in client.roster.getItems(): # we get all items 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 got_roster(dummy):
ret = []
for item in client.roster.getItems(): # we get all items 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
return client.roster.got_roster.addCallback(got_roster)
def getContactsFromGroup(self, group, profile_key):
client = self.getClient(profile_key)
@ -456,7 +459,7 @@ class SAT(service.Service):
def sendMessage(self, to_jid, msg, subject=None, mess_type='auto', extra={}, no_trigger=False, profile_key=C.PROF_KEY_NONE):
#FIXME: check validity of recipient
profile = self.memory.getProfileName(profile_key)
assert(profile)
assert profile
client = self.profiles[profile]
if extra is None:
extra = {}
@ -572,7 +575,7 @@ class SAT(service.Service):
if statuses is None:
statuses = {}
profile = self.memory.getProfileName(profile_key)
assert(profile)
assert profile
priority = int(self.memory.getParamA("Priority", "Connection", profile_key=profile))
self.profiles[profile].presence.available(to_jid, show, statuses, priority)
#XXX: FIXME: temporary fix to work around openfire 3.7.0 bug (presence is not broadcasted to generating resource)
@ -588,7 +591,7 @@ class SAT(service.Service):
@param raw_jid: unicode entity's jid
@param profile_key: profile"""
profile = self.memory.getProfileName(profile_key)
assert(profile)
assert profile
to_jid = jid.JID(raw_jid)
log.debug(_('subsciption request [%(subs_type)s] for %(jid)s') % {'subs_type': subs_type, 'jid': to_jid.full()})
if subs_type == "subscribe":
@ -606,8 +609,8 @@ class SAT(service.Service):
def addContact(self, to_jid, profile_key):
"""Add a contact in roster list"""
profile = self.memory.getProfileName(profile_key)
assert(profile)
#self.profiles[profile].roster.addItem(to_jid) #XXX: disabled (cf http://wokkel.ik.nu/ticket/56))
assert profile
# presence is sufficient, as a roster push will be sent according to RFC 6121 §3.1.2
self.profiles[profile].presence.subscribe(to_jid)
def _updateContact(self, to_jid_s, name, groups, profile_key):
@ -616,12 +619,12 @@ class SAT(service.Service):
def updateContact(self, to_jid, name, groups, profile_key):
"""update a contact in roster list"""
profile = self.memory.getProfileName(profile_key)
assert(profile)
assert profile
groups = set(groups)
roster_item = RosterItem(to_jid)
roster_item.name = name or None
roster_item.groups = set(groups)
self.profiles[profile].roster.updateItem(roster_item)
return self.profiles[profile].roster.setItem(roster_item)
def _delContact(self, to_jid_s, profile_key):
return self.delContact(jid.JID(to_jid_s), profile_key)
@ -629,10 +632,16 @@ class SAT(service.Service):
def delContact(self, to_jid, profile_key):
"""Remove contact from roster list"""
profile = self.memory.getProfileName(profile_key)
assert(profile)
self.profiles[profile].roster.removeItem(to_jid)
self.profiles[profile].presence.unsubscribe(to_jid)
assert profile
d1 = self.profiles[profile].roster.removeItem(to_jid)
d2 = self.profiles[profile].presence.unsubscribe(to_jid)
d_list = defer.DefferedList([d1, d2])
def check_result(list_result):
for success, value in list_result:
if not success:
raise value
d_list.addCallback(check_result)
return d_list
## Discovery ##
# discovery methods are shortcuts to self.memory.disco
@ -770,6 +779,9 @@ class SAT(service.Service):
@profile_key: %(doc_profile_key)s
@return: a deferred which fire a dict where key can be:
- xmlui: a XMLUI need to be displayed
- validated: if present, can be used to launch a callback, it can have the values
- C.BOOL_TRUE
- C.BOOL_FALSE
"""
profile = self.memory.getProfileName(profile_key)
if not profile:

View file

@ -172,14 +172,42 @@ class SatRosterProtocol(xmppim.RosterClientProtocol):
def __init__(self, host):
xmppim.RosterClientProtocol.__init__(self)
self.host = host
self.got_roster = defer.Deferred()
self.got_roster = defer.Deferred() # called when roster is received and ready
#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
self._groups = {} # map from groups to jids: key=group value=set of jids
self._jids = None # map from jids to RosterItem: key=jid value=RosterItem
def rosterCb(self, roster):
for raw_jid, item in roster.iteritems():
self.onRosterSet(item)
assert roster is not None # FIXME: must be managed with roster versioning
self._jids = roster
def _registerItem(self, item):
"""Register item in local cache
item must be already registered in self._jids before this method is called
@param item (RosterIem): item added
"""
log.debug("registering item: {}".format(item.jid.full()))
if item.entity.resource:
log.warning("Received a roster item with a resource, this is not common but not restricted by RFC 6121, this case may be not well tested.")
import ipdb; ipdb.set_trace()
if not item.subscriptionTo and not item.subscriptionFrom and not item.ask:
#XXX: current behaviour: we don't want contact in our roster list
# if there is no presence subscription
# may change in the future
self.removeItem(item.jid) # FIXME: to be checked
return
if not item.subscriptionTo:
if not item.subscriptionFrom:
log.info(_("There's no subscription between you and [{}]!").format(item.jid.full()))
else:
log.info(_("You are not subscribed to [{}]!").format(item.jid.full()))
if not item.subscriptionFrom:
log.info(_("[{}] is not subscribed to you!").format(item.jid.full()))
#self.host.memory.addContact(item.jid, item_attr, item.groups, self.parent.profile)
for group in item.groups:
self._groups.setdefault(group, set()).add(item.entity)
def requestRoster(self):
""" ask the server for Roster list """
@ -190,31 +218,9 @@ class SatRosterProtocol(xmppim.RosterClientProtocol):
def removeItem(self, to_jid):
"""Remove a contact from roster list
@param to_jid: a JID instance
@return: Deferred
"""
xmppim.RosterClientProtocol.removeItem(self, to_jid)
#TODO: check IQ result
#XXX: disabled (cf http://wokkel.ik.nu/ticket/56))
#def addItem(self, to):
#"""Add a contact to roster list"""
#xmppim.RosterClientProtocol.addItem(self, to)
#TODO: check IQ result"""
def updateItem(self, roster_item):
"""
Update an item of the contact list.
@param roster_item: item to update
"""
iq = compat.IQ(self.xmlstream, 'set')
iq.addElement((xmppim.NS_ROSTER, 'query'))
item = iq.query.addElement('item')
item['jid'] = roster_item.jid.userhost()
if roster_item.name:
item['name'] = roster_item.name
for group in roster_item.groups:
item.addElement('group', content=group)
return iq.send()
return xmppim.RosterClientProtocol.removeItem(self, to_jid)
def getAttributes(self, item):
"""Return dictionary of attributes as used in bridge from a RosterItem
@ -228,54 +234,34 @@ class SatRosterProtocol(xmppim.RosterClientProtocol):
item_attr['name'] = item.name
return item_attr
def onRosterSet(self, item):
"""Called when a new/update roster item is received"""
#TODO: send a signal to frontends
if not item.subscriptionTo and not item.subscriptionFrom and not item.ask:
#XXX: current behaviour: we don't want contact in our roster list
# if there is no presence subscription
# may change in the future
self.removeItem(item.jid)
return
log.debug(_("New contact in roster list: %s") % item.jid.full())
if not item.subscriptionTo:
if not item.subscriptionFrom:
log.info(_("There's no subscription between you and [%s]!") % item.jid.full())
else:
log.info(_("You are not subscribed to [%s]!") % item.jid.full())
if not item.subscriptionFrom:
log.info(_("[%s] is not subscribed to you!") % item.jid.full())
#self.host.memory.addContact(item.jid, item_attr, item.groups, self.parent.profile)
bare_jid = item.jid.userhostJID()
self._jids[bare_jid] = item
for group in item.groups:
self._groups.setdefault(group, set()).add(bare_jid)
def setReceived(self, request):
#TODO: implement roster versioning (cf RFC 6121 §2.6)
item = request.item
self._jids[item.entity] = item
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"""
def removeReceived(self, request):
entity = request.item.entity
print _("removing %s from roster list") % entity.full()
bare_jid = entity.userhostJID()
# we first remove item from local cache (self._groups and self._jids)
try:
item = self._jids.pop(bare_jid)
item = self._jids.pop(entity)
except KeyError:
log.warning("Received a roster remove event for an item not in cache")
log.error("Received a roster remove event for an item not in cache ({})".format(entity))
return
for group in item.groups:
try:
jids_set = self._groups[group]
jids_set.remove(bare_jid)
jids_set.remove(entity)
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})
{"group": group, "jid": entity})
# then we send the bridge signal
self.host.bridge.contactDeleted(entity.userhost(), self.parent.profile)
self.host.bridge.contactDeleted(entity.full(), self.parent.profile)
def getGroups(self):
"""Return a list of groups"""
@ -283,17 +269,19 @@ class SatRosterProtocol(xmppim.RosterClientProtocol):
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.userhostJID(), None)
def getBareJids(self):
"""Return all bare jids (as unicode) of the roster"""
@param jid: jid of the contact
@return: RosterItem or None if contact is not in cache
"""
return self._jids.get(jid, None)
def getJids(self):
"""Return all jids of the roster"""
return self._jids.keys()
def isJidInRoster(self, entity_jid):
"""Return True if jid is in roster"""
return entity_jid.userhostJID() in self._jids
return entity_jid in self._jids
def getItems(self):
"""Return all items of the roster"""
@ -318,7 +306,7 @@ class SatPresenceProtocol(xmppim.PresenceClientProtocol):
super(SatPresenceProtocol, self).send(obj)
def availableReceived(self, entity, show=None, statuses=None, priority=0):
log.debug(_("presence update for [%(entity)s] (available, show=%(show)s statuses=%(statuses)s priority=%(priority)d)") % {'entity': entity, 'show': show, 'statuses': statuses, 'priority': priority})
log.debug(_("presence update for [%(entity)s] (available, show=%(show)s statuses=%(statuses)s priority=%(priority)d)") % {'entity': entity, C.PRESENCE_SHOW: show, C.PRESENCE_STATUSES: statuses, C.PRESENCE_PRIORITY: priority})
if not statuses:
statuses = {}
@ -341,7 +329,7 @@ class SatPresenceProtocol(xmppim.PresenceClientProtocol):
self.parent.profile)
def unavailableReceived(self, entity, statuses=None):
log.debug(_("presence update for [%(entity)s] (unavailable, statuses=%(statuses)s)") % {'entity': entity, 'statuses': statuses})
log.debug(_("presence update for [%(entity)s] (unavailable, statuses=%(statuses)s)") % {'entity': entity, C.PRESENCE_STATUSES: statuses})
if not statuses:
statuses = {}

View file

@ -102,9 +102,9 @@ class XEP_0054(object):
# the current naive approach keeps a map between all jids of all profiles
# in persistent cache, and check if cached jid are in roster, then put avatar
# hashs in memory.
for _jid in client.roster.getBareJids() + [client.jid.userhost()]:
if _jid in self.avatars_cache:
self.host.memory.updateEntityData(jid.JID(_jid), "avatar", self.avatars_cache[_jid], client.profile)
for _jid in client.roster.getJids() + [client.jid]:
if _jid.userhost() in self.avatars_cache:
self.host.memory.updateEntityData(_jid, "avatar", self.avatars_cache[_jid.userhost()], client.profile)
def profileConnected(self, profile):
client = self.host.getClient(profile)

View file

@ -50,7 +50,7 @@ class ContactList(object):
@return: list[string]
"""
client = self.host.getClient(profile)
ret = [contact.userhost() for contact in client.roster.getBareJids()]
ret = [contact.full() for contact in client.roster.getJids()]
ret.sort()
return ret