This repository has been archived on 2023-02-02. You can view files and clone it, but cannot push or open issues or pull requests.
barkshark-web/barkshark_web/protocol/handler.py

286 lines
6 KiB
Python

import http_router, mimetypes, traceback
from izzylib import ObjectBase, class_name
from jinja2.exceptions import TemplateNotFound
from threading import Thread
from urllib.parse import unquote
from .. import var
from ..functions import get_app
def list_directory(request, files, show_hidden=False):
context = DotDict(
filelist = [],
dirlist = [],
url = request.url,
textlines = None
)
filelist = []
dirlist = []
for item in sorted(files, key=lambda d: d['filename']):
if not show_hidden and item['filename'].startswith('.'):
continue
if item.type == 'dir':
context.dirlist.append(item)
elif item.type == 'file':
context.filelist.append(item)
else:
logging.warning('Unknown item type:', item.type, item)
return request.template('files.haml', context)
def handle_remote_file(request, data):
if request.path.suffix in ['htm', 'html', 'xhtml']:
return request.response(data, request.mimetype)
#elif request.path.suffix in ['py', 'ini', 'env', 'txt', 'json', 'sgrd', 'xspf', 'go', 'js', 'css', 'scss', 'log']:
elif mimetype.startswith('text') or request.path.suffix in ['json']:
context = DotDict(
textlines = [],
url = request.url
)
if request.mimetype == 'application/json':
context.textlines = DotDict(data).to_json(4).split('\n')
else:
if type(data) == bytes:
data.decode('utf-8')
context.textlines = data.split('\n')
return request.template('files.haml', context)
return request.error(f'Unhandled mimetype: {mimetype}', 400)
class ProtocolRequest(ObjectBase):
def __init__(self, handler, request):
ObjectBase.__init__(self,
handler = handler,
request = request,
ctx = DotDict(),
finished = False,
finished_thread = False,
readonly_props = ['handler', 'request', 'ctx']
)
@property
def app(self):
return self.handler.app
@property
def db(self):
return self.handler.app.db
@property
def window(self):
return self.handler.window
@property
def webview(self):
return self.request.get_web_view()
@property
def tab(self):
return self.webview.parent
@property
def url(self):
return Url(self.request.get_uri())
@property
def domain(self):
return self.url.domain
@property
def path(self):
return self.url.path
@property
def path_unquote(self):
return Path(unquote(self.path))
@property
def protocol(self):
return self.url.proto
@property
def query(self):
return self.url.query
@property
def mimetype(self):
return mimetypes.guess_type(self.path)[0]
def _render_template(self, path, context={}):
try:
return self.app.template.render(path, context)
except TemplateNotFound:
logging.error('Cannot find template:', path)
return self.app.template.render('error.haml', {'title': 'Template Not Found', 'error_msg': path})
def response(self, data, ctype='text/html'):
if self.finished:
return logging.error('Already responded to request:', self.url)
if isinstance(data, str):
data = data.encode('UTF-8')
bytestream = Gio.MemoryInputStream.new_from_bytes(GLib.Bytes(data))
self.request.finish(bytestream, len(data), ctype)
self.finished = True
def finish_thread(self, func, *args, **kwargs):
self.finished_thread = True
Thread(target=func, args=args, kwargs=kwargs).start()
def error_response(self, body, status=404):
if self.finished:
return logging.error('Already responded to request:', self.url)
error = GLib.Error.new_literal(GLib.quark_from_string('HandlerError'), body, status)
self.request.finish_error(error)
self.finished = True
def page(self, page, context={}):
return self.template(f'page/{page}.haml', context, 'text/html')
def template(self, path, context={}, ctype=None):
context['web_request'] = self
html = self._render_template(path, context)
return self.response(html, ctype or self.mimetype)
def error(self, message, status=404):
logging.verbose(f'Error for handler: {message}')
context = {
'title': f'Error {status}',
'error_msg': message
}
html = self._render_template('error.haml', context)
return self.error_response(html, status)
def file(self, path, ctype=None):
with Path(path).open('rb') as fd:
return self.response(fd.read(), ctype)
def redirect(self, url=None, message=None, level='INFO'):
if message:
self.window.notification(message, level)
if url and url.startswith('/'):
url = self.handler.protocol + '://' + url
if not self.finished:
self.response('OK')
self.webview.load_uri(url or var.local)
class ProtocolHandler(ObjectBase):
def __init__(self, protocol, request_class=ProtocolRequest):
super().__init__(
protocol = protocol,
router = http_router.Router(),
request_class = request_class,
ctx = DotDict(),
readonly_props = True
)
def __call__(self, webkit_request):
request = self.request_class(self, webkit_request)
if not request.protocol == self.protocol:
logging.debug(f'Src: {request.url}, Proto: {self.protocol}')
raise TypeError('Request protocols do not match')
try:
route = self.get_route(request.path)
except http_router.NotFound:
return request.error(f'Path not found: {request.path}', 404)
try:
if getattr(self, route.target.__name__, None):
response = route.target(request, **(route.params or {}))
else:
response = route.target(self, request, **(route.params or {}))
if request.finished_thread:
return
if not request.finished:
return request.error(f'Request not finished: {request.url}', 500)
except Exception as e:
traceback.print_exc()
return request.error(f'Backend Error: {class_name(e)}: {e}', 500)
return response
@property
def app(self):
return get_app()
@property
def db(self):
return self.app.db
@property
def window(self):
return self.app.window
def add_route(self, handler, *paths):
self.router.bind(handler, *paths, methods=['GET'])
def get_route(self, path, method='GET'):
if not path.startswith('/'):
path = '/' + path
return self.router(str(path), method.upper())
def route(self, *paths):
def wrapper(func):
self.add_route(func, *paths)
return func
return wrapper