Mercurial > libervia-backend
diff src/core/sat_main.py @ 742:03744d9ebc13
plugin XEP-0033: implementation of the addressing feature:
- frontends pass the recipients in the extra parameter of sendMessage
- backend checks if the target server supports the feature (this is not done yet by prosody plugin)
- features and identities are cached per profile and server
- messages are duplicated in history for now (TODO: redesign the database)
- echos signals are also duplicated to the sender (FIXME)
author | souliane <souliane@mailoo.org> |
---|---|
date | Wed, 11 Dec 2013 17:16:53 +0100 |
parents | e07afabc4a25 |
children | 5aff0beddb28 |
line wrap: on
line diff
--- a/src/core/sat_main.py Fri Dec 13 05:35:24 2013 +0100 +++ b/src/core/sat_main.py Wed Dec 11 17:16:53 2013 +0100 @@ -62,6 +62,20 @@ return "sat_id_" + str(sat_id) +class MessageSentAndStored(Exception): + """ Exception to raise if the message has been already sent and stored in the + history by the trigger, so the rest of the process should be stopped. This + should normally be raised by the trigger with the minimal priority """ + pass + + +class AbortSendMessage(Exception): + """ Exception to raise if sending the message should be aborted. This can be + raised by any trigger but a side action should be planned by the trigger + to inform the user about what happened """ + pass + + class SAT(service.Service): def get_next_id(self): @@ -354,6 +368,14 @@ raise exceptions.ProfileKeyUnknownError return [self.profiles[profile]] + def getClientHostJid(self, profile_key): + """Convenient method to get the client host from profile key + @return: host jid or None if it doesn't exist""" + profile = self.memory.getProfileName(profile_key) + if not profile: + return None + return self.profiles[profile].getHostJid() + def registerNewAccount(self, login, password, email, server, port=5222, id=None, profile_key='@DEFAULT@'): """Connect to a server and create a new account using in-band registration""" profile = self.memory.getProfileName(profile_key) @@ -541,27 +563,52 @@ if mess_data["message"]: mess_data['xml'].addElement("body", None, mess_data["message"]) - def sendAndStore(mess_data): + def sendErrback(e): + text = '%s: %s' % (e.value.__class__.__name__, e.getErrorMessage()) + if e.check(MessageSentAndStored): + debug(text) + elif e.check(AbortSendMessage): + warning(text) + else: + error("Unmanaged exception: %s" % text) + return e + + treatments.addCallbacks(self.sendAndStoreMessage, sendErrback, [False, profile]) + treatments.callback(mess_data) + + def sendAndStoreMessage(self, mess_data, skip_send=False, profile=None): + """Actually send and store the message to history, after all the treatments + have been done. This has been moved outside the main sendMessage method + because it is used by XEP-0033 to complete a server-side feature not yet + implemented by the prosody plugin. + @param mess_data: message data dictionary + @param skip_send: set to True to skip sending the message to only store it + @param profile: profile + """ + try: + client = self.profiles[profile] + except KeyError: + error(_("Trying to send a message with no profile")) + return + current_jid = client.jid + if not skip_send: client.xmlstream.send(mess_data['xml']) - if mess_data["type"] != "groupchat": - # we don't add groupchat message to history, as we get them back - # and they will be added then - if mess_data['message']: # we need a message to save something - self.memory.addToHistory(current_jid, mess_data['to'], - unicode(mess_data["message"]), - unicode(mess_data["type"]), - mess_data['extra'], - profile=profile) + if mess_data["type"] != "groupchat": + # we don't add groupchat message to history, as we get them back + # and they will be added then + if mess_data['message']: # we need a message to save something + self.memory.addToHistory(current_jid, mess_data['to'], + unicode(mess_data["message"]), + unicode(mess_data["type"]), + mess_data['extra'], + profile=profile) # We send back the message, so all clients are aware of it - if mess_data["message"]: - self.bridge.newMessage(mess_data['xml']['from'], - unicode(mess_data["message"]), - mess_type=mess_data["type"], - to_jid=mess_data['xml']['to'], extra=mess_data['extra'], - profile=profile) - - treatments.addCallback(sendAndStore) - treatments.callback(mess_data) + self.bridge.newMessage(mess_data['xml']['from'], + unicode(mess_data["message"]), + mess_type=mess_data["type"], + to_jid=mess_data['xml']['to'], + extra=mess_data['extra'], + profile=profile) def setPresence(self, to="", show="", priority=0, statuses=None, profile_key='@NONE@'): """Send our presence information""" @@ -623,30 +670,100 @@ self.profiles[profile].roster.removeItem(to_jid) self.profiles[profile].presence.unsubscribe(to_jid) + def requestServerDisco(self, feature, jid_=None, cache_only=False, profile_key="@NONE"): + """Discover if a server or its items offer a given feature + @param feature: the feature to check + @param jid_: the jid of the server + @param cache_only: expect the result to be in cache and don't actually + make any request to avoid returning a Deferred. This can be used anytime + for requesting the local server because the data are cached for sure. + @result: the Deferred entity jid offering the feature, or None + """ + profile = self.memory.getProfileName(profile_key) + + if not profile: + return defer.succeed(None) + if jid_ is None: + jid_ = self.getClientHostJid(profile) + cache_only = True + hasServerFeature = lambda entity: entity if self.memory.hasServerFeature(feature, entity, profile) else None + + def haveItemsFeature(dummy=None): + if jid_ in self.memory.server_identities[profile]: + for entity in self.memory.server_identities[profile][jid_].values(): + if hasServerFeature(entity): + return entity + return None + + entity = hasServerFeature(jid_) or haveItemsFeature() + if entity: + return defer.succeed(entity) + elif entity is False or cache_only: + return defer.succeed(None) + + # data for this server are not in cache + disco = self.profiles[profile].disco + + def errback(failure, method, jid_, profile): + # the target server is not reachable + logging.error("disco.%s on %s failed! [%s]" % (method.func_name, jid_, profile)) + logging.error("reason: %s" % failure.getErrorMessage()) + if method == disco.requestInfo: + features = self.memory.server_features.setdefault(profile, {}) + features.setdefault(jid_, []) + elif method == disco.requestItems: + identities = self.memory.server_identities.setdefault(profile, {}) + identities.setdefault(jid_, {}) + return failure + + def callback(d): + if hasServerFeature(jid_): + return jid_ + else: + d2 = disco.requestItems(jid_).addCallback(self.serverDiscoItems, disco, jid_, profile) + d2.addErrback(errback, disco.requestItems, jid_, profile) + return d2.addCallback(haveItemsFeature) + + d = disco.requestInfo(jid_).addCallback(self.serverDisco, jid_, profile) + d.addCallbacks(callback, errback, [], [disco.requestInfo, jid_, profile]) + return d + ## callbacks ## - def serverDisco(self, disco, profile): - """xep-0030 Discovery Protocol.""" + def serverDisco(self, disco, jid_=None, profile=None): + """xep-0030 Discovery Protocol. + @param disco: result of the disco info query + @param jid_: the jid of the target server + @param profile: profile of the user + """ + if jid_ is None: + jid_ = self.getClientHostJid(profile) + debug(_("Requested disco info on %s") % jid_) for feature in disco.features: - debug(_("Feature found: %s"), feature) - self.memory.addServerFeature(feature, profile) - for cat, type in disco.identities: - debug(_("Identity found: [%(category)s/%(type)s] %(identity)s") % {'category': cat, 'type': type, 'identity': disco.identities[(cat, type)]}) + debug(_("Feature found: %s") % feature) + self.memory.addServerFeature(feature, jid_, profile) + for cat, type_ in disco.identities: + debug(_("Identity found: [%(category)s/%(type)s] %(identity)s") + % {'category': cat, 'type': type_, 'identity': disco.identities[(cat, type_)]}) - def serverDiscoItems(self, disco_result, disco_client, profile, initialized): + def serverDiscoItems(self, disco_result, disco_client, jid_, profile, initialized=None): """xep-0030 Discovery Protocol. @param disco_result: result of the disco item querry @param disco_client: SatDiscoProtocol instance + @param jid_: the jid of the target server @param profile: profile of the user @param initialized: deferred which must be chained when everything is done""" - def _check_entity_cb(result, entity, profile): - for category, type in result.identities: - debug(_('Identity added: (%(category)s,%(type)s) ==> %(entity)s [%(profile)s]') % { - 'category': category, 'type': type, 'entity': entity, 'profile': profile}) - self.memory.addServerIdentity(category, type, entity, profile) + def _check_entity_cb(result, entity, jid_, profile): + debug(_("Requested disco info on %s") % entity) + for category, type_ in result.identities: + debug(_('Identity added: (%(category)s,%(type)s) ==> %(entity)s [%(profile)s]') + % {'category': category, 'type': type_, 'entity': entity, 'profile': profile}) + self.memory.addServerIdentity(category, type_, entity, jid_, profile) + for feature in result.features: + self.memory.addServerFeature(feature, entity, profile) - def _errback(result, entity, profile): + def _errback(result, entity, jid_, profile): warning(_("Can't get information on identity [%(entity)s] for profile [%(profile)s]") % {'entity': entity, 'profile': profile}) defer_list = [] @@ -654,9 +771,11 @@ if item.entity.full().count('.') == 1: # XXX: workaround for a bug on jabberfr, tmp warning(_('Using jabberfr workaround, be sure your domain has at least two levels (e.g. "example.tld", not "example" alone)')) continue - args = [item.entity, profile] + args = [item.entity, jid_, profile] defer_list.append(disco_client.requestInfo(item.entity).addCallbacks(_check_entity_cb, _errback, args, None, args)) - defer.DeferredList(defer_list).chainDeferred(initialized) + if initialized: + defer.DeferredList(defer_list).chainDeferred(initialized) + ## Generic HMI ## def actionResult(self, action_id, action_type, data, profile):