#!/usr/bin/python
##
## license:BSD-3-Clause
## copyright-holders:Vas Crabb
from . import dbaccess
from . import htmltmpl
import cgi
import inspect
import json
import mimetypes
import os.path
import re
import sys
import wsgiref.simple_server
import wsgiref.util
if sys.version_info >= (3, ):
import urllib.parse as urlparse
else:
import urlparse
class HandlerBase(object):
STATUS_MESSAGE = {
400: 'Bad Request',
401: 'Unauthorized',
403: 'Forbidden',
404: 'Not Found',
405: 'Method Not Allowed',
500: 'Internal Server Error',
501: 'Not Implemented',
502: 'Bad Gateway',
503: 'Service Unavailable',
504: 'Gateway Timeout',
505: 'HTTP Version Not Supported' }
def __init__(self, app, application_uri, environ, start_response, **kwargs):
super(HandlerBase, self).__init__(**kwargs)
self.app = app
self.js_escape = app.js_escape
self.application_uri = application_uri
self.environ = environ
self.start_response = start_response
def error_page(self, code):
yield htmltmpl.ERROR_PAGE.substitute(code=cgi.escape('%d' % code), message=cgi.escape(self.STATUS_MESSAGE[code])).encode('utf-8')
class ErrorPageHandler(HandlerBase):
def __init__(self, code, app, application_uri, environ, start_response, **kwargs):
super(ErrorPageHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
self.code = code
self.start_response('%d %s' % (self.code, self.STATUS_MESSAGE[code]), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
def __iter__(self):
return self.error_page(self.code)
class AssetHandler(HandlerBase):
def __init__(self, directory, app, application_uri, environ, start_response, **kwargs):
super(AssetHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
self.directory = directory
self.asset = wsgiref.util.shift_path_info(environ)
def __iter__(self):
if not self.asset:
self.start_response('403 %s' % (self.STATUS_MESSAGE[403], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
return self.error_page(403)
elif self.environ['PATH_INFO']:
self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
return self.error_page(404)
else:
path = os.path.join(self.directory, self.asset)
if not os.path.isfile(path):
self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
return self.error_page(404)
elif self.environ['REQUEST_METHOD'] != 'GET':
self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
return self.error_page(405)
else:
try:
f = open(path, 'rb')
type, encoding = mimetypes.guess_type(path)
self.start_response('200 OK', [('Content-type', type or 'application/octet-stream'), ('Cache-Control', 'public, max-age=3600')])
return wsgiref.util.FileWrapper(f)
except:
self.start_response('500 %s' % (self.STATUS_MESSAGE[500], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
return self.error_page(500)
class QueryPageHandler(HandlerBase):
def __init__(self, app, application_uri, environ, start_response, **kwargs):
super(QueryPageHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
self.dbcurs = app.dbconn.cursor()
def machine_href(self, shortname):
return cgi.escape(urlparse.urljoin(self.application_uri, 'machine/%s' % (shortname, )), True)
def sourcefile_href(self, sourcefile):
return cgi.escape(urlparse.urljoin(self.application_uri, 'sourcefile/%s' % (sourcefile, )), True)
class MachineRpcHandlerBase(QueryPageHandler):
def __init__(self, app, application_uri, environ, start_response, **kwargs):
super(MachineRpcHandlerBase, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
self.shortname = wsgiref.util.shift_path_info(environ)
def __iter__(self):
if not self.shortname:
self.start_response('403 %s' % (self.STATUS_MESSAGE[403], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
return self.error_page(403)
elif self.environ['PATH_INFO']:
self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
return self.error_page(404)
else:
machine = self.dbcurs.get_machine_id(self.shortname)
if machine is None:
self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
return self.error_page(404)
elif self.environ['REQUEST_METHOD'] != 'GET':
self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
return self.error_page(405)
else:
self.start_response('200 OK', [('Content-type', 'application/json; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
return self.data_page(machine)
class MachineHandler(QueryPageHandler):
def __init__(self, app, application_uri, environ, start_response, **kwargs):
super(MachineHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
self.shortname = wsgiref.util.shift_path_info(environ)
def __iter__(self):
if not self.shortname:
# could probably list machines here or something
self.start_response('403 %s' % (self.STATUS_MESSAGE[403], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
return self.error_page(403)
elif self.environ['PATH_INFO']:
self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
return self.error_page(404)
else:
machine_info = self.dbcurs.get_machine_info(self.shortname).fetchone()
if not machine_info:
self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
return self.error_page(404)
elif self.environ['REQUEST_METHOD'] != 'GET':
self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
return self.error_page(405)
else:
self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
return self.machine_page(machine_info)
def machine_page(self, machine_info):
id = machine_info['id']
description = machine_info['description']
yield htmltmpl.MACHINE_PROLOGUE.substitute(
app=self.js_escape(cgi.escape(self.application_uri, True)),
assets=self.js_escape(cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True)),
sourcehref=self.sourcefile_href(machine_info['sourcefile']),
description=cgi.escape(description),
shortname=cgi.escape(self.shortname),
isdevice=cgi.escape('Yes' if machine_info['isdevice'] else 'No'),
runnable=cgi.escape('Yes' if machine_info['runnable'] else 'No'),
sourcefile=cgi.escape(machine_info['sourcefile'])).encode('utf-8')
if machine_info['year'] is not None:
yield (
'
Year: | %s |
\n' \
' Manufacturer: | %s |
\n' %
(cgi.escape(machine_info['year']), cgi.escape(machine_info['Manufacturer']))).encode('utf-8')
if machine_info['cloneof'] is not None:
parent = self.dbcurs.listfull(machine_info['cloneof']).fetchone()
if parent:
yield (
' Parent Machine: | %s (%s) |
\n' %
(cgi.escape('%smachine/%s' % (self.application_uri, machine_info['cloneof']), True), cgi.escape(parent[1]), cgi.escape(machine_info['cloneof']))).encode('utf-8')
else:
yield (
' Parent Machine: | %s |
\n' %
(cgi.escape('%smachine/%s' % (self.application_uri, machine_info['cloneof']), True), cgi.escape(machine_info['cloneof']))).encode('utf-8')
if (machine_info['romof'] is not None) and (machine_info['romof'] != machine_info['cloneof']):
parent = self.dbcurs.listfull(machine_info['romof']).fetchone()
if parent:
yield (
' Parent ROM set: | %s (%s) |
\n' %
(cgi.escape('%smachine/%s' % (self.application_uri, machine_info['romof']), True), cgi.escape(parent[1]), cgi.escape(machine_info['romof']))).encode('utf-8')
else:
yield (
' Parent Machine: | %s |
\n' %
(cgi.escape('%smachine/%s' % (self.application_uri, machine_info['romof']), True), cgi.escape(machine_info['romof']))).encode('utf-8')
unemulated = []
imperfect = []
for feature, status, overall in self.dbcurs.get_feature_flags(id):
if overall == 1:
imperfect.append(feature)
elif overall > 1:
unemulated.append(feature)
if (unemulated):
unemulated.sort()
yield(
(' Unemulated Features: | %s' + (', %s' * (len(unemulated) - 1)) + ' |
\n') %
tuple(unemulated)).encode('utf-8');
if (imperfect):
yield(
(' Imperfect Features: | %s' + (', %s' * (len(imperfect) - 1)) + ' |
\n') %
tuple(imperfect)).encode('utf-8');
yield '\n'.encode('utf-8')
# allow system BIOS selection
haveoptions = False
for name, desc, isdef in self.dbcurs.get_biossets(id):
if not haveoptions:
haveoptions = True;
yield htmltmpl.MACHINE_OPTIONS_HEADING.substitute().encode('utf-8')
yield htmltmpl.MACHINE_BIOS_PROLOGUE.substitute().encode('utf-8')
yield htmltmpl.MACHINE_BIOS_OPTION.substitute(
name=cgi.escape(name, True),
description=cgi.escape(desc),
isdefault=('yes' if isdef else 'no')).encode('utf-8')
if haveoptions:
yield '\n\n'.encode('utf-8')
# allow RAM size selection
first = True
for name, size, isdef in self.dbcurs.get_ram_options(id):
if first:
if not haveoptions:
haveoptions = True;
yield htmltmpl.MACHINE_OPTIONS_HEADING.substitute().encode('utf-8')
yield htmltmpl.MACHINE_RAM_PROLOGUE.substitute().encode('utf-8')
first = False
yield htmltmpl.MACHINE_RAM_OPTION.substitute(
name=cgi.escape(name, True),
size=cgi.escape('{:,}'.format(size)),
isdefault=('yes' if isdef else 'no')).encode('utf-8')
if not first:
yield '\n\n'.encode('utf-8')
# placeholder for machine slots - populated by client-side JavaScript
if self.dbcurs.count_slots(id):
if not haveoptions:
haveoptions = True
yield htmltmpl.MACHINE_OPTIONS_HEADING.substitute().encode('utf-8')
yield htmltmpl.MACHINE_SLOTS_PLACEHOLDER.substitute(
machine=self.js_escape(self.shortname)).encode('utf=8')
# list devices referenced by this system/device
first = True
for name, desc, src in self.dbcurs.get_devices_referenced(id):
if first:
yield \
'Devices Referenced
\n' \
'\n' \
' \n' \
' Short name | Description | Source file |
\n' \
' \n' \
' \n'.encode('utf-8')
first = False
yield self.machine_row(name, desc, src)
if not first:
yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-dev-refs').encode('utf-8')
# list slots where this device is an option
first = True
for name, desc, slot, opt, src in self.dbcurs.get_compatible_slots(id):
if (first):
yield \
'Compatible Slots
\n' \
'\n' \
' \n' \
' Short name | Description | Slot | Choice | Source file |
\n' \
' \n' \
' \n'.encode('utf-8')
first = False
yield htmltmpl.COMPATIBLE_SLOT_ROW.substitute(
machinehref=self.machine_href(name),
sourcehref=self.sourcefile_href(src),
shortname=cgi.escape(name),
description=cgi.escape(desc),
sourcefile=cgi.escape(src),
slot=cgi.escape(slot),
slotoption=cgi.escape(opt)).encode('utf-8')
if not first:
yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-comp-slots').encode('utf-8')
# list systems/devices that reference this device
first = True
for name, desc, src in self.dbcurs.get_device_references(id):
if first:
yield \
'Referenced By
\n' \
'\n' \
' \n' \
' Short name | Description | Source file |
\n' \
' \n' \
' \n'.encode('utf-8')
first = False
yield self.machine_row(name, desc, src)
if not first:
yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-ref-by').encode('utf-8')
yield '