# HG changeset patch # User Goffi # Date 1418232753 -3600 # Node ID f8a8434dbac79617362bba42e84aaa2ff9e5d884 # Parent 93bce9e4c9c8b0644ee0915b56f9b9b892bf9649 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 diff -r 93bce9e4c9c8 -r f8a8434dbac7 src/bridge/DBus.py --- a/src/bridge/DBus.py Mon Nov 24 17:20:51 2014 +0100 +++ b/src/bridge/DBus.py Wed Dec 10 18:32:33 2014 +0100 @@ -230,9 +230,9 @@ @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 @@ @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 @@ @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 diff -r 93bce9e4c9c8 -r f8a8434dbac7 src/bridge/bridge_constructor/bridge_template.ini --- a/src/bridge/bridge_constructor/bridge_template.ini Mon Nov 24 17:20:51 2014 +0100 +++ b/src/bridge/bridge_constructor/bridge_template.ini Wed Dec 10 18:32:33 2014 +0100 @@ -255,12 +255,13 @@ 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_1=%(doc_profile_key)s [updateContact] +async= type=method category=core sig_in=ssass @@ -520,6 +522,7 @@ doc_param_3=%(doc_profile_key)s [delContact] +async= type=method category=core sig_in=ss diff -r 93bce9e4c9c8 -r f8a8434dbac7 src/core/exceptions.py --- a/src/core/exceptions.py Mon Nov 24 17:20:51 2014 +0100 +++ b/src/core/exceptions.py Wed Dec 10 18:32:33 2014 +0100 @@ -93,5 +93,6 @@ class PasswordError(Exception): pass + class SkipHistory(Exception): # used in MessageReceivedTrigger to avoid history writting pass diff -r 93bce9e4c9c8 -r f8a8434dbac7 src/core/sat_main.py --- a/src/core/sat_main.py Mon Nov 24 17:20:51 2014 +0100 +++ b/src/core/sat_main.py Wed Dec 10 18:32:33 2014 +0100 @@ -331,12 +331,15 @@ 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 @@ 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 @@ 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 @@ @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 @@ 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 @@ 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 @@ 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 @@ @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: diff -r 93bce9e4c9c8 -r f8a8434dbac7 src/core/xmpp.py --- a/src/core/xmpp.py Mon Nov 24 17:20:51 2014 +0100 +++ b/src/core/xmpp.py Wed Dec 10 18:32:33 2014 +0100 @@ -172,14 +172,42 @@ 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 @@ def removeItem(self, to_jid): """Remove a contact from roster list @param to_jid: a JID instance - """ - 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): + @return: Deferred """ - 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 @@ 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 @@ 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) + @return: RosterItem or None if contact is not in cache + """ + return self._jids.get(jid, None) - def getBareJids(self): - """Return all bare jids (as unicode) of the roster""" + 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 @@ 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 @@ 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 = {} diff -r 93bce9e4c9c8 -r f8a8434dbac7 src/plugins/plugin_xep_0054.py --- a/src/plugins/plugin_xep_0054.py Mon Nov 24 17:20:51 2014 +0100 +++ b/src/plugins/plugin_xep_0054.py Wed Dec 10 18:32:33 2014 +0100 @@ -102,9 +102,9 @@ # 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) diff -r 93bce9e4c9c8 -r f8a8434dbac7 src/stdui/ui_contact_list.py --- a/src/stdui/ui_contact_list.py Mon Nov 24 17:20:51 2014 +0100 +++ b/src/stdui/ui_contact_list.py Wed Dec 10 18:32:33 2014 +0100 @@ -50,7 +50,7 @@ @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