diff sat/core/xmpp.py @ 2885:e9016bfd8cb2

core (xmpp): advanced handling of connection termination factory's clientConnectionFailed and clientConnectionLost methods are monkey patched to allow client to tune termination: - a warning when connection is lost in an unclean way - connector is saved to allow to disable automatic reconnection and retry manually later - new triggers connection_failed and connection_lost allow plugins to tune connection termination workflow
author Goffi <goffi@goffi.org>
date Sat, 06 Apr 2019 18:51:20 +0200
parents 368a60f05d0e
children b06cb71079fa
line wrap: on
line diff
--- a/sat/core/xmpp.py	Fri Apr 05 21:22:05 2019 +0200
+++ b/sat/core/xmpp.py	Sat Apr 06 18:51:20 2019 +0200
@@ -17,10 +17,11 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+from functools import partial
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.memory import cache
-from twisted.internet import defer
+from twisted.internet import defer, error as internet_error
 from twisted.words.protocols.jabber.xmlstream import XMPPHandler
 from twisted.words.protocols.jabber import xmlstream
 from twisted.words.protocols.jabber import error
@@ -53,6 +54,16 @@
 
     def __init__(self, host_app, profile, max_retries):
         factory = self.factory
+
+        # we monkey patch clientConnectionLost to handle networkEnabled/networkDisabled
+        # and to allow plugins to tune reconnection mechanism
+        clientConnectionFailed_ori = factory.clientConnectionFailed
+        clientConnectionLost_ori = factory.clientConnectionLost
+        factory.clientConnectionFailed = partial(
+            self.connectionTerminated, term_type=u"failed", cb=clientConnectionFailed_ori)
+        factory.clientConnectionLost = partial(
+            self.connectionTerminated, term_type=u"lost", cb=clientConnectionLost_ori)
+
         factory.maxRetries = max_retries
         factory.maxDelay = 30
         # when self._connected_d is None, we are not connected
@@ -275,6 +286,40 @@
 
     ## connection ##
 
+    def connectionTerminated(self, connector, reason, term_type, cb):
+        """Display disconnection reason, and call factory method
+
+        This method is monkey patched to factory, allowing plugins to handle finely
+        reconnection with the triggers.
+        @param connector(twisted.internet.base.BaseConnector): current connector
+        @param reason(failure.Failure): why connection has been terminated
+        @param term_type(unicode): on of 'failed' or 'lost'
+        @param cb(callable): original factory method
+
+        @trigger connection_failed(connector, reason): connection can't be established
+        @trigger connection_lost(connector, reason): connection was available but it not
+            anymore
+        """
+        # we save connector because it may be deleted when connection will be dropped
+        # if reconnection is disabled
+        self._saved_connector = connector
+        if reason is not None and not isinstance(reason.value,
+                                                 internet_error.ConnectionDone):
+            try:
+                reason_str = unicode(reason.value)
+            except Exception:
+                # FIXME: workaround for Android were p4a strips docstrings
+                #        while Twisted use docstring in __str__
+                # TODO: create a ticket upstream, Twisted should work when optimization
+                #       is used
+                reason_str = unicode(reason.value.__class__)
+            log.warning(u"Connection {term_type}: {reason}".format(
+                term_type = term_type,
+                reason=reason_str))
+        if not self.host_app.trigger.point(u"connection_" + term_type, connector, reason):
+            return
+        return cb(connector, reason)
+
     def _connected(self, xs):
         send_hooks = []
         receive_hooks = []