From 423cd36feddb21322d8f6ca77e7b64100141a422 Mon Sep 17 00:00:00 2001 From: JayPiKay Date: Sun, 1 Sep 2019 19:09:18 +0200 Subject: [PATCH 01/10] Added version to setup and sbeam --- setup.py | 9 +++++++-- src/sbeam.py | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index a4af114..980f73c 100755 --- a/setup.py +++ b/setup.py @@ -1,10 +1,15 @@ from setuptools import setup, find_namespace_packages +from distutils.util import convert_path +main_ns = {} +ver_path = convert_path('src/sbeam.py') +with open(ver_path) as ver_file: + exec(ver_file.read(), main_ns) setup( name="sbeam", - description="", - version="0.3", + description="SuperBeam CLI Client", + version=main_ns['__VERSION__'], author="JayPiKay", author_email="jpk+python@goatpr0n.de", url='https://git.goatpr0n.de/', diff --git a/src/sbeam.py b/src/sbeam.py index 2fc3c08..c2d579c 100755 --- a/src/sbeam.py +++ b/src/sbeam.py @@ -31,7 +31,11 @@ import requests import SuperBeam +__VERSION__ = '0.4' + + @click.group() +@click.version_option(version=__VERSION__, prog_name='SuperBeam CLI') def cli(): pass From f58adf28332f0d85035e65437e45fe150a563a38 Mon Sep 17 00:00:00 2001 From: JayPiKay Date: Thu, 3 Oct 2019 09:50:35 +0200 Subject: [PATCH 02/10] Fixed Instance name --- src/SuperBeam/RequestHandlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SuperBeam/RequestHandlers.py b/src/SuperBeam/RequestHandlers.py index 7a8d346..33f2f42 100644 --- a/src/SuperBeam/RequestHandlers.py +++ b/src/SuperBeam/RequestHandlers.py @@ -33,7 +33,7 @@ class OctetStreamHandler(object): @staticmethod def handle(httpd): raise Exception(f'Not implemented: Unable to handle {httpd} ' - '({StreamHandler.mimetype}') + '({OctetStreamHandler.mimetype}') class ChunkTemplateHandler(BaseTextHandler): From 3bda68b466efc9625c1a82885e54a0517db5bed9 Mon Sep 17 00:00:00 2001 From: JayPiKay Date: Thu, 3 Oct 2019 10:34:05 +0200 Subject: [PATCH 03/10] Automatic request handler route registration --- src/SuperBeam/RequestHandlers.py | 23 ++++++++++++++++---- src/SuperBeam/__init__.py | 37 +++++++++++--------------------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/SuperBeam/RequestHandlers.py b/src/SuperBeam/RequestHandlers.py index 33f2f42..b33fb76 100644 --- a/src/SuperBeam/RequestHandlers.py +++ b/src/SuperBeam/RequestHandlers.py @@ -37,11 +37,16 @@ class OctetStreamHandler(object): class ChunkTemplateHandler(BaseTextHandler): - pass + """TODO + """ + URI = ('/', '/index.htm', 'index') class ThumbHandler(object): + """TODO + """ mimetype = 'image/png' + URI = ('/getthumb',) @staticmethod def handle(httpd): @@ -54,7 +59,6 @@ class LegacyListHandler(BaseTextHandler): URI: ``/superlist`` """ - URI = ('/superlist',) @staticmethod @@ -110,11 +114,16 @@ class JsonListHandler(object): class SingleFileHandler(OctetStreamHandler): - pass + """TODO + """ + URI = ('/get/',) class ZipFileHandler(object): + """TODO + """ mimetype = 'application/zip' + URI = ('/getzip',) @staticmethod def handle(httpd): @@ -130,6 +139,7 @@ class SuperStreamHandler(OctetStreamHandler): URI: ``/getstream`` """ + URI = ('/getstream',) @staticmethod def _get_streamsize(files): @@ -160,11 +170,16 @@ class SuperStreamHandler(OctetStreamHandler): class AssetHandler(OctetStreamHandler): - pass + """TODO + """ + URI = ('*',) class ApkRequestHandler(object): + """TODO + """ mimetype = 'application/vnd.android.package-archive' + URI = ('/getapk',) @staticmethod def handle(httpd): diff --git a/src/SuperBeam/__init__.py b/src/SuperBeam/__init__.py index 817ea2b..cbb9ed1 100644 --- a/src/SuperBeam/__init__.py +++ b/src/SuperBeam/__init__.py @@ -8,12 +8,8 @@ import socket import struct import base64 - -from SuperBeam.RequestHandlers import ( - ChunkTemplateHandler, ThumbHandler, ApkRequestHandler, LegacyListHandler, - SingleFileHandler, ZipFileHandler, SuperStreamHandler, AssetHandler, - JsonListHandler -) +import inspect +from SuperBeam import RequestHandlers class SuperBeamServer(BaseHTTPRequestHandler): @@ -37,9 +33,14 @@ class SuperBeamServer(BaseHTTPRequestHandler): files (list): list of files served by the `SuperBeamServer`. """ - def __init__(self, debug=False, *args, **kwargs): + def __init__(self, request, client_address, server, debug=False): self.debug = debug - super(SuperBeamServer, self).__init__() + self.routes = {} + for _, cls in inspect.getmembers(RequestHandlers, inspect.isclass): + if hasattr(cls, 'URI'): + for route in cls.URI: + self.routes[route] = cls + super(SuperBeamServer, self).__init__(request, client_address, server) def do_GET(self): """GET request handler @@ -53,26 +54,12 @@ class SuperBeamServer(BaseHTTPRequestHandler): exists - forwarded to :func:`respond()`, where the request gets answered. """ - # TODO Add globbing if self.debug: print(threading.current_thread()) - paths = { - 'index': ChunkTemplateHandler, - '/': ChunkTemplateHandler, - '/index.htm': ChunkTemplateHandler, # TODO handler2 - '/getthumb': ThumbHandler, - '/light': ChunkTemplateHandler, # TODO handler2 - '/superlist': LegacyListHandler, - '/jsonlist': JsonListHandler, - '/get/': SingleFileHandler, - '/getapk': ApkRequestHandler, - '/getzip': ZipFileHandler, - '/getstream': SuperStreamHandler, - '*': AssetHandler, - } - if self.path in paths: # TODO startswith key check - self.respond(paths[self.path]) + # TODO Add globbing + if self.path in self.routes: # TODO startswith key check + self.respond(self.routes[self.path]) def respond(self, handler): """Respond client with request with registered handler. From 60a1dc7bf9aa18416907602ef88b8e61dc068baf Mon Sep 17 00:00:00 2001 From: JayPiKay Date: Thu, 3 Oct 2019 10:34:38 +0200 Subject: [PATCH 04/10] Request jsonlist instead of superlist --- src/sbeam.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sbeam.py b/src/sbeam.py index c2d579c..d4c7b2e 100755 --- a/src/sbeam.py +++ b/src/sbeam.py @@ -55,12 +55,12 @@ def serve(recursive, filename): @cli.command() @click.argument('host', default='127.0.0.1') -def superlist(host): - """Fetch superlist from HOST running SupberBeam. +def jsonlist(host): + """Fetch jsonlist from HOST running SupberBeam. HOST is the IP of the SuperBeam server. """ - with requests.get(f'http://{host}:8080/superlist', + with requests.get(f'http://{host}:8080/jsonlist', headers={'User-Agent': 'sbeam'}) as response: assert response.status_code == 200 return response.text.split('\n') From 6e0729e6f017c6741cf5c5d6ae5dac2a3c376f7d Mon Sep 17 00:00:00 2001 From: JayPiKay Date: Thu, 3 Oct 2019 10:46:13 +0200 Subject: [PATCH 05/10] Updated documentation --- source/conf.py | 4 ++-- source/index.rst | 13 +++++++++++++ src/SuperBeam/RequestHandlers.py | 11 ++++------- src/SuperBeam/__init__.py | 3 +++ src/sbeam.py | 2 +- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/source/conf.py b/source/conf.py index 4be96ad..48e60b6 100644 --- a/source/conf.py +++ b/source/conf.py @@ -22,10 +22,10 @@ copyright = '2019, Julian Knauer' author = 'Julian Knauer' # The short X.Y version -version = '0.3' +version = '0.5' # The full version, including alpha/beta/rc tags -release = '0.3' +release = 'alpha' # -- General configuration --------------------------------------------------- diff --git a/source/index.rst b/source/index.rst index 00a1c90..1f2f430 100644 --- a/source/index.rst +++ b/source/index.rst @@ -6,11 +6,24 @@ Welcome to SuperBeam documentation! =================================== + +Command line tool +----------------- + .. toctree:: :maxdepth: 2 :caption: Contents: sbeam + + +SuperBeam Development +--------------------- + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + SuperBeam SuperBeam.RequestHandlers diff --git a/src/SuperBeam/RequestHandlers.py b/src/SuperBeam/RequestHandlers.py index b33fb76..05f90a8 100644 --- a/src/SuperBeam/RequestHandlers.py +++ b/src/SuperBeam/RequestHandlers.py @@ -1,15 +1,12 @@ # -*- coding: utf-8 -*- """SuperBeam Handlers -SuperBeam Handlers define how to handle certain requests to the -`SuperBeamServer`. Each handler needs to be registered in ThumbHandler -`SuperBeamServer.do_GET()` function within the ``paths`` variable and imported -in the top of the :mod:`SuperBeam` module. - -The handlers itself are defined in this module. +The handlers are automatically registered during the creation of the +:class:`SuperBeamServer` instance. Each class handler method needs to be a ``staticmethod``. The callee will call -the `handle()` with the ``httpd`` instance of the :class:`SuperBeamServer`. +the `handle()` with the ``httpd`` instance of the :class:`SuperBeamServer` as +required argument. """ import os diff --git a/src/SuperBeam/__init__.py b/src/SuperBeam/__init__.py index cbb9ed1..4c31245 100644 --- a/src/SuperBeam/__init__.py +++ b/src/SuperBeam/__init__.py @@ -20,6 +20,9 @@ class SuperBeamServer(BaseHTTPRequestHandler): Class is built on top of :mod:`http.server.BaseHTTPRequestHandler` as simple http server. + During initialization, the RequestHandlers are registered as routable + paths. + Call :func:`serve_forever()` to start the HTTP server. The mainloop will serve a list of files given as argument to :func:`serve_forever()`. diff --git a/src/sbeam.py b/src/sbeam.py index d4c7b2e..4c7a5fb 100755 --- a/src/sbeam.py +++ b/src/sbeam.py @@ -31,7 +31,7 @@ import requests import SuperBeam -__VERSION__ = '0.4' +__VERSION__ = '0.5' @click.group() From f5882933ab54714ef921bfcb4f08b1275bfe654c Mon Sep 17 00:00:00 2001 From: JayPiKay Date: Thu, 3 Oct 2019 10:59:53 +0200 Subject: [PATCH 06/10] Fixed setup script --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index 980f73c..305060b 100755 --- a/setup.py +++ b/setup.py @@ -1,6 +1,9 @@ +import sys from setuptools import setup, find_namespace_packages from distutils.util import convert_path +sys.path.insert(0, 'src/') + main_ns = {} ver_path = convert_path('src/sbeam.py') with open(ver_path) as ver_file: From 2f653b31accf3899fa5cfcf7b9a232df7784532b Mon Sep 17 00:00:00 2001 From: JayPiKay Date: Sat, 19 Oct 2019 14:53:45 +0200 Subject: [PATCH 07/10] Directory structure refactoring --- .gitignore | 3 +++ Makefile | 4 ++-- {source => doc/source}/SuperBeam.RequestHandlers.rst | 0 {source => doc/source}/SuperBeam.rst | 0 {source => doc/source}/conf.py | 2 +- {source => doc/source}/index.rst | 0 {source => doc/source}/sbeam.rst | 0 make.bat | 4 ++-- 8 files changed, 8 insertions(+), 5 deletions(-) rename {source => doc/source}/SuperBeam.RequestHandlers.rst (100%) rename {source => doc/source}/SuperBeam.rst (100%) rename {source => doc/source}/conf.py (98%) rename {source => doc/source}/index.rst (100%) rename {source => doc/source}/sbeam.rst (100%) diff --git a/.gitignore b/.gitignore index 34d312f..ccd87b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ *.pyc *.egg-info +.python-version + +doc/ build/ dist/ venv/ diff --git a/Makefile b/Makefile index d0c3cbf..ecb8d0a 100644 --- a/Makefile +++ b/Makefile @@ -5,8 +5,8 @@ # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build -SOURCEDIR = source -BUILDDIR = build +SOURCEDIR = doc/source +BUILDDIR = doc # Put it first so that "make" without argument is like "make help". help: diff --git a/source/SuperBeam.RequestHandlers.rst b/doc/source/SuperBeam.RequestHandlers.rst similarity index 100% rename from source/SuperBeam.RequestHandlers.rst rename to doc/source/SuperBeam.RequestHandlers.rst diff --git a/source/SuperBeam.rst b/doc/source/SuperBeam.rst similarity index 100% rename from source/SuperBeam.rst rename to doc/source/SuperBeam.rst diff --git a/source/conf.py b/doc/source/conf.py similarity index 98% rename from source/conf.py rename to doc/source/conf.py index 48e60b6..b8c1267 100644 --- a/source/conf.py +++ b/doc/source/conf.py @@ -12,7 +12,7 @@ # import os import sys -sys.path.insert(0, os.path.abspath('../src')) +sys.path.insert(0, os.path.abspath('../../src')) # -- Project information ----------------------------------------------------- diff --git a/source/index.rst b/doc/source/index.rst similarity index 100% rename from source/index.rst rename to doc/source/index.rst diff --git a/source/sbeam.rst b/doc/source/sbeam.rst similarity index 100% rename from source/sbeam.rst rename to doc/source/sbeam.rst diff --git a/make.bat b/make.bat index 6247f7e..9a4f94b 100644 --- a/make.bat +++ b/make.bat @@ -7,8 +7,8 @@ REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) -set SOURCEDIR=source -set BUILDDIR=build +set SOURCEDIR=doc\source +set BUILDDIR=doc if "%1" == "" goto help From 182d36a3fceaf21cff8fafce2a442139739d4f42 Mon Sep 17 00:00:00 2001 From: JayPiKay Date: Sat, 19 Oct 2019 15:18:33 +0200 Subject: [PATCH 08/10] Catch connection errors --- src/sbeam.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sbeam.py b/src/sbeam.py index 4c7a5fb..6d98f8e 100755 --- a/src/sbeam.py +++ b/src/sbeam.py @@ -102,8 +102,8 @@ def receive(host, destination): fullpath = os.path.join(destination, filepath[1:]) os.makedirs(fullpath, exist_ok=True) with open(os.path.join(fullpath, filename), 'wb') as fd: - # TODO split filesize in chunks with remainder fd.write(response.raw.read(int(filesize))) + bar.update(len(filelist)) @cli.command() @@ -153,4 +153,7 @@ def tui(): if __name__ == '__main__': - cli() + try: + cli() + except requests.exceptions.ConnectionError as conerr: + print(f'Failed to connect. Target alive?') From 089da0ac01ccdbae6f57bc7a7866b06e3826d5dc Mon Sep 17 00:00:00 2001 From: JayPiKay Date: Sat, 19 Oct 2019 15:31:10 +0200 Subject: [PATCH 09/10] Remove tui poc code from master --- src/SuperBeam/tui.py | 159 ------------------------------------------- src/sbeam.py | 23 ------- 2 files changed, 182 deletions(-) delete mode 100644 src/SuperBeam/tui.py diff --git a/src/SuperBeam/tui.py b/src/SuperBeam/tui.py deleted file mode 100644 index f0a92a5..0000000 --- a/src/SuperBeam/tui.py +++ /dev/null @@ -1,159 +0,0 @@ -import asyncio - -import urwid - -import weakref - -import struct -import base64 -import pyqrcode -import socket - -from datetime import datetime - - -loop = asyncio.get_event_loop() - - -def unhandled(key): - if key == 'ctrl c': - raise urwid.ExitMainLoop - - -def get_primary_ip(): - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - try: - # doesn't even have to be reachable - s.connect(('10.255.255.255', 1)) - IP = s.getsockname()[0] - except Exception as exc: - print(exc) - IP = '127.0.0.1' - finally: - s.close() - return IP - - -def get_qrcode(): - primary_ip = get_primary_ip() - octets = [1] - octets.extend([int(_) for _ in primary_ip.split('.')[::-1]]) - url = 'http://superbe.am/q?' + base64.b64encode( - struct.pack('BBBBB', *octets) - ).decode('utf-8') - return pyqrcode.create(url) - - -class ClientModel(object): - - def __init__(self): - self.clients = ['127.0.0.1', '192.168.178.34'] - - def get_clients(self): - return self.clients - - -class SuperBeamView(urwid.WidgetWrap): - - palette = [ - ('body', 'black', 'light gray', 'standout'), - ('header', 'white', 'dark red', 'bold'), - ('screen edge', 'light blue', 'dark cyan'), - ('main shadow', 'dark gray', 'black'), - ('line', 'black', 'light gray', 'standout'), - ('bg background', 'light gray', 'black'), - ('bg 1', 'black', 'dark blue', 'standout'), - ('bg 1 smooth', 'dark blue', 'black'), - ('bg 2', 'black', 'dark cyan', 'standout'), - ('bg 2 smooth', 'dark cyan', 'black'), - ('button normal', 'light gray', 'dark blue', 'standout'), - ('button select', 'white', 'dark green'), - ('line', 'black', 'light gray', 'standout'), - ('pg normal', 'white', 'black', 'standout'), - ('pg complete', 'white', 'dark magenta'), - ('pg smooth', 'dark magenta', 'black'), - ('qr code', 'black', 'white'), - ] - - def __init__(self, controller): - self.controller = controller - self.client_model = ClientModel() - urwid.WidgetWrap.__init__(self, self.main_window()) - - def main_shadow(self, w): - bg = urwid.AttrWrap(urwid.SolidFill(u"\u2592"), 'screen edge') - shadow = urwid.AttrWrap(urwid.SolidFill(u" "), 'main shadow') - - bg = urwid.Overlay(shadow, bg, - ('fixed left', 3), ('fixed right', 1), - ('fixed top', 2), ('fixed bottom', 1)) - w = urwid.Overlay(w, bg, - ('fixed left', 2), ('fixed right', 3), - ('fixed top', 1), ('fixed bottom', 2)) - return w - - def update_clock(self, widget_ref): - widget = widget_ref() - if not widget: - return - widget.set_text(datetime.now().isoformat()) - loop.call_later(1, self.update_clock, widget_ref) - - def qrcode(self): - qrcode = get_qrcode().terminal() - qrcode = qrcode.replace('\x1b[0m\x1b[49m ', '██') - qrcode = qrcode.replace('\x1b[0m\x1b[7m ', ' ') - qrcode = qrcode.replace('\x1b[0m', '') - qrcode = qrcode.replace('\x1b[7m', '') - widgets = [] - for row in qrcode.split('\n'): - widgets.append(urwid.AttrWrap(urwid.Text(row), 'qr code')) - w = urwid.Pile(widgets) - return urwid.Filler(urwid.AttrWrap(w, 'body')) - - def client_list(self): - clients = [] - for client in self.client_model.get_clients(): - clients.append(urwid.Filler(urwid.AttrWrap(urwid.Text(client), - 'line'))) - return urwid.Pile([(1, _) for _ in clients]) - - def main_window(self): - header = urwid.AttrWrap(urwid.Text('SuperBeam Tui'), 'header') - clock = urwid.Text('') - self.update_clock(weakref.ref(clock)) - footer = urwid.AttrWrap(clock, 'header') - - body = urwid.Pile([(44, self.qrcode()), self.client_list()]) - - window = urwid.Frame(body, header, footer) - return self.main_shadow(window) - - -class SuperBeamController(object): - - def __init__(self): - self.model = ClientModel() - self.view = SuperBeamView(self) - - def run(self): - # self.loop = urwid.MainLoop(self.view, self.view.palette) - self.loop = urwid.MainLoop( - self.view, self.view.palette, - event_loop=urwid.AsyncioEventLoop(loop=loop), - unhandled_input=unhandled - ) - self.loop.run() - - -def run(): - screen = urwid.raw_display.Screen() - size = screen.get_cols_rows() - if size[0] < 87 or size[1] < 58: - raise Exception('Not enough screen space to render QR code.') - else: - SuperBeamController().run() - - -if __name__ == '__main__': - run() diff --git a/src/sbeam.py b/src/sbeam.py index 6d98f8e..0c40d5d 100755 --- a/src/sbeam.py +++ b/src/sbeam.py @@ -6,24 +6,6 @@ # # Distributed under terms of the MIT license. -""" -> GET /getstream HTTP/1.1 -> Host: 192.168.178.50:8080 -> User-Agent: sbeam -> Accept: */* -> -* Mark bundle as not supporting multiuse -< HTTP/1.1 200 OK -< ylfrettub: 1 -< Content-Disposition: attachment; filename="RedHotChillyStream" -< Date: Fri, 09 Aug 2019 12:57:20 GMT -< Content-Length: 8137967 -< Content-Type: application/octet-stream - -Server QR Code = http://superbe.am/q?ATKyqMA= -Base64 = 01 32 b2 a8 c0 = 01 50 178 168 192 -""" - import os import click import requests @@ -147,11 +129,6 @@ def split_stream(filename, superlist, directory): handle.write(blob.read(int(filesize))) -@cli.command() -def tui(): - SuperBeam.tui.run() - - if __name__ == '__main__': try: cli() From f6f7923bc07a18cdd02e564e57187c0c5f48ff57 Mon Sep 17 00:00:00 2001 From: JayPiKay Date: Sat, 19 Oct 2019 15:32:34 +0200 Subject: [PATCH 10/10] requirements --- requirements.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 64aeefd..4d0a032 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,3 @@ -certifi==2019.6.16 -chardet==3.0.4 Click==7.0 -idna==2.8 PyQRCode==1.2.1 requests==2.22.0 -urllib3==1.25.3