[tor-commits] [stem/master] More uniformly renaming to 'tutorials'

atagar at torproject.org atagar at torproject.org
Wed Mar 13 16:19:37 UTC 2013


commit 89e8fb8f37ebaad8d2009fedba766d1d42831dd8
Author: Damian Johnson <atagar at torproject.org>
Date:   Wed Mar 13 09:14:33 2013 -0700

    More uniformly renaming to 'tutorials'
    
    Several of our resources, including individual tutorial pages, were still under
    'tutorial'. I'm also dropping the old 'tutorial.rst' (I'm not getting any
    replies to the tor-dev@ email and having a stale copy is confusing).
---
 docs/_static/section/tutorial/cauldron.png        |  Bin 9943 -> 0 bytes
 docs/_static/section/tutorial/mirror.png          |  Bin 2985 -> 0 bytes
 docs/_static/section/tutorial/soviet.png          |  Bin 4679 -> 0 bytes
 docs/_static/section/tutorial/tortoise.png        |  Bin 14700 -> 0 bytes
 docs/_static/section/tutorial/train.png           |  Bin 9887 -> 0 bytes
 docs/_static/section/tutorials/cauldron.png       |  Bin 0 -> 9943 bytes
 docs/_static/section/tutorials/mirror.png         |  Bin 0 -> 2985 bytes
 docs/_static/section/tutorials/soviet.png         |  Bin 0 -> 4679 bytes
 docs/_static/section/tutorials/tortoise.png       |  Bin 0 -> 14700 bytes
 docs/_static/section/tutorials/train.png          |  Bin 0 -> 9887 bytes
 docs/contents.rst                                 |   11 +-
 docs/tutorial.rst                                 |   74 --------
 docs/tutorial/double_double_toil_and_trouble.rst  |   26 ---
 docs/tutorial/mirror_mirror_on_the_wall.rst       |  178 ------------------
 docs/tutorial/the_little_relay_that_could.rst     |   65 -------
 docs/tutorial/to_russia_with_love.rst             |   70 -------
 docs/tutorial/tortoise_and_the_hare.rst           |  204 ---------------------
 docs/tutorials.rst                                |   30 ++--
 docs/tutorials/double_double_toil_and_trouble.rst |   26 +++
 docs/tutorials/mirror_mirror_on_the_wall.rst      |  178 ++++++++++++++++++
 docs/tutorials/the_little_relay_that_could.rst    |   65 +++++++
 docs/tutorials/to_russia_with_love.rst            |   70 +++++++
 docs/tutorials/tortoise_and_the_hare.rst          |  204 +++++++++++++++++++++
 23 files changed, 564 insertions(+), 637 deletions(-)

diff --git a/docs/_static/section/tutorial/cauldron.png b/docs/_static/section/tutorial/cauldron.png
deleted file mode 100644
index 74d32bf..0000000
Binary files a/docs/_static/section/tutorial/cauldron.png and /dev/null differ
diff --git a/docs/_static/section/tutorial/mirror.png b/docs/_static/section/tutorial/mirror.png
deleted file mode 100644
index 69c2938..0000000
Binary files a/docs/_static/section/tutorial/mirror.png and /dev/null differ
diff --git a/docs/_static/section/tutorial/soviet.png b/docs/_static/section/tutorial/soviet.png
deleted file mode 100644
index c54c3d8..0000000
Binary files a/docs/_static/section/tutorial/soviet.png and /dev/null differ
diff --git a/docs/_static/section/tutorial/tortoise.png b/docs/_static/section/tutorial/tortoise.png
deleted file mode 100644
index 52b5da0..0000000
Binary files a/docs/_static/section/tutorial/tortoise.png and /dev/null differ
diff --git a/docs/_static/section/tutorial/train.png b/docs/_static/section/tutorial/train.png
deleted file mode 100644
index 625b82e..0000000
Binary files a/docs/_static/section/tutorial/train.png and /dev/null differ
diff --git a/docs/_static/section/tutorials/cauldron.png b/docs/_static/section/tutorials/cauldron.png
new file mode 100644
index 0000000..74d32bf
Binary files /dev/null and b/docs/_static/section/tutorials/cauldron.png differ
diff --git a/docs/_static/section/tutorials/mirror.png b/docs/_static/section/tutorials/mirror.png
new file mode 100644
index 0000000..69c2938
Binary files /dev/null and b/docs/_static/section/tutorials/mirror.png differ
diff --git a/docs/_static/section/tutorials/soviet.png b/docs/_static/section/tutorials/soviet.png
new file mode 100644
index 0000000..c54c3d8
Binary files /dev/null and b/docs/_static/section/tutorials/soviet.png differ
diff --git a/docs/_static/section/tutorials/tortoise.png b/docs/_static/section/tutorials/tortoise.png
new file mode 100644
index 0000000..52b5da0
Binary files /dev/null and b/docs/_static/section/tutorials/tortoise.png differ
diff --git a/docs/_static/section/tutorials/train.png b/docs/_static/section/tutorials/train.png
new file mode 100644
index 0000000..625b82e
Binary files /dev/null and b/docs/_static/section/tutorials/train.png differ
diff --git a/docs/contents.rst b/docs/contents.rst
index 5826ad7..e25578a 100644
--- a/docs/contents.rst
+++ b/docs/contents.rst
@@ -4,11 +4,12 @@ Contents
 .. toctree::
    :maxdepth: 2
 
-   tutorial
-   tutorial/the_little_relay_that_could
-   tutorial/to_russia_with_love
-   tutorial/mirror_mirror_on_the_wall
-   tutorial/double_double_toil_and_trouble
+   tutorials
+   tutorials/the_little_relay_that_could
+   tutorials/to_russia_with_love
+   tutorials/tortoise_and_the_hare
+   tutorials/mirror_mirror_on_the_wall
+   tutorials/double_double_toil_and_trouble
 
    download
 
diff --git a/docs/tutorial.rst b/docs/tutorial.rst
deleted file mode 100644
index 7143892..0000000
--- a/docs/tutorial.rst
+++ /dev/null
@@ -1,74 +0,0 @@
-Tutorial
-========
-
-.. Image Sources:
-   
-   * The Little Relay That Could - train.png
-     Source: https://openclipart.org/detail/140185/tren-train-by-antroares
-     Author: Antroares
-     License: Public Domain
-     Alternate: https://openclipart.org/detail/1128/train-roadsign-by-ryanlerch
-   
-   * To Russia With Love - soviet.png
-     Source: https://openclipart.org/detail/146017/flag-of-the-soviet-union-by-marxist-leninist
-     Author: Unknown
-     License: Public Domain (not a subject of copyright according the Russian civil code)
-     Alternate: https://openclipart.org/detail/85555/communist-sabbatarian-ribbon-by-rones-85555
-   
-   * Mirror Mirror On The Wall - mirror.png
-     Source: https://openclipart.org/detail/152155/mirror-frame-by-gsagri04
-     Author: Unknown (gsagri04?)
-     License: Public Domain
-     Alternate: https://openclipart.org/detail/174179/miroir-rectangulaire-by-defaz36-174179
-   
-   * Double Double Toil and Trouble - cauldron.png
-     Source: https://openclipart.org/detail/174099/cauldron-by-jarda-174099
-     Author: Unknown (jarda?)
-     License: Public Domain
-
-Getting started with any new library can be rather daunting, so let's get our
-feet wet by jumping straight in with some tutorials...
-
-.. list-table::
-   :widths: 1 10
-   :header-rows: 0
-
-   * - .. image:: /_static/section/tutorial/train.png
-          :target: tutorial/the_little_relay_that_could.html
-
-     - .. image:: /_static/label/the_little_relay_that_could.png
-          :target: tutorial/the_little_relay_that_could.html
-
-       Basics for talking with Tor. This will step you through configuring Tor
-       and writing your first script to talk with it.
-
-   * - .. image:: /_static/section/tutorial/soviet.png
-          :target: tutorial/to_russia_with_love.html
-
-     - .. image:: /_static/label/to_russia_with_love.png
-          :target: tutorial/to_russia_with_love.html
-
-       Rather than talking to Tor, we'll now talk **through** it. In this
-       tutorial we'll programmatically start Tor then use it to read a site
-       through mother Russia!
-
-   * - .. image:: /_static/section/tutorial/mirror.png
-          :target: tutorial/mirror_mirror_on_the_wall.html
-
-     - .. image:: /_static/label/mirror_mirror_on_the_wall.png
-          :target: tutorial/mirror_mirror_on_the_wall.html
-
-       Getting and acting upon information about relays in the Tor network.
-       Relay information is provided through documents called **descriptors**.
-       This walks you through both where to get them and a small script to tell
-       you the fastest Tor exits.
-
-   * - .. image:: /_static/section/tutorial/cauldron.png
-          :target: tutorial/double_double_toil_and_trouble.html
-
-     - .. image:: /_static/label/double_double_toil_and_trouble.png
-          :target: tutorial/double_double_toil_and_trouble.html
-
-       Sometimes it's easiest to learn a library by seeing how it's used in the
-       wild. This is a directory of scripts and applications that use stem.
-
diff --git a/docs/tutorial/double_double_toil_and_trouble.rst b/docs/tutorial/double_double_toil_and_trouble.rst
deleted file mode 100644
index 129a302..0000000
--- a/docs/tutorial/double_double_toil_and_trouble.rst
+++ /dev/null
@@ -1,26 +0,0 @@
-Double Double Toil and Trouble
-==============================
-
-Below is a listing of scripts and applications that use stem. If you have
-something you would like to have included on this page then `let me know
-<http://www.atagar.com/contact/>`_!
-
-.. list-table::
-   :widths: 1 10
-   :header-rows: 0
-
-   * - .. image:: /_static/arm.png
-          :target: http://www.atagar.com/arm/
-
-     - .. image:: /_static/label/arm.png
-          :target: http://www.atagar.com/arm/
-
-       Terminal status monitor for Tor. This provides a top like interface
-       including system resource usage, connection information, and much more.
-
-=========================================================================================================== ==========
-`Consensus Tracker <https://gitweb.torproject.org/atagar/tor-utils.git/blob/HEAD:/consensusTracker.py>`_    Script that performs an hourly check for the number of relays within the Tor network, looking for large jumps that may indicate a sybil attack.
-`Metrics Tasks <https://gitweb.torproject.org/metrics-tasks.git/tree>`_                                     One-off tasks related to Tor metrics. These mostly involve using descriptor information to answer a particular question. Tasks that involve stem are: `1854 <https://gitweb.torproject.org/metrics-tasks.git/blob/HEAD:/task-1854/pylinf.py>`_, `6232 <https://gitweb.torproject.org/metrics-tasks.git/blob/HEAD:/task-6232/pyentropy.py>`_, and `7241 <https://gitweb.torproject.org/metrics-tasks.git/blob/HEAD:/task-7241/first_pass.py>`_.
-`check_tor <http://anonscm.debian.org/gitweb/?p=users/lunar/check_tor.git;a=blob;f=check_tor.py;hb=HEAD>`_  Nagios check to verify that a relay is participating in the Tor network.
-=========================================================================================================== ==========
-
diff --git a/docs/tutorial/mirror_mirror_on_the_wall.rst b/docs/tutorial/mirror_mirror_on_the_wall.rst
deleted file mode 100644
index 1a7f81d..0000000
--- a/docs/tutorial/mirror_mirror_on_the_wall.rst
+++ /dev/null
@@ -1,178 +0,0 @@
-Mirror Mirror on the Wall
-=========================
-
-* :ref:`what-is-a-descriptor`
-* :ref:`where-can-i-get-the-current-descriptors`
-* :ref:`where-can-i-get-past-descriptors`
-* :ref:`putting-it-together`
-
-.. _what-is-a-descriptor:
-
-What is a descriptor?
----------------------
-
-Tor is made up of two parts: the application and a distributed network of a few
-thousand volunteer relays. Information about these relays is public, and made
-up of documents called **descriptors**.
-
-There are several different kinds of descriptors, the most common ones being...
-
-====================================================================== ===========
-Descriptor Type                                                        Description
-====================================================================== ===========
-`Server Descriptor <../api/descriptor/server_descriptor.html>`_        Information that relays publish about themselves. Tor clients once downloaded this information, but now they use microdescriptors instead.
-`ExtraInfo Descriptor <../api/descriptor/extrainfo_descriptor.html>`_  Relay information that tor clients do not need in order to function. This is self-published, like server descriptors, but not downloaded by default.
-`Microdescriptor <../api/descriptor/microdescriptor.html>`_            Minimalistic document that just includes the information necessary for tor clients to work.
-`Network Status Document <../api/descriptor/networkstatus.html>`_      Though tor relays are decentralized, the directories that track the overall network are not. These central points are called **directory authorities**, and every hour they publish a document called a **consensus** (aka, network status document). The consensus in turn is made up of **router status entries**.
-`Router Status Entry <../api/descriptor/router_status_entry.html>`_    Relay information provided by the directory authorities including flags, heuristics used for relay selection, etc.
-====================================================================== ===========
-
-.. _where-can-i-get-the-current-descriptors:
-
-Where can I get the current descriptors?
-----------------------------------------
-
-To work tor needs to have up-to-date information about relays within the
-network. As such getting current descriptors is easy: *just run tor*.
-
-Tor only gets the descriptors that it needs by default, so if you're scripting
-against tor you may want to set some of the following in your `torrc
-<https://www.torproject.org/docs/faq.html.en#torrc>`_. Keep in mind that these
-add a small burden to the network, so don't set them in a widely distributed
-application. And, of course, please consider `running tor as a relay
-<https://www.torproject.org/docs/tor-doc-relay.html.en>`_ so you give back to
-the network!
-
-::
-
-  # Descriptors have a range of time during which they're valid. To get the
-  # most recent descriptor information, regardless of if tor needs it or not,
-  # set the following.
-
-  FetchDirInfoExtraEarly 1
-
-  # If you aren't actively using tor as a client then tor will eventually stop
-  # downloading descriptor information that it doesn't need. To prevent this
-  # from happening set...
-
-  FetchUselessDescriptors 1
-
-  # Tor no longer downloads server descriptors by default, opting for
-  # microdescriptors instead. If you want tor to download server descriptors
-  # then set...
-
-  UseMicrodescriptors 0
-
-  # Tor doesn't need extrainfo descriptors to work. If you want tor to download
-  # them anyway then set...
-
-  DownloadExtraInfo 1
-
-Now that tor is happy chugging along up-to-date descriptors are available
-through tor's control socket...
-
-::
-
-  from stem.control import Controller
-
-  with Controller.from_port(port = 9051) as controller:
-    controller.authenticate()
-
-    for desc in controller.get_network_statuses():
-      print "found relay %s (%s)" % (desc.nickname, desc.fingerprint)
-
-... or by reading directly from tor's data directory...
-
-::
-
-  from stem.descriptor import parse_file
-
-  for desc in parse_file(open("/home/atagar/.tor/cached-consensus")):
-    print "found relay %s (%s)" % (desc.nickname, desc.fingerprint)
-
-.. _where-can-i-get-past-descriptors:
-
-Where can I get past descriptors?
----------------------------------
-
-Descriptor archives are available on `Tor's metrics site
-<https://metrics.torproject.org/data.html>`_. These archives can be read with
-the `DescriptorReader <api/descriptor/reader.html>`_...
-
-::
-
-  from stem.descriptor.reader import DescriptorReader
-
-  with DescriptorReader(["/home/atagar/server-descriptors-2013-03.tar"]) as reader:
-    for desc in reader:
-      print "found relay %s (%s)" % (desc.nickname, desc.fingerprint)
-
-.. _putting-it-together:
-
-Putting it together...
-----------------------
-
-As discussed above there are three methods for reading descriptors...
-
-* With the :class:`~stem.control.Controller` via methods like :func:`~stem.control.Controller.get_server_descriptors` and :func:`~stem.control.Controller.get_network_statuses`.
-* By reading the file directly with :func:`~stem.descriptor.__init__.parse_file`.
-* Reading with the `DescriptorReader <api/descriptor/reader.html>`_. This is best if you have you want to read everything from a directory or archive.
-
-Now lets say you want to figure out who the *biggest* exit relays are. You
-could use any of the methods above, but for this example we'll use the
-:class:`~stem.control.Controller`. This uses server descriptors, so keep in
-mind that you'll likely need to set "UseMicrodescriptors 0" in your torrc for
-this to work.
-
-::
-
-  import sys
-
-  from stem.control import Controller
-  from stem.util import str_tools
-
-  # provides a mapping of observed bandwidth to the relay nicknames
-  def get_bw_to_relay():
-    bw_to_relay = {}
-
-    with Controller.from_port(port = 9051) as controller:
-      controller.authenticate()
-
-      for desc in controller.get_server_descriptors():
-        if desc.exit_policy.is_exiting_allowed():
-          bw_to_relay.setdefault(desc.observed_bandwidth, []).append(desc.nickname)
-
-    return bw_to_relay
-
-  # prints the top fifteen relays
-
-  bw_to_relay = get_bw_to_relay()
-  count = 1
-
-  for bw_value in sorted(bw_to_relay.keys(), reverse = True):
-    for nickname in bw_to_relay[bw_value]:
-      print "%i. %s (%s/s)" % (count, nickname, str_tools.get_size_label(bw_value, 2))
-      count += 1
-
-      if count > 15:
-        sys.exit()
-
-::
-
-  % python example.py
-  1. herngaard (40.95 MB/s)
-  2. chaoscomputerclub19 (40.43 MB/s)
-  3. chaoscomputerclub18 (40.02 MB/s)
-  4. chaoscomputerclub20 (38.98 MB/s)
-  5. wannabe (38.63 MB/s)
-  6. dorrisdeebrown (38.48 MB/s)
-  7. manning2 (38.20 MB/s)
-  8. chaoscomputerclub21 (36.90 MB/s)
-  9. TorLand1 (36.22 MB/s)
-  10. bolobolo1 (35.93 MB/s)
-  11. manning1 (35.39 MB/s)
-  12. gorz (34.10 MB/s)
-  13. ndnr1 (25.36 MB/s)
-  14. politkovskaja2 (24.93 MB/s)
-  15. wau (24.72 MB/s)
-
diff --git a/docs/tutorial/the_little_relay_that_could.rst b/docs/tutorial/the_little_relay_that_could.rst
deleted file mode 100644
index 4f22b5b..0000000
--- a/docs/tutorial/the_little_relay_that_could.rst
+++ /dev/null
@@ -1,65 +0,0 @@
-The Little Relay that Could
-===========================
-
-Let's say you just set up your very first `Tor relay
-<https://www.torproject.org/docs/tor-doc-relay.html.en>`_ (thank you!), and now
-you want to write a script that tells you how much it is being used.
-
-First, for any script to talk with your relay it will need to have a control
-port available. This is a port that's usually only available on localhost and
-protected by either a **password** or **authentication cookie**.
-
-Look at your `torrc <https://www.torproject.org/docs/faq.html.en#torrc>`_ for
-the following configuration options...
-
-::
-
-  # This provides a port for our script to talk with. If you set this then be
-  # sure to also set either CookieAuthentication *or* HashedControlPassword!
-  #
-  # You could also use ControlSocket instead of ControlPort, which provides a
-  # file based socket. You don't need to have authentication if you use
-  # ControlSocket. For this example however we'll use a port.
-  
-  ControlPort 9051
-  
-  # Setting this will make Tor write an authentication cookie. Anything with
-  # permission to read this file can connect to Tor. If you're going to run
-  # your script with the same user or permission group as Tor then this is the
-  # easiest method of authentication to use.
-  
-  CookieAuthentication 1
-  
-  # Alternatively we can authenticate with a password. To set a password first
-  # get its hash...
-  #
-  # % tor --hash-password "my_password"
-  # 16:E600ADC1B52C80BB6022A0E999A7734571A451EB6AE50FED489B72E3DF
-  #
-  # ... and use that for the HashedControlPassword in your torrc.
-  
-  HashedControlPassword 16:E600ADC1B52C80BB6022A0E999A7734571A451EB6AE50FED489B72E3DF
-
-When you change your torrc you'll need to either restart Tor is issue a SIGHUP
-for the new settings to take effect. Now let's write a script that tells us how
-many bytes Tor has sent and received...
-
-::
-
-  from stem.control import Controller
-  
-  with Controller.from_port(port = 9051) as controller:
-    controller.authenticate()  # provide the password here if you set one
-
-    bytes_read = controller.get_info("traffic/read")
-    bytes_written = controller.get_info("traffic/written")
-
-    print "My Tor relay has read %s bytes and written %s." % (bytes_read, bytes_written)
-
-::
-
-  % python example.py 
-  My Tor relay has read 33406 bytes and written 29649.
-
-Congratulations! You've just written your first controller script.
-
diff --git a/docs/tutorial/to_russia_with_love.rst b/docs/tutorial/to_russia_with_love.rst
deleted file mode 100644
index 7fb7c66..0000000
--- a/docs/tutorial/to_russia_with_love.rst
+++ /dev/null
@@ -1,70 +0,0 @@
-To Russia With Love
-===================
-
-Say it's 1982, the height of the Cold War, and you're a journalist doing a piece on how the Internet looks from behind the Iron Curtain. Ignoring the minor detail that the Internet doesn't yet exist, we'll walk you through how you could do it - no passport required!
-
-The Internet isn't uniform. Localization, censorship, and selective service based on your IP's geographic location can make the Internet a very different place depending on where you're coming from.
-
-Tor relays are scattered all over the world and, as such, you can pretend to be from any place running an exit. This can be especially useful to evade pesky geolocational restrictions, such as news sites that refuse to work while you're traveling abroad.
-
-Tor makes `configuring your exit locale <https://www.torproject.org/docs/faq.html.en#ChooseEntryExit>`_ easy through the **ExitNodes** torrc option. Note that you don't need a control port (or even stem) to do this, though they can be useful if you later want to do something more elaborate.
-
-In the following example we're using stem to `start Tor <../api/process.html>`_, then reading a site through it with `PycURL <http://pycurl.sourceforge.net/>`_. This is not always reliable (some relays are lemons) so you may need to run this more than once.
-
-**Do not rely on the following not to leak.** Though it seems to work, DNS resolution and other edge cases might expose your real IP. If you have a suggestion for how to improve this example then please `let me know <http://www.atagar.com/contact/>`_!
-
-::
-
-  import StringIO
-
-  import pycurl
-  import stem.process
-
-  from stem.util import term
-
-  SOCKS_PORT = 7000
-
-  def curl(url):
-    """ 
-    Uses pycurl to fetch a site using the proxy on the SOCKS_PORT.
-    """
-
-    output = StringIO.StringIO()
-
-    query = pycurl.Curl()
-    query.setopt(pycurl.URL, url)
-    query.setopt(pycurl.PROXY, 'localhost')
-    query.setopt(pycurl.PROXYPORT, SOCKS_PORT)
-    query.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5)
-    query.setopt(pycurl.WRITEFUNCTION, output.write)
-
-    try:
-      query.perform()
-      return output.getvalue()
-    except pycurl.error, exc:
-      return "Unable to reach %s (%s)" % (url, exc)
-
-  # Start an instance of tor configured to only exit through Russia. This prints
-  # tor's bootstrap information as it starts.
-
-  def print_bootstrap_lines(line):
-    if "Bootstrapped " in line:
-      print term.format(line, term.Color.BLUE)
-
-  print term.format("Starting Tor:\n", term.Attr.BOLD)
-
-  tor_process = stem.process.launch_tor_with_config(
-    config = {
-      'SocksPort': str(SOCKS_PORT),
-      'ExitNodes': '{ru}',
-    },
-    init_msg_handler = print_bootstrap_lines,
-  )
-
-  print term.format("\nChecking our endpoint:\n", term.Attr.BOLD)
-  print term.format(curl("http://www.atagar.com/echo.php"), term.Color.BLUE)
-
-  tor_process.kill()  # stops tor
-
-.. image:: /_static/locale_selection_output.png
-
diff --git a/docs/tutorial/tortoise_and_the_hare.rst b/docs/tutorial/tortoise_and_the_hare.rst
deleted file mode 100644
index f575853..0000000
--- a/docs/tutorial/tortoise_and_the_hare.rst
+++ /dev/null
@@ -1,204 +0,0 @@
-Tortoise and the Hare
-=====================
-
-Controllers have two methods of talking with Tor...
-
-* **Synchronous** - Most commonly you make a request to Tor then receive its
-  reply. The :func:`~stem.control.Controller.get_info` calls in the `first
-  tutorial <the_little_relay_that_could.html>`_ are an example of this.
-
-* **Asynchronous** - Controllers can subscribe to be notified when various
-  kinds of events occur within Tor (see the :data:`~stem.control.EventType`).
-  Stem's users provide a callback function to
-  :func:`~stem.control.Controller.add_event_listener` which is then notified
-  when the event occurs.
-
-Try to avoid lengthy operations within event callbacks. They're notified by a
-single dedicated event thread, and blocking this thread will prevent the
-delivery of further events.
-
-With that out of the way lets see an example. The following is a `curses
-<http://docs.python.org/2/howto/curses.html>`_ application that graphs the
-bandwidth usage of Tor...
-
-.. image:: /_static/bandwidth_graph_output.png
-
-To do this it listens to **BW events**
-(the class for which is a :class:`~stem.response.events.BandwidthEvent`). These
-are events that Tor emits each second saying the number of bytes downloaded and
-uploaded.
-
-::
-
-  import curses
-  import functools
-
-  from stem.control import EventType, Controller
-  from stem.util import str_tools
-
-  # colors that curses can handle
-
-  COLOR_LIST = {
-    "red": curses.COLOR_RED,
-    "green": curses.COLOR_GREEN,
-    "yellow": curses.COLOR_YELLOW,
-    "blue": curses.COLOR_BLUE,
-    "cyan": curses.COLOR_CYAN,
-    "magenta": curses.COLOR_MAGENTA,
-    "black": curses.COLOR_BLACK,
-    "white": curses.COLOR_WHITE,
-  }
-
-  GRAPH_WIDTH = 40
-  GRAPH_HEIGHT = 8
-
-  DOWNLOAD_COLOR = "green"
-  UPLOAD_COLOR = "blue"
-
-  def main():
-    with Controller.from_port(port = 9051) as controller:
-      controller.authenticate()
-
-      try:
-        curses.wrapper(drawBandwidthGraph, controller)
-      except KeyboardInterrupt:
-        pass  # the user hit ctrl+c
-
-  def drawBandwidthGraph(stdscr, controller):
-    window = Window(stdscr)
-
-    # (downloaded, uploaded) tuples for the last 40 seconds
-
-    bandwidth_rates = [(0, 0)] * GRAPH_WIDTH
-
-    # Making a partial that wraps the window and bandwidth_rates with a function
-    # for Tor to call when it gets a BW event.
-
-    bw_event_handler = functools.partial(_handle_bandwidth_event, window, bandwidth_rates)
-
-    # Registering this listener with Tor. Tor reports a BW event each second.
-
-    controller.add_event_listener(bw_event_handler, EventType.BW)
-
-    # Pause the main thread until the user hits any key... and no, don't you dare
-    # ask where the 'any' key is. :P
-
-    stdscr.getch()
-
-  def _handle_bandwidth_event(window, bandwidth_rates, event):
-    # callback for when tor provides us with a BW event
-
-    bandwidth_rates.insert(0, (event.read, event.written))
-    bandwidth_rates = bandwidth_rates[:GRAPH_WIDTH]  # truncate old values
-    _render_graph(window, bandwidth_rates)
-
-  def _render_graph(window, bandwidth_rates):
-    window.erase()
-
-    download_rates = [entry[0] for entry in bandwidth_rates]
-    upload_rates = [entry[1] for entry in bandwidth_rates]
-
-    # show the latest values at the top
-
-    label = "Downloaded (%s/s):" % str_tools.get_size_label(download_rates[0], 1)
-    window.addstr(0, 1, label, DOWNLOAD_COLOR, curses.A_BOLD)
-
-    label = "Uploaded (%s/s):" % str_tools.get_size_label(upload_rates[0], 1)
-    window.addstr(0, GRAPH_WIDTH + 7, label, UPLOAD_COLOR, curses.A_BOLD)
-
-    # draw the graph bounds in KB
-
-    max_download_rate = max(download_rates)
-    max_upload_rate = max(upload_rates)
-
-    window.addstr(1, 1, "%4i" % (max_download_rate / 1024), DOWNLOAD_COLOR)
-    window.addstr(GRAPH_HEIGHT, 1, "   0", DOWNLOAD_COLOR)
-
-    window.addstr(1, GRAPH_WIDTH + 7, "%4i" % (max_upload_rate / 1024), UPLOAD_COLOR)
-    window.addstr(GRAPH_HEIGHT, GRAPH_WIDTH + 7, "   0", UPLOAD_COLOR)
-
-    # draw the graph
-
-    for col in xrange(GRAPH_WIDTH):
-      col_height = GRAPH_HEIGHT * download_rates[col] / max(max_download_rate, 1)
-
-      for row in xrange(col_height):
-        window.addstr(GRAPH_HEIGHT - row, col + 6, " ", DOWNLOAD_COLOR, curses.A_STANDOUT)
-
-      col_height = GRAPH_HEIGHT * upload_rates[col] / max(max_upload_rate, 1)
-
-      for row in xrange(col_height):
-        window.addstr(GRAPH_HEIGHT - row, col + GRAPH_WIDTH + 12, " ", UPLOAD_COLOR, curses.A_STANDOUT)
-
-    window.refresh()
-
-  class Window(object):
-    """
-    Simple wrapper for the curses standard screen object.
-    """
-
-    def __init__(self, stdscr):
-      self._stdscr = stdscr
-
-      # Mappings of names to the curses color attribute. Initially these all
-      # reference black text, but if the terminal can handle color then
-      # they're set with that foreground color.
-
-      self._colors = dict([(color, 0) for color in COLOR_LIST])
-
-      # allows for background transparency
-
-      try:
-        curses.use_default_colors()
-      except curses.error:
-        pass
-
-      # makes the cursor invisible
-
-      try:
-        curses.curs_set(0)
-      except curses.error:
-        pass
-
-      # initializes colors if the terminal can handle them
-
-      try:
-        if curses.has_colors():
-          color_pair = 1
-
-          for name, foreground in COLOR_LIST.items():
-            background = -1  # allows for default (possibly transparent) background
-            curses.init_pair(color_pair, foreground, background)
-            self._colors[name] = curses.color_pair(color_pair)
-            color_pair += 1
-      except curses.error:
-        pass
-
-    def addstr(self, y, x, msg, color = None, attr = curses.A_NORMAL):
-      # Curses throws an error if we try to draw a message that spans out of the
-      # window's bounds (... seriously?), so doing our best to avoid that.
-
-      if color is not None:
-        if color not in self._colors:
-          recognized_colors = ", ".join(self._colors.keys())
-          raise ValueError("The '%s' color isn't recognized: %s" % (color, recognized_colors))
-
-        attr |= self._colors[color]
-
-      max_y, max_x = self._stdscr.getmaxyx()
-
-      if max_x > x and max_y > y:
-        try:
-          self._stdscr.addstr(y, x, msg[:max_x - x], attr)
-        except:
-          pass  # maybe an edge case while resizing the window
-
-    def erase(self):
-      self._stdscr.erase()
-
-    def refresh(self):
-      self._stdscr.refresh()
-
-  if __name__ == '__main__':
-    main()
-
diff --git a/docs/tutorials.rst b/docs/tutorials.rst
index 646af20..8153181 100644
--- a/docs/tutorials.rst
+++ b/docs/tutorials.rst
@@ -38,51 +38,51 @@ feet wet by jumping straight in with some tutorials...
    :widths: 1 10
    :header-rows: 0
 
-   * - .. image:: /_static/section/tutorial/train.png
-          :target: tutorial/the_little_relay_that_could.html
+   * - .. image:: /_static/section/tutorials/train.png
+          :target: tutorials/the_little_relay_that_could.html
 
      - .. image:: /_static/label/the_little_relay_that_could.png
-          :target: tutorial/the_little_relay_that_could.html
+          :target: tutorials/the_little_relay_that_could.html
 
        Basics for talking with Tor. This will step you through configuring Tor
        and writing your first script to talk with it.
 
-   * - .. image:: /_static/section/tutorial/soviet.png
-          :target: tutorial/to_russia_with_love.html
+   * - .. image:: /_static/section/tutorials/soviet.png
+          :target: tutorials/to_russia_with_love.html
 
      - .. image:: /_static/label/to_russia_with_love.png
-          :target: tutorial/to_russia_with_love.html
+          :target: tutorials/to_russia_with_love.html
 
        Rather than talking to Tor, we'll now talk **through** it. In this
        tutorial we'll programmatically start Tor then use it to read a site
        through mother Russia!
 
-   * - .. image:: /_static/section/tutorial/tortoise.png
-          :target: tutorial/tortoise_and_the_hare.html
+   * - .. image:: /_static/section/tutorials/tortoise.png
+          :target: tutorials/tortoise_and_the_hare.html
 
      - .. image:: /_static/label/tortoise_and_the_hare.png
-          :target: tutorial/tortoise_and_the_hare.html
+          :target: tutorials/tortoise_and_the_hare.html
 
        As Tor runs it generates a variety of **events** that controllers can
        subscribe to be notified of. In this tutorial we'll do just that,
        writing a curses application that graphs the bandwidth usage of Tor.
 
-   * - .. image:: /_static/section/tutorial/mirror.png
-          :target: tutorial/mirror_mirror_on_the_wall.html
+   * - .. image:: /_static/section/tutorials/mirror.png
+          :target: tutorials/mirror_mirror_on_the_wall.html
 
      - .. image:: /_static/label/mirror_mirror_on_the_wall.png
-          :target: tutorial/mirror_mirror_on_the_wall.html
+          :target: tutorials/mirror_mirror_on_the_wall.html
 
        Getting and acting upon information about relays in the Tor network.
        Relay information is provided through documents called **descriptors**.
        This walks you through both where to get them and a small script to tell
        you the fastest Tor exits.
 
-   * - .. image:: /_static/section/tutorial/cauldron.png
-          :target: tutorial/double_double_toil_and_trouble.html
+   * - .. image:: /_static/section/tutorials/cauldron.png
+          :target: tutorials/double_double_toil_and_trouble.html
 
      - .. image:: /_static/label/double_double_toil_and_trouble.png
-          :target: tutorial/double_double_toil_and_trouble.html
+          :target: tutorials/double_double_toil_and_trouble.html
 
        Sometimes it's easiest to learn a library by seeing how it's used in the
        wild. This is a directory of scripts and applications that use stem.
diff --git a/docs/tutorials/double_double_toil_and_trouble.rst b/docs/tutorials/double_double_toil_and_trouble.rst
new file mode 100644
index 0000000..129a302
--- /dev/null
+++ b/docs/tutorials/double_double_toil_and_trouble.rst
@@ -0,0 +1,26 @@
+Double Double Toil and Trouble
+==============================
+
+Below is a listing of scripts and applications that use stem. If you have
+something you would like to have included on this page then `let me know
+<http://www.atagar.com/contact/>`_!
+
+.. list-table::
+   :widths: 1 10
+   :header-rows: 0
+
+   * - .. image:: /_static/arm.png
+          :target: http://www.atagar.com/arm/
+
+     - .. image:: /_static/label/arm.png
+          :target: http://www.atagar.com/arm/
+
+       Terminal status monitor for Tor. This provides a top like interface
+       including system resource usage, connection information, and much more.
+
+=========================================================================================================== ==========
+`Consensus Tracker <https://gitweb.torproject.org/atagar/tor-utils.git/blob/HEAD:/consensusTracker.py>`_    Script that performs an hourly check for the number of relays within the Tor network, looking for large jumps that may indicate a sybil attack.
+`Metrics Tasks <https://gitweb.torproject.org/metrics-tasks.git/tree>`_                                     One-off tasks related to Tor metrics. These mostly involve using descriptor information to answer a particular question. Tasks that involve stem are: `1854 <https://gitweb.torproject.org/metrics-tasks.git/blob/HEAD:/task-1854/pylinf.py>`_, `6232 <https://gitweb.torproject.org/metrics-tasks.git/blob/HEAD:/task-6232/pyentropy.py>`_, and `7241 <https://gitweb.torproject.org/metrics-tasks.git/blob/HEAD:/task-7241/first_pass.py>`_.
+`check_tor <http://anonscm.debian.org/gitweb/?p=users/lunar/check_tor.git;a=blob;f=check_tor.py;hb=HEAD>`_  Nagios check to verify that a relay is participating in the Tor network.
+=========================================================================================================== ==========
+
diff --git a/docs/tutorials/mirror_mirror_on_the_wall.rst b/docs/tutorials/mirror_mirror_on_the_wall.rst
new file mode 100644
index 0000000..1a7f81d
--- /dev/null
+++ b/docs/tutorials/mirror_mirror_on_the_wall.rst
@@ -0,0 +1,178 @@
+Mirror Mirror on the Wall
+=========================
+
+* :ref:`what-is-a-descriptor`
+* :ref:`where-can-i-get-the-current-descriptors`
+* :ref:`where-can-i-get-past-descriptors`
+* :ref:`putting-it-together`
+
+.. _what-is-a-descriptor:
+
+What is a descriptor?
+---------------------
+
+Tor is made up of two parts: the application and a distributed network of a few
+thousand volunteer relays. Information about these relays is public, and made
+up of documents called **descriptors**.
+
+There are several different kinds of descriptors, the most common ones being...
+
+====================================================================== ===========
+Descriptor Type                                                        Description
+====================================================================== ===========
+`Server Descriptor <../api/descriptor/server_descriptor.html>`_        Information that relays publish about themselves. Tor clients once downloaded this information, but now they use microdescriptors instead.
+`ExtraInfo Descriptor <../api/descriptor/extrainfo_descriptor.html>`_  Relay information that tor clients do not need in order to function. This is self-published, like server descriptors, but not downloaded by default.
+`Microdescriptor <../api/descriptor/microdescriptor.html>`_            Minimalistic document that just includes the information necessary for tor clients to work.
+`Network Status Document <../api/descriptor/networkstatus.html>`_      Though tor relays are decentralized, the directories that track the overall network are not. These central points are called **directory authorities**, and every hour they publish a document called a **consensus** (aka, network status document). The consensus in turn is made up of **router status entries**.
+`Router Status Entry <../api/descriptor/router_status_entry.html>`_    Relay information provided by the directory authorities including flags, heuristics used for relay selection, etc.
+====================================================================== ===========
+
+.. _where-can-i-get-the-current-descriptors:
+
+Where can I get the current descriptors?
+----------------------------------------
+
+To work tor needs to have up-to-date information about relays within the
+network. As such getting current descriptors is easy: *just run tor*.
+
+Tor only gets the descriptors that it needs by default, so if you're scripting
+against tor you may want to set some of the following in your `torrc
+<https://www.torproject.org/docs/faq.html.en#torrc>`_. Keep in mind that these
+add a small burden to the network, so don't set them in a widely distributed
+application. And, of course, please consider `running tor as a relay
+<https://www.torproject.org/docs/tor-doc-relay.html.en>`_ so you give back to
+the network!
+
+::
+
+  # Descriptors have a range of time during which they're valid. To get the
+  # most recent descriptor information, regardless of if tor needs it or not,
+  # set the following.
+
+  FetchDirInfoExtraEarly 1
+
+  # If you aren't actively using tor as a client then tor will eventually stop
+  # downloading descriptor information that it doesn't need. To prevent this
+  # from happening set...
+
+  FetchUselessDescriptors 1
+
+  # Tor no longer downloads server descriptors by default, opting for
+  # microdescriptors instead. If you want tor to download server descriptors
+  # then set...
+
+  UseMicrodescriptors 0
+
+  # Tor doesn't need extrainfo descriptors to work. If you want tor to download
+  # them anyway then set...
+
+  DownloadExtraInfo 1
+
+Now that tor is happy chugging along up-to-date descriptors are available
+through tor's control socket...
+
+::
+
+  from stem.control import Controller
+
+  with Controller.from_port(port = 9051) as controller:
+    controller.authenticate()
+
+    for desc in controller.get_network_statuses():
+      print "found relay %s (%s)" % (desc.nickname, desc.fingerprint)
+
+... or by reading directly from tor's data directory...
+
+::
+
+  from stem.descriptor import parse_file
+
+  for desc in parse_file(open("/home/atagar/.tor/cached-consensus")):
+    print "found relay %s (%s)" % (desc.nickname, desc.fingerprint)
+
+.. _where-can-i-get-past-descriptors:
+
+Where can I get past descriptors?
+---------------------------------
+
+Descriptor archives are available on `Tor's metrics site
+<https://metrics.torproject.org/data.html>`_. These archives can be read with
+the `DescriptorReader <api/descriptor/reader.html>`_...
+
+::
+
+  from stem.descriptor.reader import DescriptorReader
+
+  with DescriptorReader(["/home/atagar/server-descriptors-2013-03.tar"]) as reader:
+    for desc in reader:
+      print "found relay %s (%s)" % (desc.nickname, desc.fingerprint)
+
+.. _putting-it-together:
+
+Putting it together...
+----------------------
+
+As discussed above there are three methods for reading descriptors...
+
+* With the :class:`~stem.control.Controller` via methods like :func:`~stem.control.Controller.get_server_descriptors` and :func:`~stem.control.Controller.get_network_statuses`.
+* By reading the file directly with :func:`~stem.descriptor.__init__.parse_file`.
+* Reading with the `DescriptorReader <api/descriptor/reader.html>`_. This is best if you have you want to read everything from a directory or archive.
+
+Now lets say you want to figure out who the *biggest* exit relays are. You
+could use any of the methods above, but for this example we'll use the
+:class:`~stem.control.Controller`. This uses server descriptors, so keep in
+mind that you'll likely need to set "UseMicrodescriptors 0" in your torrc for
+this to work.
+
+::
+
+  import sys
+
+  from stem.control import Controller
+  from stem.util import str_tools
+
+  # provides a mapping of observed bandwidth to the relay nicknames
+  def get_bw_to_relay():
+    bw_to_relay = {}
+
+    with Controller.from_port(port = 9051) as controller:
+      controller.authenticate()
+
+      for desc in controller.get_server_descriptors():
+        if desc.exit_policy.is_exiting_allowed():
+          bw_to_relay.setdefault(desc.observed_bandwidth, []).append(desc.nickname)
+
+    return bw_to_relay
+
+  # prints the top fifteen relays
+
+  bw_to_relay = get_bw_to_relay()
+  count = 1
+
+  for bw_value in sorted(bw_to_relay.keys(), reverse = True):
+    for nickname in bw_to_relay[bw_value]:
+      print "%i. %s (%s/s)" % (count, nickname, str_tools.get_size_label(bw_value, 2))
+      count += 1
+
+      if count > 15:
+        sys.exit()
+
+::
+
+  % python example.py
+  1. herngaard (40.95 MB/s)
+  2. chaoscomputerclub19 (40.43 MB/s)
+  3. chaoscomputerclub18 (40.02 MB/s)
+  4. chaoscomputerclub20 (38.98 MB/s)
+  5. wannabe (38.63 MB/s)
+  6. dorrisdeebrown (38.48 MB/s)
+  7. manning2 (38.20 MB/s)
+  8. chaoscomputerclub21 (36.90 MB/s)
+  9. TorLand1 (36.22 MB/s)
+  10. bolobolo1 (35.93 MB/s)
+  11. manning1 (35.39 MB/s)
+  12. gorz (34.10 MB/s)
+  13. ndnr1 (25.36 MB/s)
+  14. politkovskaja2 (24.93 MB/s)
+  15. wau (24.72 MB/s)
+
diff --git a/docs/tutorials/the_little_relay_that_could.rst b/docs/tutorials/the_little_relay_that_could.rst
new file mode 100644
index 0000000..4f22b5b
--- /dev/null
+++ b/docs/tutorials/the_little_relay_that_could.rst
@@ -0,0 +1,65 @@
+The Little Relay that Could
+===========================
+
+Let's say you just set up your very first `Tor relay
+<https://www.torproject.org/docs/tor-doc-relay.html.en>`_ (thank you!), and now
+you want to write a script that tells you how much it is being used.
+
+First, for any script to talk with your relay it will need to have a control
+port available. This is a port that's usually only available on localhost and
+protected by either a **password** or **authentication cookie**.
+
+Look at your `torrc <https://www.torproject.org/docs/faq.html.en#torrc>`_ for
+the following configuration options...
+
+::
+
+  # This provides a port for our script to talk with. If you set this then be
+  # sure to also set either CookieAuthentication *or* HashedControlPassword!
+  #
+  # You could also use ControlSocket instead of ControlPort, which provides a
+  # file based socket. You don't need to have authentication if you use
+  # ControlSocket. For this example however we'll use a port.
+  
+  ControlPort 9051
+  
+  # Setting this will make Tor write an authentication cookie. Anything with
+  # permission to read this file can connect to Tor. If you're going to run
+  # your script with the same user or permission group as Tor then this is the
+  # easiest method of authentication to use.
+  
+  CookieAuthentication 1
+  
+  # Alternatively we can authenticate with a password. To set a password first
+  # get its hash...
+  #
+  # % tor --hash-password "my_password"
+  # 16:E600ADC1B52C80BB6022A0E999A7734571A451EB6AE50FED489B72E3DF
+  #
+  # ... and use that for the HashedControlPassword in your torrc.
+  
+  HashedControlPassword 16:E600ADC1B52C80BB6022A0E999A7734571A451EB6AE50FED489B72E3DF
+
+When you change your torrc you'll need to either restart Tor is issue a SIGHUP
+for the new settings to take effect. Now let's write a script that tells us how
+many bytes Tor has sent and received...
+
+::
+
+  from stem.control import Controller
+  
+  with Controller.from_port(port = 9051) as controller:
+    controller.authenticate()  # provide the password here if you set one
+
+    bytes_read = controller.get_info("traffic/read")
+    bytes_written = controller.get_info("traffic/written")
+
+    print "My Tor relay has read %s bytes and written %s." % (bytes_read, bytes_written)
+
+::
+
+  % python example.py 
+  My Tor relay has read 33406 bytes and written 29649.
+
+Congratulations! You've just written your first controller script.
+
diff --git a/docs/tutorials/to_russia_with_love.rst b/docs/tutorials/to_russia_with_love.rst
new file mode 100644
index 0000000..7fb7c66
--- /dev/null
+++ b/docs/tutorials/to_russia_with_love.rst
@@ -0,0 +1,70 @@
+To Russia With Love
+===================
+
+Say it's 1982, the height of the Cold War, and you're a journalist doing a piece on how the Internet looks from behind the Iron Curtain. Ignoring the minor detail that the Internet doesn't yet exist, we'll walk you through how you could do it - no passport required!
+
+The Internet isn't uniform. Localization, censorship, and selective service based on your IP's geographic location can make the Internet a very different place depending on where you're coming from.
+
+Tor relays are scattered all over the world and, as such, you can pretend to be from any place running an exit. This can be especially useful to evade pesky geolocational restrictions, such as news sites that refuse to work while you're traveling abroad.
+
+Tor makes `configuring your exit locale <https://www.torproject.org/docs/faq.html.en#ChooseEntryExit>`_ easy through the **ExitNodes** torrc option. Note that you don't need a control port (or even stem) to do this, though they can be useful if you later want to do something more elaborate.
+
+In the following example we're using stem to `start Tor <../api/process.html>`_, then reading a site through it with `PycURL <http://pycurl.sourceforge.net/>`_. This is not always reliable (some relays are lemons) so you may need to run this more than once.
+
+**Do not rely on the following not to leak.** Though it seems to work, DNS resolution and other edge cases might expose your real IP. If you have a suggestion for how to improve this example then please `let me know <http://www.atagar.com/contact/>`_!
+
+::
+
+  import StringIO
+
+  import pycurl
+  import stem.process
+
+  from stem.util import term
+
+  SOCKS_PORT = 7000
+
+  def curl(url):
+    """ 
+    Uses pycurl to fetch a site using the proxy on the SOCKS_PORT.
+    """
+
+    output = StringIO.StringIO()
+
+    query = pycurl.Curl()
+    query.setopt(pycurl.URL, url)
+    query.setopt(pycurl.PROXY, 'localhost')
+    query.setopt(pycurl.PROXYPORT, SOCKS_PORT)
+    query.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5)
+    query.setopt(pycurl.WRITEFUNCTION, output.write)
+
+    try:
+      query.perform()
+      return output.getvalue()
+    except pycurl.error, exc:
+      return "Unable to reach %s (%s)" % (url, exc)
+
+  # Start an instance of tor configured to only exit through Russia. This prints
+  # tor's bootstrap information as it starts.
+
+  def print_bootstrap_lines(line):
+    if "Bootstrapped " in line:
+      print term.format(line, term.Color.BLUE)
+
+  print term.format("Starting Tor:\n", term.Attr.BOLD)
+
+  tor_process = stem.process.launch_tor_with_config(
+    config = {
+      'SocksPort': str(SOCKS_PORT),
+      'ExitNodes': '{ru}',
+    },
+    init_msg_handler = print_bootstrap_lines,
+  )
+
+  print term.format("\nChecking our endpoint:\n", term.Attr.BOLD)
+  print term.format(curl("http://www.atagar.com/echo.php"), term.Color.BLUE)
+
+  tor_process.kill()  # stops tor
+
+.. image:: /_static/locale_selection_output.png
+
diff --git a/docs/tutorials/tortoise_and_the_hare.rst b/docs/tutorials/tortoise_and_the_hare.rst
new file mode 100644
index 0000000..f575853
--- /dev/null
+++ b/docs/tutorials/tortoise_and_the_hare.rst
@@ -0,0 +1,204 @@
+Tortoise and the Hare
+=====================
+
+Controllers have two methods of talking with Tor...
+
+* **Synchronous** - Most commonly you make a request to Tor then receive its
+  reply. The :func:`~stem.control.Controller.get_info` calls in the `first
+  tutorial <the_little_relay_that_could.html>`_ are an example of this.
+
+* **Asynchronous** - Controllers can subscribe to be notified when various
+  kinds of events occur within Tor (see the :data:`~stem.control.EventType`).
+  Stem's users provide a callback function to
+  :func:`~stem.control.Controller.add_event_listener` which is then notified
+  when the event occurs.
+
+Try to avoid lengthy operations within event callbacks. They're notified by a
+single dedicated event thread, and blocking this thread will prevent the
+delivery of further events.
+
+With that out of the way lets see an example. The following is a `curses
+<http://docs.python.org/2/howto/curses.html>`_ application that graphs the
+bandwidth usage of Tor...
+
+.. image:: /_static/bandwidth_graph_output.png
+
+To do this it listens to **BW events**
+(the class for which is a :class:`~stem.response.events.BandwidthEvent`). These
+are events that Tor emits each second saying the number of bytes downloaded and
+uploaded.
+
+::
+
+  import curses
+  import functools
+
+  from stem.control import EventType, Controller
+  from stem.util import str_tools
+
+  # colors that curses can handle
+
+  COLOR_LIST = {
+    "red": curses.COLOR_RED,
+    "green": curses.COLOR_GREEN,
+    "yellow": curses.COLOR_YELLOW,
+    "blue": curses.COLOR_BLUE,
+    "cyan": curses.COLOR_CYAN,
+    "magenta": curses.COLOR_MAGENTA,
+    "black": curses.COLOR_BLACK,
+    "white": curses.COLOR_WHITE,
+  }
+
+  GRAPH_WIDTH = 40
+  GRAPH_HEIGHT = 8
+
+  DOWNLOAD_COLOR = "green"
+  UPLOAD_COLOR = "blue"
+
+  def main():
+    with Controller.from_port(port = 9051) as controller:
+      controller.authenticate()
+
+      try:
+        curses.wrapper(drawBandwidthGraph, controller)
+      except KeyboardInterrupt:
+        pass  # the user hit ctrl+c
+
+  def drawBandwidthGraph(stdscr, controller):
+    window = Window(stdscr)
+
+    # (downloaded, uploaded) tuples for the last 40 seconds
+
+    bandwidth_rates = [(0, 0)] * GRAPH_WIDTH
+
+    # Making a partial that wraps the window and bandwidth_rates with a function
+    # for Tor to call when it gets a BW event.
+
+    bw_event_handler = functools.partial(_handle_bandwidth_event, window, bandwidth_rates)
+
+    # Registering this listener with Tor. Tor reports a BW event each second.
+
+    controller.add_event_listener(bw_event_handler, EventType.BW)
+
+    # Pause the main thread until the user hits any key... and no, don't you dare
+    # ask where the 'any' key is. :P
+
+    stdscr.getch()
+
+  def _handle_bandwidth_event(window, bandwidth_rates, event):
+    # callback for when tor provides us with a BW event
+
+    bandwidth_rates.insert(0, (event.read, event.written))
+    bandwidth_rates = bandwidth_rates[:GRAPH_WIDTH]  # truncate old values
+    _render_graph(window, bandwidth_rates)
+
+  def _render_graph(window, bandwidth_rates):
+    window.erase()
+
+    download_rates = [entry[0] for entry in bandwidth_rates]
+    upload_rates = [entry[1] for entry in bandwidth_rates]
+
+    # show the latest values at the top
+
+    label = "Downloaded (%s/s):" % str_tools.get_size_label(download_rates[0], 1)
+    window.addstr(0, 1, label, DOWNLOAD_COLOR, curses.A_BOLD)
+
+    label = "Uploaded (%s/s):" % str_tools.get_size_label(upload_rates[0], 1)
+    window.addstr(0, GRAPH_WIDTH + 7, label, UPLOAD_COLOR, curses.A_BOLD)
+
+    # draw the graph bounds in KB
+
+    max_download_rate = max(download_rates)
+    max_upload_rate = max(upload_rates)
+
+    window.addstr(1, 1, "%4i" % (max_download_rate / 1024), DOWNLOAD_COLOR)
+    window.addstr(GRAPH_HEIGHT, 1, "   0", DOWNLOAD_COLOR)
+
+    window.addstr(1, GRAPH_WIDTH + 7, "%4i" % (max_upload_rate / 1024), UPLOAD_COLOR)
+    window.addstr(GRAPH_HEIGHT, GRAPH_WIDTH + 7, "   0", UPLOAD_COLOR)
+
+    # draw the graph
+
+    for col in xrange(GRAPH_WIDTH):
+      col_height = GRAPH_HEIGHT * download_rates[col] / max(max_download_rate, 1)
+
+      for row in xrange(col_height):
+        window.addstr(GRAPH_HEIGHT - row, col + 6, " ", DOWNLOAD_COLOR, curses.A_STANDOUT)
+
+      col_height = GRAPH_HEIGHT * upload_rates[col] / max(max_upload_rate, 1)
+
+      for row in xrange(col_height):
+        window.addstr(GRAPH_HEIGHT - row, col + GRAPH_WIDTH + 12, " ", UPLOAD_COLOR, curses.A_STANDOUT)
+
+    window.refresh()
+
+  class Window(object):
+    """
+    Simple wrapper for the curses standard screen object.
+    """
+
+    def __init__(self, stdscr):
+      self._stdscr = stdscr
+
+      # Mappings of names to the curses color attribute. Initially these all
+      # reference black text, but if the terminal can handle color then
+      # they're set with that foreground color.
+
+      self._colors = dict([(color, 0) for color in COLOR_LIST])
+
+      # allows for background transparency
+
+      try:
+        curses.use_default_colors()
+      except curses.error:
+        pass
+
+      # makes the cursor invisible
+
+      try:
+        curses.curs_set(0)
+      except curses.error:
+        pass
+
+      # initializes colors if the terminal can handle them
+
+      try:
+        if curses.has_colors():
+          color_pair = 1
+
+          for name, foreground in COLOR_LIST.items():
+            background = -1  # allows for default (possibly transparent) background
+            curses.init_pair(color_pair, foreground, background)
+            self._colors[name] = curses.color_pair(color_pair)
+            color_pair += 1
+      except curses.error:
+        pass
+
+    def addstr(self, y, x, msg, color = None, attr = curses.A_NORMAL):
+      # Curses throws an error if we try to draw a message that spans out of the
+      # window's bounds (... seriously?), so doing our best to avoid that.
+
+      if color is not None:
+        if color not in self._colors:
+          recognized_colors = ", ".join(self._colors.keys())
+          raise ValueError("The '%s' color isn't recognized: %s" % (color, recognized_colors))
+
+        attr |= self._colors[color]
+
+      max_y, max_x = self._stdscr.getmaxyx()
+
+      if max_x > x and max_y > y:
+        try:
+          self._stdscr.addstr(y, x, msg[:max_x - x], attr)
+        except:
+          pass  # maybe an edge case while resizing the window
+
+    def erase(self):
+      self._stdscr.erase()
+
+    def refresh(self):
+      self._stdscr.refresh()
+
+  if __name__ == '__main__':
+    main()
+



More information about the tor-commits mailing list