diff --git a/docs/releases.rst b/docs/releases.rst index 8de19077..3d067fe5 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -20,6 +20,7 @@ unreleased ---------- `git master `_ *will likely become v19.0.0* +* add `TorControlProtocol.when_disconnected()` Returns a new deferred to each caller. v18.3.0 ------- diff --git a/test/test_torcontrolprotocol.py b/test/test_torcontrolprotocol.py index fe6d8e6f..8a0012f3 100644 --- a/test/test_torcontrolprotocol.py +++ b/test/test_torcontrolprotocol.py @@ -215,6 +215,38 @@ def it_was_called(*args): self.protocol.connectionLost(f) self.assertTrue(it_was_called.yes) + def test_when_disconnect(self): + """ + see that we get our callback for when_disconnected if the + transport goes away + """ + def it_was_called(arg): + it_was_called.yes = True + return None + it_was_called.yes = False + + d = self.protocol.when_disconnected() + d.addCallback(it_was_called) + f = failure.Failure(error.ConnectionDone("It's all over")) + self.protocol.connectionLost(f) + self.assertTrue(it_was_called.yes) + + def test_when_disconnect_error(self): + """ + see that we get our errback for when_disconnected if the + transport goes away + """ + def it_was_called(arg): + it_was_called.yes = True + return None + it_was_called.yes = False + + d = self.protocol.when_disconnected() + d.addErrback(it_was_called) + f = failure.Failure(RuntimeError("sadness")) + self.protocol.connectionLost(f) + self.assertTrue(it_was_called.yes) + def test_disconnect_errback(self): """ see that we get our callback on_disconnect if the transport diff --git a/txtorcon/torcontrolprotocol.py b/txtorcon/torcontrolprotocol.py index db8ebf18..33421a57 100644 --- a/txtorcon/torcontrolprotocol.py +++ b/txtorcon/torcontrolprotocol.py @@ -23,7 +23,7 @@ from txtorcon.interface import ITorControlProtocol from .spaghetti import FSM, State, Transition -from .util import maybe_coroutine +from .util import maybe_coroutine, SingleObserver DEFAULT_VALUE = 'DEFAULT' @@ -271,6 +271,11 @@ def __init__(self, password_function=None): there was an error, the errback is called instead. """ + self._when_disconnected = SingleObserver() + """ + Private. See :func:`.when_disconnected` + """ + self.post_bootstrap = defer.Deferred() """ This Deferred is triggered when we're done setting up @@ -648,6 +653,13 @@ def queue_command(self, cmd, arg=None): self._maybe_issue_command() return d + def when_disconnected(self): + """ + :returns: a Deferred that fires when (if) we disconnect from our + Tor process. + """ + return self._when_disconnected.when_fired() + # the remaining methods are internal API implementations, # callbacks and state-tracking methods -- you shouldn't have any # need to call them. @@ -672,6 +684,11 @@ def connectionMade(self): def connectionLost(self, reason): "Protocol API" txtorlog.msg('connection terminated: ' + str(reason)) + if reason.check(ConnectionDone): + self._when_disconnected.fire(self) + else: + self._when_disconnected.fire(reason) + # ...and this is why we don't do on_disconnect = Deferred() :( # and instead should have had on_disconnect() method that # returned a new Deferred to each caller..(we're checking if