286 lines
6 KiB
Python
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
|