[stem/master] Added downloadable tutorial for tutorial Tortoise and the Hare

commit a20733062c11b5d68613ec0c17518fb16229bd0f Author: Sambuddha Basu <sambuddhabasu1@gmail.com> Date: Mon May 25 07:34:11 2015 +0400 Added downloadable tutorial for tutorial Tortoise and the Hare --- docs/_static/example/event_listening.py | 177 ++++++++++++++++++++++++++++ docs/tutorials/tortoise_and_the_hare.rst | 184 +----------------------------- 2 files changed, 181 insertions(+), 180 deletions(-) diff --git a/docs/_static/example/event_listening.py b/docs/_static/example/event_listening.py new file mode 100644 index 0000000..2e6f026 --- /dev/null +++ b/docs/_static/example/event_listening.py @@ -0,0 +1,177 @@ +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: + # This makes curses initialize and call draw_bandwidth_graph() with a + # reference to the screen, followed by additional arguments (in this + # case just the controller). + + curses.wrapper(draw_bandwidth_graph, controller) + except KeyboardInterrupt: + pass # the user hit ctrl+c + +def draw_bandwidth_graph(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. This causes the 'window' and + # 'bandwidth_rates' to be provided as the first two arguments whenever + # 'bw_event_handler()' is called. + + 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.size_label(download_rates[0], 1) + window.addstr(0, 1, label, DOWNLOAD_COLOR, curses.A_BOLD) + + label = "Uploaded (%s/s):" % str_tools.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/tortoise_and_the_hare.rst b/docs/tutorials/tortoise_and_the_hare.rst index d3f759e..413e149 100644 --- a/docs/tutorials/tortoise_and_the_hare.rst +++ b/docs/tutorials/tortoise_and_the_hare.rst @@ -28,184 +28,8 @@ To do this it listens to **BW events** are events that Tor emits each second saying the number of bytes downloaded and uploaded. -.. code-block:: python - :emphasize-lines: 53-55,62-67 - - 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: - # This makes curses initialize and call draw_bandwidth_graph() with a - # reference to the screen, followed by additional arguments (in this - # case just the controller). - - curses.wrapper(draw_bandwidth_graph, controller) - except KeyboardInterrupt: - pass # the user hit ctrl+c - - def draw_bandwidth_graph(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. This causes the 'window' and - # 'bandwidth_rates' to be provided as the first two arguments whenever - # 'bw_event_handler()' is called. - - 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.size_label(download_rates[0], 1) - window.addstr(0, 1, label, DOWNLOAD_COLOR, curses.A_BOLD) - - label = "Uploaded (%s/s):" % str_tools.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() +.. literalinclude:: /_static/example/event_listening.py + :caption: `[Download] <../_static/example/event_listening.py>`__ + :emphasize-lines: 53-55,62-67 + :language: python
participants (1)
-
atagar@torproject.org