[tor-commits] [stem/master] Reordering Controller methods

atagar at torproject.org atagar at torproject.org
Mon Dec 31 03:13:07 UTC 2012


commit 86de12ecab643b809d2e75265512e0dc4267dd12
Author: Damian Johnson <atagar at torproject.org>
Date:   Sun Dec 30 16:23:42 2012 -0800

    Reordering Controller methods
    
    Rearranging the Controller's methods to match the header. This'll both make it
    easier to find things and rearrange our documentation.
---
 stem/control.py |  577 +++++++++++++++++++++++++++----------------------------
 1 files changed, 288 insertions(+), 289 deletions(-)

diff --git a/stem/control.py b/stem/control.py
index fdcf332..73f0faf 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -15,19 +15,18 @@ providing its own for interacting at a higher level.
     |
     |- authenticate - authenticates this controller with tor
     |
+    |- get_info - issues a GETINFO query for a parameter
     |- get_version - convenience method to get tor version
     |- get_socks_listeners - provides where tor is listening for SOCKS connections
     |- get_protocolinfo - information about the controller interface
     |
-    |- get_info - issues a GETINFO query for a parameter
-    |- get_conf - gets the value of a configuration option
-    |- get_conf_map - gets the values of multiple configuration options
-    |
     |- get_server_descriptor - querying the server descriptor for a relay
     |- get_server_descriptors - provides all presently available server descriptors
     |- get_network_status - querying the router status entry for a relay
     |- get_network_statuses - provides all preently available router status entries
     |
+    |- get_conf - gets the value of a configuration option
+    |- get_conf_map - gets the values of multiple configuration options
     |- set_conf - sets the value of a configuration option
     |- reset_conf - reverts configuration options to their default values
     |- set_options - sets or resets the values of multiple configuration options
@@ -35,12 +34,12 @@ providing its own for interacting at a higher level.
     |- add_event_listener - attaches an event listener to be notified of tor events
     |- remove_event_listener - removes a listener so it isn't notified of further events
     |
-    |- load_conf - loads configuration information as if it was in the torrc
-    |- save_conf - saves configuration information to the torrc
-    |
     |- is_caching_enabled - true if the controller has enabled caching
     |- clear_cache - clears any cached results
     |
+    |- load_conf - loads configuration information as if it was in the torrc
+    |- save_conf - saves configuration information to the torrc
+    |
     |- is_feature_enabled - checks if a given controller feature is enabled
     |- enable_feature - enables a controller feature that has been disabled by default
     |
@@ -666,101 +665,14 @@ class Controller(BaseController):
     
     super(Controller, self).close()
   
-  def add_event_listener(self, listener, *events):
-    """
-    Directs further tor controller events to a given function. The function is
-    expected to take a single argument, which is a
-    :class:`~stem.response.events.Event` subclass. For instance the following
-    would print the bytes sent and received by tor over five seconds...
-    
-    ::
-    
-      import time
-      from stem.control import Controller, EventType
-      
-      def print_bw(event):
-        print "sent: %i, received: %i" % (event.written, event.read)
-      
-      with Controller.from_port(control_port = 9051) as controller:
-        controller.authenticate()
-        controller.add_event_listener(print_bw, EventType.BW)
-        time.sleep(5)
-    
-    :param functor listener: function to be called when an event is received
-    :param stem.control.EventType events: event types to be listened for
-    
-    :raises: :class:`stem.ProtocolError` if unable to set the events
-    """
-    
-    # first checking that tor supports these event types
-    for event_type in events:
-      event_version = stem.response.events.EVENT_TYPE_TO_CLASS[event_type]._VERSION_ADDED
-      if not self.get_version().meets_requirements(event_version):
-        raise stem.InvalidRequest(552, "%s event requires Tor version %s or later" % (event_type, event_version))
-    
-    with self._event_listeners_lock:
-      for event_type in events:
-        self._event_listeners.setdefault(event_type, []).append(listener)
-      
-      self._attach_listeners()
-  
-  def remove_event_listener(self, listener):
-    """
-    Stops a listener from being notified of further tor events.
-    
-    :param stem.control.EventListener listener: listener to be removed
-    
-    :raises: :class:`stem.ProtocolError` if unable to set the events
-    """
-    
-    with self._event_listeners_lock:
-      event_types_changed = False
-      
-      for event_type, event_listeners in self._event_listeners.items():
-        if listener in event_listeners:
-          event_listeners.remove(listener)
-          
-          if len(event_listeners) == 0:
-            event_types_changed = True
-            del self._event_listeners[event_type]
-      
-      if event_types_changed:
-        response = self.msg("SETEVENTS %s" % " ".join(self._event_listeners.keys()))
-        
-        if not response.is_ok():
-          raise stem.ProtocolError("SETEVENTS received unexpected response\n%s" % response)
-  
-  def is_caching_enabled(self):
-    """
-    **True** if caching has been enabled, **False** otherwise.
-    
-    :returns: bool to indicate if caching is enabled
-    """
-    
-    return self._is_caching_enabled
-  
-  def is_geoip_unavailable(self):
-    """
-    Provides **True** if we've concluded hat our geoip database is unavailable,
-    **False** otherwise. This is determined by having our 'GETINFO
-    ip-to-country/\*' lookups fail so this will default to **False** if we
-    aren't making those queries.
-    
-    Geoip failures will be untracked if caching is disabled.
-    
-    :returns: **bool** to indicate if we've concluded our geoip database to be
-      unavailable or not
-    """
-    
-    return self._geoip_failure_count >= GEOIP_FAILURE_THRESHOLD
-  
-  def clear_cache(self):
+  def authenticate(self, *args, **kwargs):
     """
-    Drops any cached results.
+    A convenience method to authenticate the controller. This is just a
+    pass-through to :func:`stem.connection.authenticate`.
     """
     
-    self._request_cache = {}
-    self._geoip_failure_count = 0
+    import stem.connection
+    stem.connection.authenticate(self, *args, **kwargs)
   
   def get_info(self, params, default = UNDEFINED):
     """
@@ -890,6 +802,81 @@ class Controller(BaseController):
       if default == UNDEFINED: raise exc
       else: return default
   
+  def get_socks_listeners(self, default = UNDEFINED):
+    """
+    Provides the SOCKS **(address, port)** tuples that tor has open.
+    
+    :param object default: response if the query fails
+    
+    :returns: list of **(address, port)** tuples for the available SOCKS
+      listeners
+    
+    :raises: :class:`stem.ControllerError` if unable to determine the listeners
+      and no default was provided
+    """
+    
+    try:
+      proxy_addrs = []
+      
+      try:
+        for listener in self.get_info("net/listeners/socks").split():
+          if not (listener.startswith('"') and listener.endswith('"')):
+            raise stem.ProtocolError("'GETINFO net/listeners/socks' responses are expected to be quoted: %s" % listener)
+          elif not ':' in listener:
+            raise stem.ProtocolError("'GETINFO net/listeners/socks' had a listener without a colon: %s" % listener)
+          
+          listener = listener[1:-1] # strip quotes
+          addr, port = listener.split(':')
+          proxy_addrs.append((addr, port))
+      except stem.InvalidArguments:
+        # tor version is old (pre-tor-0.2.2.26-beta), use get_conf() instead
+        socks_port = self.get_conf('SocksPort')
+        
+        for listener in self.get_conf('SocksListenAddress', multiple = True):
+          if ':' in listener:
+            addr, port = listener.split(':')
+            proxy_addrs.append((addr, port))
+          else:
+            proxy_addrs.append((listener, socks_port))
+      
+      # validate that address/ports are valid, and convert ports to ints
+      
+      for addr, port in proxy_addrs:
+        if not stem.util.connection.is_valid_ip_address(addr):
+          raise stem.ProtocolError("Invalid address for a SOCKS listener: %s" % addr)
+        elif not stem.util.connection.is_valid_port(port):
+          raise stem.ProtocolError("Invalid port for a SOCKS listener: %s" % port)
+      
+      return [(addr, int(port)) for (addr, port) in proxy_addrs]
+    except Exception, exc:
+      if default == UNDEFINED: raise exc
+      else: return default
+  
+  def get_protocolinfo(self, default = UNDEFINED):
+    """
+    A convenience method to get the protocol info of the controller.
+    
+    :param object default: response if the query fails
+    
+    :returns: :class:`~stem.response.protocolinfo.ProtocolInfoResponse` provided by tor
+    
+    :raises:
+      * :class:`stem.ProtocolError` if the PROTOCOLINFO response is
+        malformed
+      * :class:`stem.SocketError` if problems arise in establishing or
+        using the socket
+      
+      An exception is only raised if we weren't provided a default response.
+    """
+    
+    import stem.connection
+    
+    try:
+      return stem.connection.get_protocolinfo(self)
+    except Exception, exc:
+      if default == UNDEFINED: raise exc
+      else: return default
+  
   def get_server_descriptor(self, relay, default = UNDEFINED):
     """
     Provides the server descriptor for the relay with the given fingerprint or
@@ -1023,40 +1010,6 @@ class Controller(BaseController):
           for entry in default:
             yield entry
   
-  def authenticate(self, *args, **kwargs):
-    """
-    A convenience method to authenticate the controller. This is just a
-    pass-through to :func:`stem.connection.authenticate`.
-    """
-    
-    import stem.connection
-    stem.connection.authenticate(self, *args, **kwargs)
-  
-  def get_protocolinfo(self, default = UNDEFINED):
-    """
-    A convenience method to get the protocol info of the controller.
-    
-    :param object default: response if the query fails
-    
-    :returns: :class:`~stem.response.protocolinfo.ProtocolInfoResponse` provided by tor
-    
-    :raises:
-      * :class:`stem.ProtocolError` if the PROTOCOLINFO response is
-        malformed
-      * :class:`stem.SocketError` if problems arise in establishing or
-        using the socket
-      
-      An exception is only raised if we weren't provided a default response.
-    """
-    
-    import stem.connection
-    
-    try:
-      return stem.connection.get_protocolinfo(self)
-    except Exception, exc:
-      if default == UNDEFINED: raise exc
-      else: return default
-  
   def get_conf(self, param, default = UNDEFINED, multiple = False):
     """
     Queries the current value for a configuration option. Some configuration
@@ -1347,31 +1300,112 @@ class Controller(BaseController):
       else:
         raise stem.ProtocolError("Returned unexpected status code: %s" % response.code)
   
-  def load_conf(self, configtext):
+  def add_event_listener(self, listener, *events):
     """
-    Sends the configuration text to Tor and loads it as if it has been read from
-    the torrc.
+    Directs further tor controller events to a given function. The function is
+    expected to take a single argument, which is a
+    :class:`~stem.response.events.Event` subclass. For instance the following
+    would print the bytes sent and received by tor over five seconds...
     
-    :param str configtext: the configuration text
+    ::
     
-    :raises: :class:`stem.ControllerError` if the call fails
-    """
+      import time
+      from stem.control import Controller, EventType
+      
+      def print_bw(event):
+        print "sent: %i, received: %i" % (event.written, event.read)
+      
+      with Controller.from_port(control_port = 9051) as controller:
+        controller.authenticate()
+        controller.add_event_listener(print_bw, EventType.BW)
+        time.sleep(5)
     
-    response = self.msg("LOADCONF\n%s" % configtext)
-    stem.response.convert("SINGLELINE", response)
+    :param functor listener: function to be called when an event is received
+    :param stem.control.EventType events: event types to be listened for
     
-    if response.code in ("552", "553"):
-      if response.code == "552" and response.message.startswith("Invalid config file: Failed to parse/validate config: Unknown option"):
-        raise stem.InvalidArguments(response.code, response.message, [response.message[70:response.message.find('.', 70) - 1]])
-      raise stem.InvalidRequest(response.code, response.message)
-    elif not response.is_ok():
-      raise stem.ProtocolError("+LOADCONF Received unexpected response\n%s" % str(response))
-  
-  def save_conf(self):
+    :raises: :class:`stem.ProtocolError` if unable to set the events
     """
-    Saves the current configuration options into the active torrc file.
     
-    :raises:
+    # first checking that tor supports these event types
+    for event_type in events:
+      event_version = stem.response.events.EVENT_TYPE_TO_CLASS[event_type]._VERSION_ADDED
+      if not self.get_version().meets_requirements(event_version):
+        raise stem.InvalidRequest(552, "%s event requires Tor version %s or later" % (event_type, event_version))
+    
+    with self._event_listeners_lock:
+      for event_type in events:
+        self._event_listeners.setdefault(event_type, []).append(listener)
+      
+      self._attach_listeners()
+  
+  def remove_event_listener(self, listener):
+    """
+    Stops a listener from being notified of further tor events.
+    
+    :param stem.control.EventListener listener: listener to be removed
+    
+    :raises: :class:`stem.ProtocolError` if unable to set the events
+    """
+    
+    with self._event_listeners_lock:
+      event_types_changed = False
+      
+      for event_type, event_listeners in self._event_listeners.items():
+        if listener in event_listeners:
+          event_listeners.remove(listener)
+          
+          if len(event_listeners) == 0:
+            event_types_changed = True
+            del self._event_listeners[event_type]
+      
+      if event_types_changed:
+        response = self.msg("SETEVENTS %s" % " ".join(self._event_listeners.keys()))
+        
+        if not response.is_ok():
+          raise stem.ProtocolError("SETEVENTS received unexpected response\n%s" % response)
+  
+  def is_caching_enabled(self):
+    """
+    **True** if caching has been enabled, **False** otherwise.
+    
+    :returns: bool to indicate if caching is enabled
+    """
+    
+    return self._is_caching_enabled
+  
+  def clear_cache(self):
+    """
+    Drops any cached results.
+    """
+    
+    self._request_cache = {}
+    self._geoip_failure_count = 0
+  
+  def load_conf(self, configtext):
+    """
+    Sends the configuration text to Tor and loads it as if it has been read from
+    the torrc.
+    
+    :param str configtext: the configuration text
+    
+    :raises: :class:`stem.ControllerError` if the call fails
+    """
+    
+    response = self.msg("LOADCONF\n%s" % configtext)
+    stem.response.convert("SINGLELINE", response)
+    
+    if response.code in ("552", "553"):
+      if response.code == "552" and response.message.startswith("Invalid config file: Failed to parse/validate config: Unknown option"):
+        raise stem.InvalidArguments(response.code, response.message, [response.message[70:response.message.find('.', 70) - 1]])
+      raise stem.InvalidRequest(response.code, response.message)
+    elif not response.is_ok():
+      raise stem.ProtocolError("+LOADCONF Received unexpected response\n%s" % str(response))
+  
+  def save_conf(self):
+    """
+    Saves the current configuration options into the active torrc file.
+    
+    :raises:
       * :class:`stem.ControllerError` if the call fails
       * :class:`stem.OperationFailed` if the client is unable to save
         the configuration file
@@ -1387,56 +1421,6 @@ class Controller(BaseController):
     else:
       raise stem.ProtocolError("SAVECONF returned unexpected response code")
   
-  def get_socks_listeners(self, default = UNDEFINED):
-    """
-    Provides the SOCKS **(address, port)** tuples that tor has open.
-    
-    :param object default: response if the query fails
-    
-    :returns: list of **(address, port)** tuples for the available SOCKS
-      listeners
-    
-    :raises: :class:`stem.ControllerError` if unable to determine the listeners
-      and no default was provided
-    """
-    
-    try:
-      proxy_addrs = []
-      
-      try:
-        for listener in self.get_info("net/listeners/socks").split():
-          if not (listener.startswith('"') and listener.endswith('"')):
-            raise stem.ProtocolError("'GETINFO net/listeners/socks' responses are expected to be quoted: %s" % listener)
-          elif not ':' in listener:
-            raise stem.ProtocolError("'GETINFO net/listeners/socks' had a listener without a colon: %s" % listener)
-          
-          listener = listener[1:-1] # strip quotes
-          addr, port = listener.split(':')
-          proxy_addrs.append((addr, port))
-      except stem.InvalidArguments:
-        # tor version is old (pre-tor-0.2.2.26-beta), use get_conf() instead
-        socks_port = self.get_conf('SocksPort')
-        
-        for listener in self.get_conf('SocksListenAddress', multiple = True):
-          if ':' in listener:
-            addr, port = listener.split(':')
-            proxy_addrs.append((addr, port))
-          else:
-            proxy_addrs.append((listener, socks_port))
-      
-      # validate that address/ports are valid, and convert ports to ints
-      
-      for addr, port in proxy_addrs:
-        if not stem.util.connection.is_valid_ip_address(addr):
-          raise stem.ProtocolError("Invalid address for a SOCKS listener: %s" % addr)
-        elif not stem.util.connection.is_valid_port(port):
-          raise stem.ProtocolError("Invalid port for a SOCKS listener: %s" % port)
-      
-      return [(addr, int(port)) for (addr, port) in proxy_addrs]
-    except Exception, exc:
-      if default == UNDEFINED: raise exc
-      else: return default
-  
   def is_feature_enabled(self, feature):
     """
     Checks if a control connection feature is enabled. These features can be
@@ -1502,44 +1486,56 @@ class Controller(BaseController):
     
     self._enabled_features += [entry.upper() for entry in features]
   
-  def signal(self, signal):
+  def get_circuit(self, circuit_id, default = UNDEFINED):
     """
-    Sends a signal to the Tor client.
+    Provides a circuit presently available from tor.
     
-    :param stem.Signal signal: type of signal to be sent
+    :param int circuit_id: circuit to be fetched
+    :param object default: response if the query fails
     
-    :raises: :class:`stem.InvalidArguments` if signal provided wasn't recognized
-    """
+    :returns: :class:`stem.events.CircuitEvent` for the given circuit
     
-    response = self.msg("SIGNAL %s" % signal)
-    stem.response.convert("SINGLELINE", response)
+    :raises:
+      * :class:`stem.ControllerError` if the call fails
+      * ValueError if the circuit doesn't exist
+      
+      An exception is only raised if we weren't provided a default response.
+    """
     
-    if not response.is_ok():
-      if response.code == "552":
-        raise stem.InvalidArguments(response.code, response.message, [signal])
+    try:
+      for circ in self.get_circuits():
+        if circ.id == circuit_id:
+          return circ
       
-      raise stem.ProtocolError("SIGNAL response contained unrecognized status code: %s" % response.code)
+      raise ValueError("Tor presently does not have a circuit with the id of '%s'" % circuit_id)
+    except Exception, exc:
+      if default == UNDEFINED: raise exc
+      else: return default
   
-  def repurpose_circuit(self, circuit_id, purpose):
+  def get_circuits(self, default = UNDEFINED):
     """
-    Changes a circuit's purpose. Currently, two purposes are recognized...
-      * general
-      * controller
+    Provides tor's currently available circuits.
     
-    :param str circuit_id: id of the circuit whose purpose is to be changed
-    :param str purpose: purpose (either "general" or "controller")
+    :param object default: response if the query fails
     
-    :raises: :class:`stem.InvalidArguments` if the circuit doesn't exist or if the purpose was invalid
-    """
+    :returns: **list** of :class:`stem.events.CircuitEvent` for our circuits
     
-    response = self.msg("SETCIRCUITPURPOSE %s purpose=%s" % (circuit_id, purpose))
-    stem.response.convert("SINGLELINE", response)
+    :raises: :class:`stem.ControllerError` if the call fails and no default was provided
+    """
     
-    if not response.is_ok():
-      if response.code == "552":
-        raise stem.InvalidRequest(response.code, response.message)
-      else:
-        raise stem.ProtocolError("SETCIRCUITPURPOSE returned unexpected response code: %s" % response.code)
+    try:
+      circuits = []
+      response = self.get_info("circuit-status")
+      
+      for circ in response.splitlines():
+        circ_message = stem.socket.recv_message(StringIO.StringIO("650 CIRC " + circ + "\r\n"))
+        stem.response.convert("EVENT", circ_message, arrived_at = 0)
+        circuits.append(circ_message)
+      
+      return circuits
+    except Exception, exc:
+      if default == UNDEFINED: raise exc
+      else: return default
   
   def new_circuit(self, path = None, purpose = "general"):
     """
@@ -1614,56 +1610,49 @@ class Controller(BaseController):
     
     return new_circuit
   
-  def get_circuit(self, circuit_id, default = UNDEFINED):
+  def repurpose_circuit(self, circuit_id, purpose):
     """
-    Provides a circuit presently available from tor.
-    
-    :param int circuit_id: circuit to be fetched
-    :param object default: response if the query fails
+    Changes a circuit's purpose. Currently, two purposes are recognized...
+      * general
+      * controller
     
-    :returns: :class:`stem.events.CircuitEvent` for the given circuit
+    :param str circuit_id: id of the circuit whose purpose is to be changed
+    :param str purpose: purpose (either "general" or "controller")
     
-    :raises:
-      * :class:`stem.ControllerError` if the call fails
-      * ValueError if the circuit doesn't exist
-      
-      An exception is only raised if we weren't provided a default response.
+    :raises: :class:`stem.InvalidArguments` if the circuit doesn't exist or if the purpose was invalid
     """
     
-    try:
-      for circ in self.get_circuits():
-        if circ.id == circuit_id:
-          return circ
-      
-      raise ValueError("Tor presently does not have a circuit with the id of '%s'" % circuit_id)
-    except Exception, exc:
-      if default == UNDEFINED: raise exc
-      else: return default
+    response = self.msg("SETCIRCUITPURPOSE %s purpose=%s" % (circuit_id, purpose))
+    stem.response.convert("SINGLELINE", response)
+    
+    if not response.is_ok():
+      if response.code == "552":
+        raise stem.InvalidRequest(response.code, response.message)
+      else:
+        raise stem.ProtocolError("SETCIRCUITPURPOSE returned unexpected response code: %s" % response.code)
   
-  def get_circuits(self, default = UNDEFINED):
+  def close_circuit(self, circuit_id, flag = ''):
     """
-    Provides tor's currently available circuits.
-    
-    :param object default: response if the query fails
+    Closes the specified circuit.
     
-    :returns: **list** of :class:`stem.events.CircuitEvent` for our circuits
+    :param str circuit_id: id of the circuit to be closed
+    :param str flag: optional value to modify closing, the only flag available
+      is "IfUnused" which will not close the circuit unless it is unused
     
-    :raises: :class:`stem.ControllerError` if the call fails and no default was provided
+    :raises: :class:`stem.InvalidArguments` if the circuit is unknown
+    :raises: :class:`stem.InvalidRequest` if not enough information is provided
     """
     
-    try:
-      circuits = []
-      response = self.get_info("circuit-status")
-      
-      for circ in response.splitlines():
-        circ_message = stem.socket.recv_message(StringIO.StringIO("650 CIRC " + circ + "\r\n"))
-        stem.response.convert("EVENT", circ_message, arrived_at = 0)
-        circuits.append(circ_message)
-      
-      return circuits
-    except Exception, exc:
-      if default == UNDEFINED: raise exc
-      else: return default
+    response = self.msg("CLOSECIRCUIT %s %s"% (str(circuit_id), flag))
+    stem.response.convert("SINGLELINE", response)
+    
+    if not response.is_ok():
+      if response.code in ('512', '552'):
+        if response.message.startswith("Unknown circuit "):
+          raise stem.InvalidArguments(response.code, response.message, [circuit_id])
+        raise stem.InvalidRequest(response.code, response.message)
+      else:
+        raise stem.ProtocolError("CLOSECIRCUIT returned unexpected response code: %s" % response.code)
   
   def attach_stream(self, stream_id, circuit_id, hop = None):
     """
@@ -1693,29 +1682,6 @@ class Controller(BaseController):
       else:
         raise stem.ProtocolError("ATTACHSTREAM returned unexpected response code: %s" % response.code)
   
-  def close_circuit(self, circuit_id, flag = ''):
-    """
-    Closes the specified circuit.
-    
-    :param str circuit_id: id of the circuit to be closed
-    :param str flag: optional value to modify closing, the only flag available
-      is "IfUnused" which will not close the circuit unless it is unused
-    
-    :raises: :class:`stem.InvalidArguments` if the circuit is unknown
-    :raises: :class:`stem.InvalidRequest` if not enough information is provided
-    """
-    
-    response = self.msg("CLOSECIRCUIT %s %s"% (str(circuit_id), flag))
-    stem.response.convert("SINGLELINE", response)
-    
-    if not response.is_ok():
-      if response.code in ('512', '552'):
-        if response.message.startswith("Unknown circuit "):
-          raise stem.InvalidArguments(response.code, response.message, [circuit_id])
-        raise stem.InvalidRequest(response.code, response.message)
-      else:
-        raise stem.ProtocolError("CLOSECIRCUIT returned unexpected response code: %s" % response.code)
-  
   def close_stream(self, stream_id, reason = stem.RelayEndReason.MISC, flag = ''):
     """
     Closes the specified stream.
@@ -1744,6 +1710,39 @@ class Controller(BaseController):
       else:
         raise stem.ProtocolError("CLOSESTREAM returned unexpected response code: %s" % response.code)
   
+  def signal(self, signal):
+    """
+    Sends a signal to the Tor client.
+    
+    :param stem.Signal signal: type of signal to be sent
+    
+    :raises: :class:`stem.InvalidArguments` if signal provided wasn't recognized
+    """
+    
+    response = self.msg("SIGNAL %s" % signal)
+    stem.response.convert("SINGLELINE", response)
+    
+    if not response.is_ok():
+      if response.code == "552":
+        raise stem.InvalidArguments(response.code, response.message, [signal])
+      
+      raise stem.ProtocolError("SIGNAL response contained unrecognized status code: %s" % response.code)
+  
+  def is_geoip_unavailable(self):
+    """
+    Provides **True** if we've concluded hat our geoip database is unavailable,
+    **False** otherwise. This is determined by having our 'GETINFO
+    ip-to-country/\*' lookups fail so this will default to **False** if we
+    aren't making those queries.
+    
+    Geoip failures will be untracked if caching is disabled.
+    
+    :returns: **bool** to indicate if we've concluded our geoip database to be
+      unavailable or not
+    """
+    
+    return self._geoip_failure_count >= GEOIP_FAILURE_THRESHOLD
+  
   def map_address(self, mapping):
     """
     Map addresses to replacement addresses. Tor replaces subseqent connections





More information about the tor-commits mailing list