Merge branch 'master' of git.goatpr0n.de:jpk/sbeam

* 'master' of git.goatpr0n.de:jpk/sbeam:
  requirements
  Remove tui poc code from master
  Catch connection errors
  Directory structure refactoring
  Fixed setup script
  Updated documentation
  Request jsonlist instead of superlist
  Automatic request handler route registration
  Fixed Instance name
  Added version to setup and sbeam
This commit is contained in:
jpk 2019-12-04 09:42:16 +01:00
commit ab3325616c
14 changed files with 84 additions and 237 deletions

3
.gitignore vendored
View File

@ -1,6 +1,9 @@
*.pyc
*.egg-info
.python-version
doc/
build/
dist/
venv/

View File

@ -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:

View File

@ -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 -----------------------------------------------------
@ -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 ---------------------------------------------------

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,10 +1,18 @@
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:
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/',

View File

@ -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
@ -33,15 +30,20 @@ class OctetStreamHandler(object):
@staticmethod
def handle(httpd):
raise Exception(f'Not implemented: Unable to handle {httpd} '
'({StreamHandler.mimetype}')
'({OctetStreamHandler.mimetype}')
class ChunkTemplateHandler(BaseTextHandler):
pass
"""TODO
"""
URI = ('/', '/index.htm', 'index')
class ThumbHandler(object):
"""TODO
"""
mimetype = 'image/png'
URI = ('/getthumb',)
@staticmethod
def handle(httpd):
@ -54,7 +56,6 @@ class LegacyListHandler(BaseTextHandler):
URI: ``/superlist``
"""
URI = ('/superlist',)
@staticmethod
@ -110,11 +111,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 +136,7 @@ class SuperStreamHandler(OctetStreamHandler):
URI: ``/getstream``
"""
URI = ('/getstream',)
@staticmethod
def _get_streamsize(files):
@ -160,11 +167,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):

View File

@ -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):
@ -24,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()`.
@ -37,9 +36,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 +57,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.

View File

@ -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()

View File

@ -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
@ -31,7 +13,11 @@ import requests
import SuperBeam
__VERSION__ = '0.5'
@click.group()
@click.version_option(version=__VERSION__, prog_name='SuperBeam CLI')
def cli():
pass
@ -54,12 +40,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')
@ -101,8 +87,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()
@ -146,10 +132,8 @@ def split_stream(filename, superlist, directory):
handle.write(blob.read(int(filesize)))
@cli.command()
def tui():
SuperBeam.tui.run()
if __name__ == '__main__':
cli()
try:
cli()
except requests.exceptions.ConnectionError as conerr:
print(f'Failed to connect. Target alive?')