From 81b50a22f45f7d28dc448e92aa6459992c1d9bb3 Mon Sep 17 00:00:00 2001
From: Matt Traudt <sirmatt@ksu.edu>
Date: Sat, 24 Mar 2018 08:28:33 -0400
Subject: [PATCH] Turn into a package

Hopefully everything works first try
---
 .gitignore                            |   1 +
 requirements.txt                      |   2 -
 {lib => sbws}/__init__.py             |   0
 sbws/__main__.py                      |  35 +++++++++
 {util => sbws/commands}/__init__.py   |   0
 scanner.py => sbws/commands/client.py | 100 +++++++++++++-------------
 server.py => sbws/commands/server.py  |  48 ++++++-------
 sbws/lib/__init__.py                  |   0
 {lib => sbws/lib}/circuitbuilder.py   |   4 +-
 {lib => sbws/lib}/pastlylogger.py     |   0
 {lib => sbws/lib}/periodicevent.py    |   0
 {lib => sbws/lib}/relaylist.py        |   2 +-
 {lib => sbws/lib}/resultdump.py       |   0
 sbws/util/__init__.py                 |   0
 {util => sbws/util}/simpleauth.py     |   0
 {util => sbws/util}/stem.py           |   0
 setup.py                              |  72 +++++++++++++++++++
 17 files changed, 184 insertions(+), 80 deletions(-)
 delete mode 100644 requirements.txt
 rename {lib => sbws}/__init__.py (100%)
 create mode 100644 sbws/__main__.py
 rename {util => sbws/commands}/__init__.py (100%)
 rename scanner.py => sbws/commands/client.py (79%)
 mode change 100755 => 100644
 rename server.py => sbws/commands/server.py (79%)
 mode change 100755 => 100644
 create mode 100644 sbws/lib/__init__.py
 rename {lib => sbws/lib}/circuitbuilder.py (99%)
 rename {lib => sbws/lib}/pastlylogger.py (100%)
 rename {lib => sbws/lib}/periodicevent.py (100%)
 rename {lib => sbws/lib}/relaylist.py (98%)
 rename {lib => sbws/lib}/resultdump.py (100%)
 create mode 100644 sbws/util/__init__.py
 rename {util => sbws/util}/simpleauth.py (100%)
 rename {util => sbws/util}/stem.py (100%)
 create mode 100755 setup.py

diff --git a/.gitignore b/.gitignore
index bbfe4321..8d245e55 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 __pycache__
 venv
 passwords.txt
+*.egg-info
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 1b85859a..00000000
--- a/requirements.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-PySocks==1.6.8
-stem==1.6.0
diff --git a/lib/__init__.py b/sbws/__init__.py
similarity index 100%
rename from lib/__init__.py
rename to sbws/__init__.py
diff --git a/sbws/__main__.py b/sbws/__main__.py
new file mode 100644
index 00000000..6a081fb8
--- /dev/null
+++ b/sbws/__main__.py
@@ -0,0 +1,35 @@
+import sbws.commands.client
+import sbws.commands.server
+from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
+
+
+VERSION = '0.0.1'
+
+
+def create_parser():
+    p = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
+    sub = p.add_subparsers(dest='command')
+    sbws.commands.client.gen_parser(sub)
+    sbws.commands.server.gen_parser(sub)
+    return p
+
+
+def main():
+    parser = create_parser()
+    args = parser.parse_args()
+    def_args = [args]
+    def_kwargs = {}
+    known_commands = {
+        'client': {'f': sbws.commands.client.main,
+                   'a': def_args, 'kw': def_kwargs},
+        'server': {'f': sbws.commands.server.main,
+                   'a': def_args, 'kw': def_kwargs},
+    }
+    try:
+        if args.command not in known_commands:
+            parser.print_help()
+        else:
+            comm = known_commands[args.command]
+            exit(comm['f'](*comm['a'], **comm['kw']))
+    except KeyboardInterrupt:
+        print('')
diff --git a/util/__init__.py b/sbws/commands/__init__.py
similarity index 100%
rename from util/__init__.py
rename to sbws/commands/__init__.py
diff --git a/scanner.py b/sbws/commands/client.py
old mode 100755
new mode 100644
similarity index 79%
rename from scanner.py
rename to sbws/commands/client.py
index 9734148d..0480b2dd
--- a/scanner.py
+++ b/sbws/commands/client.py
@@ -1,27 +1,26 @@
-#!/usr/bin/env python3
-from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser
-import time
-import socks  # PySocks
-import socket
-import random
+from ..lib.pastlylogger import PastlyLogger
+from ..lib.circuitbuilder import GapsCircuitBuilder as CB
+from ..lib.resultdump import ResultDump
+from ..lib.resultdump import Result
+from ..lib.relaylist import RelayList
+from ..util.simpleauth import is_good_clientside_password_file
+from ..util.simpleauth import authenticate_to_server
+import sbws.util.stem as stem_utils
 from stem.control import EventType
+from argparse import ArgumentDefaultsHelpFormatter
+from multiprocessing.dummy import Pool
 from threading import Event
 from threading import RLock
-from multiprocessing.dummy import Pool
-import util.stem as stem_utils
-from util.simpleauth import is_good_clientside_password_file
-from util.simpleauth import authenticate_to_server
-from lib.circuitbuilder import GapsCircuitBuilder as CB
-from lib.resultdump import ResultDump
-from lib.resultdump import Result
-from lib.relaylist import RelayList
-from lib.pastlylogger import PastlyLogger
+import random
+import socks
+import socket
+import time
+
 
+log = None
 end_event = Event()
 stream_building_lock = RLock()
-log = PastlyLogger(debug='/dev/stdout', overwrite=['debug'], log_threads=True)
 
-# maximum we want to read per read() call
 MAX_RECV_PER_READ = 1*1024*1024
 DOWNLOAD_TIMES = {'toofast': 1, 'min': 5, 'target': 6, 'max': 10}
 DESIRED_RESULTS = 5
@@ -227,7 +226,7 @@ def test_speedtest(args):
     pending_results = []
     relays = rl.relays
     random.shuffle(relays)
-    for target in relays:
+    for target in relays[0:2]:
         callback = result_putter(rd)
         callback_err = result_putter_error(target)
         async_result = pool.apply_async(
@@ -243,37 +242,38 @@ def test_speedtest(args):
     log.notice('Got all results')
 
 
+def gen_parser(sub):
+    p = sub.add_parser('client',
+                       formatter_class=ArgumentDefaultsHelpFormatter)
+    p.add_argument('--control', nargs=2, metavar=('TYPE', 'LOCATION'),
+                   default=['port', '9051'],
+                   help='How to control Tor. Examples: "port 9051" or '
+                   '"socket /var/lib/tor/control"')
+    p.add_argument('--socks-host', default='127.0.0.1', type=str,
+                   help='Host for a local Tor SocksPort')
+    p.add_argument('--socks-port', default=9050, type=int,
+                   help='Port for a local Tor SocksPort')
+    p.add_argument('--server-host', default='127.0.0.1', type=str,
+                   help='Host for a measurement server')
+    p.add_argument('--server-port', default=4444, type=int,
+                   help='Port for a measurement server')
+    p.add_argument('--result-directory', default='dd', type=str,
+                   help='Where to store raw result output')
+    p.add_argument('--threads', default=1, type=int,
+                   help='Number of measurements to make in parallel')
+    p.add_argument('--helper-relay', type=str, required=True,
+                   help='Relay to which to build circuits and is running '
+                   'the server.py')
+    p.add_argument('--password-file', type=str, default='passwords.txt',
+                   help='Read the first line and use it as the password '
+                   'when authenticating to the server.')
+
+
 def main(args):
-    test_speedtest(args)
-
-
-if __name__ == '__main__':
-    parser = ArgumentParser(
-            formatter_class=ArgumentDefaultsHelpFormatter)
-    parser.add_argument('--control', nargs=2, metavar=('TYPE', 'LOCATION'),
-                        default=['port', '9051'],
-                        help='How to control Tor. Examples: "port 9051" or '
-                        '"socket /var/lib/tor/control"')
-    parser.add_argument('--socks-host', default='127.0.0.1', type=str,
-                        help='Host for a local Tor SocksPort')
-    parser.add_argument('--socks-port', default=9050, type=int,
-                        help='Port for a local Tor SocksPort')
-    parser.add_argument('--server-host', default='127.0.0.1', type=str,
-                        help='Host for a measurement server')
-    parser.add_argument('--server-port', default=4444, type=int,
-                        help='Port for a measurement server')
-    parser.add_argument('--result-directory', default='dd', type=str,
-                        help='Where to store raw result output')
-    parser.add_argument('--threads', default=1, type=int,
-                        help='Number of measurements to make in parallel')
-    parser.add_argument('--helper-relay', type=str, required=True,
-                        help='Relay to which to build circuits and is running '
-                        'the server.py')
-    parser.add_argument('--password-file', type=str, default='passwords.txt',
-                        help='Read the first line and use it as the password '
-                        'when authenticating to the server.')
-
-    args = parser.parse_args()
+    global log
+    log = PastlyLogger(debug='/dev/stdout', overwrite=['debug'],
+                       log_threads=True)
+
     if args.threads < 1:
         fail_hard('--threads must be larger than 1')
 
@@ -289,10 +289,8 @@ if __name__ == '__main__':
         fail_hard(error_reason)
 
     try:
-        main(args)
+        test_speedtest(args)
     except KeyboardInterrupt as e:
         raise e
     finally:
         end_event.set()
-
-# pylama:ignore=E265
diff --git a/server.py b/sbws/commands/server.py
old mode 100755
new mode 100644
similarity index 79%
rename from server.py
rename to sbws/commands/server.py
index 69fab361..7405731c
--- a/server.py
+++ b/sbws/commands/server.py
@@ -1,18 +1,28 @@
-#!/usr/bin/env python3
-from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser
+from ..lib.pastlylogger import PastlyLogger
+from ..util.simpleauth import authenticate_client
+from ..util.simpleauth import is_good_serverside_password_file
+from argparse import ArgumentDefaultsHelpFormatter
+from threading import Thread
 import socket
 import time
-from threading import Thread
-from lib.pastlylogger import PastlyLogger
-from util.simpleauth import authenticate_client
-from util.simpleauth import is_good_serverside_password_file
 
-log = PastlyLogger(debug='/dev/stdout', overwrite=['debug'], log_threads=True)
+
+log = None
 
 MAX_SEND_PER_WRITE = 100*1024*1024
 MAX_SEND_PER_WRITE = 4096
 
 
+def gen_parser(sub):
+    p = sub.add_parser('server',
+                       formatter_class=ArgumentDefaultsHelpFormatter)
+    p.add_argument('bind_ip', type=str, default='127.0.0.1')
+    p.add_argument('bind_port', type=int, default=4444)
+    p.add_argument('--password-file', type=str, default='passwords.txt',
+                   help='All lines in this file will be considered '
+                   'valid passwords scanners may use to authenticate.')
+
+
 def fail_hard(*s):
     ''' Optionally log something to stdout ... and then exit as fast as
     possible '''
@@ -95,6 +105,13 @@ def new_thread(args, sock):
 
 
 def main(args):
+    global log
+    log = PastlyLogger(debug='/dev/stdout', overwrite=['debug'],
+                       log_threads=True)
+    valid, error_reason = is_good_serverside_password_file(args.password_file)
+    if not valid:
+        fail_hard(error_reason)
+
     server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     h = (args.bind_ip, args.bind_port)
     log.notice('binding to', h)
@@ -118,20 +135,3 @@ def main(args):
         pass
     finally:
         close_socket(server)
-
-
-if __name__ == '__main__':
-    parser = ArgumentParser(
-            formatter_class=ArgumentDefaultsHelpFormatter)
-    parser.add_argument('bind_ip', type=str, default='127.0.0.1')
-    parser.add_argument('bind_port', type=int, default=4444)
-    parser.add_argument('--password-file', type=str, default='passwords.txt',
-                        help='All lines in this file will be considered '
-                        'valid passwords scanners may use to authenticate.')
-    args = parser.parse_args()
-
-    valid, error_reason = is_good_serverside_password_file(args.password_file)
-    if not valid:
-        fail_hard(error_reason)
-
-    main(args)
diff --git a/sbws/lib/__init__.py b/sbws/lib/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/lib/circuitbuilder.py b/sbws/lib/circuitbuilder.py
similarity index 99%
rename from lib/circuitbuilder.py
rename to sbws/lib/circuitbuilder.py
index a370180a..2a446c49 100644
--- a/lib/circuitbuilder.py
+++ b/sbws/lib/circuitbuilder.py
@@ -1,7 +1,7 @@
 from stem import (CircuitExtensionFailed, InvalidRequest)
 import random
-import util.stem as stem_utils
-from lib.relaylist import RelayList
+import sbws.util.stem as stem_utils
+from .relaylist import RelayList
 
 
 class PathLengthException(Exception):
diff --git a/lib/pastlylogger.py b/sbws/lib/pastlylogger.py
similarity index 100%
rename from lib/pastlylogger.py
rename to sbws/lib/pastlylogger.py
diff --git a/lib/periodicevent.py b/sbws/lib/periodicevent.py
similarity index 100%
rename from lib/periodicevent.py
rename to sbws/lib/periodicevent.py
diff --git a/lib/relaylist.py b/sbws/lib/relaylist.py
similarity index 98%
rename from lib/relaylist.py
rename to sbws/lib/relaylist.py
index 97b93f49..8b5eacf3 100644
--- a/lib/relaylist.py
+++ b/sbws/lib/relaylist.py
@@ -1,4 +1,4 @@
-import util.stem as stem_utils
+import sbws.util.stem as stem_utils
 from stem import Flag
 import time
 import random
diff --git a/lib/resultdump.py b/sbws/lib/resultdump.py
similarity index 100%
rename from lib/resultdump.py
rename to sbws/lib/resultdump.py
diff --git a/sbws/util/__init__.py b/sbws/util/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/util/simpleauth.py b/sbws/util/simpleauth.py
similarity index 100%
rename from util/simpleauth.py
rename to sbws/util/simpleauth.py
diff --git a/util/stem.py b/sbws/util/stem.py
similarity index 100%
rename from util/stem.py
rename to sbws/util/stem.py
diff --git a/setup.py b/setup.py
new file mode 100755
index 00000000..330f9332
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+# Always prefer setuptools over distutils
+from setuptools import setup, find_packages
+# To use a consistent encoding
+from codecs import open
+import os
+import re
+
+here = os.path.abspath(os.path.dirname(__file__))
+
+with open(os.path.join(here, 'README.md'), encoding='utf-8') as f:
+        long_description = f.read()
+
+
+def get_package_data():
+    # Example that grabs all *.ini files in the cwd and all files in foo/bar
+    # other_files = ['*.ini']
+    # for r, _, fs in os.walk(os.path.join(here, 'foo', 'bar')):
+    #     for f in fs:
+    #         other_files.append(os.path.join(r, f))
+    # return other_files
+    return []
+
+
+def find_version(fname):
+    with open(fname, 'rt') as fd:
+        contents = fd.read()
+        match = re.search(r"^VERSION = ['\"]([^'\"]*)['\"]", contents, re.M)
+        if match:
+            return match.group(1)
+        raise RuntimeError('Unable to find version string')
+
+
+setup(
+    name='sbws',
+    version=find_version('sbws/__main__.py'),
+    description='Simple Bandwidth Scanner',
+    long_description=long_description,
+    author='Matt Traudt',
+    author_email='pastly@torproject.org',
+    # license='MIT',
+    # https://packaging.python.org/tutorials/distributing-packages/#id48
+    # https://pypi.python.org/pypi?%3Aaction=list_classifiers
+    classifiers=[
+        # How mature is this project? Common values are
+        #   3 - Alpha
+        #   4 - Beta
+        #   5 - Production/Stable
+        'Development Status :: 3 - Alpha',
+    ],
+    packages=find_packages(),
+    # package_data={
+    #     'foo': get_package_data(),
+    # },
+    keywords='',
+    python_requires='>=3.5',
+    # test_suite='test',
+    entry_points={
+        'console_scripts': [
+            'sbws = sbws.__main__:main',
+        ]
+    },
+    install_requires=[
+        'stem',
+        'pysocks',
+    ],
+    extras_require={
+        'dev': [],
+        'test': [],
+        'doc': ['sphinx'],
+    },
+)
-- 
GitLab