1#!/usr/bin/python 2## 3## license:BSD-3-Clause 4## copyright-holders:Vas Crabb 5 6from . import dbaccess 7from . import htmltmpl 8 9import cgi 10import inspect 11import json 12import mimetypes 13import os.path 14import re 15import sys 16import urllib 17import wsgiref.util 18 19if sys.version_info >= (3, ): 20 import urllib.parse as urlparse 21 urlquote = urlparse.quote 22else: 23 import urlparse 24 urlquote = urllib.quote 25 26 27class HandlerBase(object): 28 STATUS_MESSAGE = { 29 400: 'Bad Request', 30 401: 'Unauthorized', 31 403: 'Forbidden', 32 404: 'Not Found', 33 405: 'Method Not Allowed', 34 500: 'Internal Server Error', 35 501: 'Not Implemented', 36 502: 'Bad Gateway', 37 503: 'Service Unavailable', 38 504: 'Gateway Timeout', 39 505: 'HTTP Version Not Supported' } 40 41 def __init__(self, app, application_uri, environ, start_response, **kwargs): 42 super(HandlerBase, self).__init__(**kwargs) 43 self.app = app 44 self.js_escape = app.js_escape 45 self.application_uri = application_uri 46 self.environ = environ 47 self.start_response = start_response 48 49 def error_page(self, code): 50 yield htmltmpl.ERROR_PAGE.substitute(code=cgi.escape('%d' % (code, )), message=cgi.escape(self.STATUS_MESSAGE[code])).encode('utf-8') 51 52 53class ErrorPageHandler(HandlerBase): 54 def __init__(self, code, app, application_uri, environ, start_response, **kwargs): 55 super(ErrorPageHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs) 56 self.code = code 57 self.start_response('%d %s' % (self.code, self.STATUS_MESSAGE[code]), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 58 59 def __iter__(self): 60 return self.error_page(self.code) 61 62 63class AssetHandler(HandlerBase): 64 def __init__(self, directory, app, application_uri, environ, start_response, **kwargs): 65 super(AssetHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs) 66 self.directory = directory 67 self.asset = wsgiref.util.shift_path_info(environ) 68 69 def __iter__(self): 70 if not self.asset: 71 self.start_response('403 %s' % (self.STATUS_MESSAGE[403], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 72 return self.error_page(403) 73 elif self.environ['PATH_INFO']: 74 self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 75 return self.error_page(404) 76 else: 77 path = os.path.join(self.directory, self.asset) 78 if not os.path.isfile(path): 79 self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 80 return self.error_page(404) 81 elif self.environ['REQUEST_METHOD'] != 'GET': 82 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')]) 83 return self.error_page(405) 84 else: 85 try: 86 f = open(path, 'rb') 87 type, encoding = mimetypes.guess_type(path) 88 self.start_response('200 OK', [('Content-type', type or 'application/octet-stream'), ('Cache-Control', 'public, max-age=3600')]) 89 return wsgiref.util.FileWrapper(f) 90 except: 91 self.start_response('500 %s' % (self.STATUS_MESSAGE[500], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 92 return self.error_page(500) 93 94 95class QueryPageHandler(HandlerBase): 96 def __init__(self, app, application_uri, environ, start_response, **kwargs): 97 super(QueryPageHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs) 98 self.dbcurs = app.dbconn.cursor() 99 100 def machine_href(self, shortname): 101 return cgi.escape(urlparse.urljoin(self.application_uri, 'machine/%s' % (urlquote(shortname), )), True) 102 103 def sourcefile_href(self, sourcefile): 104 return cgi.escape(urlparse.urljoin(self.application_uri, 'sourcefile/%s' % (urlquote(sourcefile), )), True) 105 106 def softwarelist_href(self, softwarelist): 107 return cgi.escape(urlparse.urljoin(self.application_uri, 'softwarelist/%s' % (urlquote(softwarelist), )), True) 108 109 def software_href(self, softwarelist, software): 110 return cgi.escape(urlparse.urljoin(self.application_uri, 'softwarelist/%s/%s' % (urlquote(softwarelist), urlquote(software))), True) 111 112 def bios_data(self, machine): 113 result = { } 114 for name, description, isdefault in self.dbcurs.get_biossets(machine): 115 result[name] = { 'description': description, 'isdefault': True if isdefault else False } 116 return result 117 118 def flags_data(self, machine): 119 result = { 'features': { } } 120 for feature, status, overall in self.dbcurs.get_feature_flags(machine): 121 detail = { } 122 if status == 1: 123 detail['status'] = 'imperfect' 124 elif status > 1: 125 detail['status'] = 'unemulated' 126 if overall == 1: 127 detail['overall'] = 'imperfect' 128 elif overall > 1: 129 detail['overall'] = 'unemulated' 130 result['features'][feature] = detail 131 return result 132 133 def slot_data(self, machine): 134 result = { 'defaults': { }, 'slots': { } } 135 136 # get slot options 137 prev = None 138 for slot, option, shortname, description in self.dbcurs.get_slot_options(machine): 139 if slot != prev: 140 if slot in result['slots']: 141 options = result['slots'][slot] 142 else: 143 options = { } 144 result['slots'][slot] = options 145 prev = slot 146 options[option] = { 'device': shortname, 'description': description } 147 148 # if there are any slots, get defaults 149 if result['slots']: 150 for slot, default in self.dbcurs.get_slot_defaults(machine): 151 result['defaults'][slot] = default 152 153 # remove slots that come from default cards in other slots 154 for slot in tuple(result['slots'].keys()): 155 slot += ':' 156 for candidate in tuple(result['slots'].keys()): 157 if candidate.startswith(slot): 158 del result['slots'][candidate] 159 160 return result 161 162 def softwarelist_data(self, machine): 163 result = { } 164 165 # get software lists referenced by machine 166 for softwarelist in self.dbcurs.get_machine_softwarelists(machine): 167 result[softwarelist['tag']] = { 168 'status': softwarelist['status'], 169 'shortname': softwarelist['shortname'], 170 'description': softwarelist['description'], 171 'total': softwarelist['total'], 172 'supported': softwarelist['supported'], 173 'partiallysupported': softwarelist['partiallysupported'], 174 'unsupported': softwarelist['unsupported'] } 175 176 # remove software lists that come from default cards in slots 177 if result: 178 for slot, default in self.dbcurs.get_slot_defaults(machine): 179 slot += ':' 180 for candidate in tuple(result.keys()): 181 if candidate.startswith(slot): 182 del result[candidate] 183 184 return result 185 186 187class MachineRpcHandlerBase(QueryPageHandler): 188 def __init__(self, app, application_uri, environ, start_response, **kwargs): 189 super(MachineRpcHandlerBase, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs) 190 self.shortname = wsgiref.util.shift_path_info(environ) 191 192 def __iter__(self): 193 if not self.shortname: 194 self.start_response('403 %s' % (self.STATUS_MESSAGE[403], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 195 return self.error_page(403) 196 elif self.environ['PATH_INFO']: 197 self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 198 return self.error_page(404) 199 else: 200 machine = self.dbcurs.get_machine_id(self.shortname) 201 if machine is None: 202 self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 203 return self.error_page(404) 204 elif self.environ['REQUEST_METHOD'] != 'GET': 205 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')]) 206 return self.error_page(405) 207 else: 208 self.start_response('200 OK', [('Content-type', 'application/json; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 209 return self.data_page(machine) 210 211 212class MachineHandler(QueryPageHandler): 213 def __init__(self, app, application_uri, environ, start_response, **kwargs): 214 super(MachineHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs) 215 self.shortname = wsgiref.util.shift_path_info(environ) 216 217 def __iter__(self): 218 if not self.shortname: 219 # could probably list machines here or something 220 self.start_response('403 %s' % (self.STATUS_MESSAGE[403], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 221 return self.error_page(403) 222 elif self.environ['PATH_INFO']: 223 self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 224 return self.error_page(404) 225 else: 226 machine_info = self.dbcurs.get_machine_details(self.shortname).fetchone() 227 if not machine_info: 228 self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 229 return self.error_page(404) 230 elif self.environ['REQUEST_METHOD'] != 'GET': 231 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')]) 232 return self.error_page(405) 233 else: 234 self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 235 return self.machine_page(machine_info) 236 237 def machine_page(self, machine_info): 238 id = machine_info['id'] 239 description = machine_info['description'] 240 yield htmltmpl.MACHINE_PROLOGUE.substitute( 241 app=self.js_escape(cgi.escape(self.application_uri, True)), 242 assets=self.js_escape(cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True)), 243 sourcehref=self.sourcefile_href(machine_info['sourcefile']), 244 description=cgi.escape(description), 245 shortname=cgi.escape(self.shortname), 246 isdevice=cgi.escape('Yes' if machine_info['isdevice'] else 'No'), 247 runnable=cgi.escape('Yes' if machine_info['runnable'] else 'No'), 248 sourcefile=cgi.escape(machine_info['sourcefile'])).encode('utf-8') 249 if machine_info['year'] is not None: 250 yield ( 251 ' <tr><th>Year:</th><td>%s</td></tr>\n' \ 252 ' <tr><th>Manufacturer:</th><td>%s</td></tr>\n' % 253 (cgi.escape(machine_info['year']), cgi.escape(machine_info['Manufacturer']))).encode('utf-8') 254 if machine_info['cloneof'] is not None: 255 parent = self.dbcurs.listfull(machine_info['cloneof']).fetchone() 256 if parent: 257 yield ( 258 ' <tr><th>Parent machine:</th><td><a href="%s">%s (%s)</a></td></tr>\n' % 259 (self.machine_href(machine_info['cloneof']), cgi.escape(parent[1]), cgi.escape(machine_info['cloneof']))).encode('utf-8') 260 else: 261 yield ( 262 ' <tr><th>Parent machine:</th><td><a href="%s">%s</a></td></tr>\n' % 263 (self.machine_href(machine_info['cloneof']), cgi.escape(machine_info['cloneof']))).encode('utf-8') 264 if (machine_info['romof'] is not None) and (machine_info['romof'] != machine_info['cloneof']): 265 parent = self.dbcurs.listfull(machine_info['romof']).fetchone() 266 if parent: 267 yield ( 268 ' <tr><th>Parent ROM set:</th><td><a href="%s">%s (%s)</a></td></tr>\n' % 269 (self.machine_href(machine_info['romof']), cgi.escape(parent[1]), cgi.escape(machine_info['romof']))).encode('utf-8') 270 else: 271 yield ( 272 ' <tr><th>Parent machine:</th><td><a href="%s">%s</a></td></tr>\n' % 273 (self.machine_href(machine_info['romof']), cgi.escape(machine_info['romof']))).encode('utf-8') 274 unemulated = [] 275 imperfect = [] 276 for feature, status, overall in self.dbcurs.get_feature_flags(id): 277 if overall == 1: 278 imperfect.append(feature) 279 elif overall > 1: 280 unemulated.append(feature) 281 if (unemulated): 282 unemulated.sort() 283 yield( 284 (' <tr><th>Unemulated Features:</th><td>%s' + (', %s' * (len(unemulated) - 1)) + '</td></tr>\n') % 285 tuple(unemulated)).encode('utf-8'); 286 if (imperfect): 287 yield( 288 (' <tr><th>Imperfect Features:</th><td>%s' + (', %s' * (len(imperfect) - 1)) + '</td></tr>\n') % 289 tuple(imperfect)).encode('utf-8'); 290 yield '</table>\n'.encode('utf-8') 291 292 # make a table of clones 293 first = True 294 for clone, clonedescription, cloneyear, clonemanufacturer in self.dbcurs.get_clones(self.shortname): 295 if first: 296 yield htmltmpl.MACHINE_CLONES_PROLOGUE.substitute().encode('utf-8') 297 first = False 298 yield htmltmpl.MACHINE_CLONES_ROW.substitute( 299 href=self.machine_href(clone), 300 shortname=cgi.escape(clone), 301 description=cgi.escape(clonedescription), 302 year=cgi.escape(cloneyear or ''), 303 manufacturer=cgi.escape(clonemanufacturer or '')).encode('utf-8') 304 if not first: 305 yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-clones').encode('utf-8') 306 307 # make a table of software lists 308 yield htmltmpl.MACHINE_SOFTWARELISTS_TABLE_PROLOGUE.substitute().encode('utf-8') 309 for softwarelist in self.dbcurs.get_machine_softwarelists(id): 310 total = softwarelist['total'] 311 yield htmltmpl.MACHINE_SOFTWARELISTS_TABLE_ROW.substitute( 312 rowid=cgi.escape(softwarelist['tag'].replace(':', '-'), True), 313 href=self.softwarelist_href(softwarelist['shortname']), 314 shortname=cgi.escape(softwarelist['shortname']), 315 description=cgi.escape(softwarelist['description']), 316 status=cgi.escape(softwarelist['status']), 317 total=cgi.escape('%d' % (total, )), 318 supported=cgi.escape('%.1f%%' % (softwarelist['supported'] * 100.0 / (total or 1), )), 319 partiallysupported=cgi.escape('%.1f%%' % (softwarelist['partiallysupported'] * 100.0 / (total or 1), )), 320 unsupported=cgi.escape('%.1f%%' % (softwarelist['unsupported'] * 100.0 / (total or 1), ))).encode('utf-8') 321 yield htmltmpl.MACHINE_SOFTWARELISTS_TABLE_EPILOGUE.substitute().encode('utf-8') 322 323 # allow system BIOS selection 324 haveoptions = False 325 for name, desc, isdef in self.dbcurs.get_biossets(id): 326 if not haveoptions: 327 haveoptions = True; 328 yield htmltmpl.MACHINE_OPTIONS_HEADING.substitute().encode('utf-8') 329 yield htmltmpl.MACHINE_BIOS_PROLOGUE.substitute().encode('utf-8') 330 yield htmltmpl.MACHINE_BIOS_OPTION.substitute( 331 name=cgi.escape(name, True), 332 description=cgi.escape(desc), 333 isdefault=('yes' if isdef else 'no')).encode('utf-8') 334 if haveoptions: 335 yield '</select>\n<script>set_default_system_bios();</script>\n'.encode('utf-8') 336 337 # allow RAM size selection 338 first = True 339 for name, size, isdef in self.dbcurs.get_ram_options(id): 340 if first: 341 if not haveoptions: 342 haveoptions = True; 343 yield htmltmpl.MACHINE_OPTIONS_HEADING.substitute().encode('utf-8') 344 yield htmltmpl.MACHINE_RAM_PROLOGUE.substitute().encode('utf-8') 345 first = False 346 yield htmltmpl.MACHINE_RAM_OPTION.substitute( 347 name=cgi.escape(name, True), 348 size=cgi.escape('{:,}'.format(size)), 349 isdefault=('yes' if isdef else 'no')).encode('utf-8') 350 if not first: 351 yield '</select>\n<script>set_default_ram_option();</script>\n'.encode('utf-8') 352 353 # placeholder for machine slots - populated by client-side JavaScript 354 if self.dbcurs.count_slots(id): 355 if not haveoptions: 356 haveoptions = True 357 yield htmltmpl.MACHINE_OPTIONS_HEADING.substitute().encode('utf-8') 358 yield htmltmpl.MACHINE_SLOTS_PLACEHOLDER_PROLOGUE.substitute().encode('utf=8') 359 pending = set((self.shortname, )) 360 added = set((self.shortname, )) 361 haveextra = set() 362 while pending: 363 requested = pending.pop() 364 slots = self.slot_data(self.dbcurs.get_machine_id(requested)) 365 yield (' slot_info[%s] = %s;\n' % (self.sanitised_json(requested), self.sanitised_json(slots))).encode('utf-8') 366 for slotname, slot in slots['slots'].items(): 367 for choice, card in slot.items(): 368 carddev = card['device'] 369 if carddev not in added: 370 pending.add(carddev) 371 added.add(carddev) 372 if (carddev not in haveextra) and (slots['defaults'].get(slotname) == choice): 373 haveextra.add(carddev) 374 cardid = self.dbcurs.get_machine_id(carddev) 375 carddev = self.sanitised_json(carddev) 376 yield ( 377 ' bios_sets[%s] = %s;\n machine_flags[%s] = %s;\n softwarelist_info[%s] = %s;\n' % 378 (carddev, self.sanitised_json(self.bios_data(cardid)), carddev, self.sanitised_json(self.flags_data(cardid)), carddev, self.sanitised_json(self.softwarelist_data(cardid)))).encode('utf-8') 379 yield htmltmpl.MACHINE_SLOTS_PLACEHOLDER_EPILOGUE.substitute( 380 machine=self.sanitised_json(self.shortname)).encode('utf=8') 381 382 # list devices referenced by this system/device 383 first = True 384 for name, desc, src in self.dbcurs.get_devices_referenced(id): 385 if first: 386 yield \ 387 '<h2>Devices Referenced</h2>\n' \ 388 '<table id="tbl-dev-refs">\n' \ 389 ' <thead>\n' \ 390 ' <tr><th>Short name</th><th>Description</th><th>Source file</th></tr>\n' \ 391 ' </thead>\n' \ 392 ' <tbody>\n'.encode('utf-8') 393 first = False 394 yield self.machine_row(name, desc, src) 395 if not first: 396 yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-dev-refs').encode('utf-8') 397 398 # list slots where this device is an option 399 first = True 400 for name, desc, slot, opt, src in self.dbcurs.get_compatible_slots(id): 401 if (first): 402 yield \ 403 '<h2>Compatible Slots</h2>\n' \ 404 '<table id="tbl-comp-slots">\n' \ 405 ' <thead>\n' \ 406 ' <tr><th>Short name</th><th>Description</th><th>Slot</th><th>Choice</th><th>Source file</th></tr>\n' \ 407 ' </thead>\n' \ 408 ' <tbody>\n'.encode('utf-8') 409 first = False 410 yield htmltmpl.COMPATIBLE_SLOT_ROW.substitute( 411 machinehref=self.machine_href(name), 412 sourcehref=self.sourcefile_href(src), 413 shortname=cgi.escape(name), 414 description=cgi.escape(desc), 415 sourcefile=cgi.escape(src), 416 slot=cgi.escape(slot), 417 slotoption=cgi.escape(opt)).encode('utf-8') 418 if not first: 419 yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-comp-slots').encode('utf-8') 420 421 # list systems/devices that reference this device 422 first = True 423 for name, desc, src in self.dbcurs.get_device_references(id): 424 if first: 425 yield \ 426 '<h2>Referenced By</h2>\n' \ 427 '<table id="tbl-ref-by">\n' \ 428 ' <thead>\n' \ 429 ' <tr><th>Short name</th><th>Description</th><th>Source file</th></tr>\n' \ 430 ' </thead>\n' \ 431 ' <tbody>\n'.encode('utf-8') 432 first = False 433 yield self.machine_row(name, desc, src) 434 if not first: 435 yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-ref-by').encode('utf-8') 436 437 yield '</html>\n'.encode('utf-8') 438 439 def machine_row(self, shortname, description, sourcefile): 440 return (htmltmpl.MACHINE_ROW if description is not None else htmltmpl.EXCL_MACHINE_ROW).substitute( 441 machinehref=self.machine_href(shortname), 442 sourcehref=self.sourcefile_href(sourcefile), 443 shortname=cgi.escape(shortname), 444 description=cgi.escape(description or ''), 445 sourcefile=cgi.escape(sourcefile or '')).encode('utf-8') 446 447 @staticmethod 448 def sanitised_json(data): 449 return json.dumps(data).replace('<', '\\u003c').replace('>', '\\u003e') 450 451 452class SourceFileHandler(QueryPageHandler): 453 def __init__(self, app, application_uri, environ, start_response, **kwargs): 454 super(SourceFileHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs) 455 456 def __iter__(self): 457 self.filename = self.environ['PATH_INFO'] 458 if self.filename and (self.filename[0] == '/'): 459 self.filename = self.filename[1:] 460 if not self.filename: 461 if self.environ['REQUEST_METHOD'] != 'GET': 462 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')]) 463 return self.error_page(405) 464 else: 465 self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 466 return self.sourcefile_listing_page(None) 467 else: 468 id = self.dbcurs.get_sourcefile_id(self.filename) 469 if id is None: 470 if ('*' not in self.filename) and ('?' not in self.filename) and ('?' not in self.filename): 471 self.filename += '*' if self.filename[-1] == '/' else '/*' 472 if not self.dbcurs.count_sourcefiles(self.filename): 473 self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 474 return self.error_page(404) 475 elif self.environ['REQUEST_METHOD'] != 'GET': 476 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')]) 477 return self.error_page(405) 478 else: 479 self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 480 return self.sourcefile_listing_page(self.filename) 481 else: 482 self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 483 return self.error_page(404) 484 elif self.environ['REQUEST_METHOD'] != 'GET': 485 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')]) 486 return self.error_page(405) 487 else: 488 self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 489 return self.sourcefile_page(id) 490 491 def sourcefile_listing_page(self, pattern): 492 if not pattern: 493 title = heading = 'All Source Files' 494 else: 495 heading = self.linked_title(pattern) 496 title = 'Source Files: ' + cgi.escape(pattern) 497 yield htmltmpl.SOURCEFILE_LIST_PROLOGUE.substitute( 498 assets=cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True), 499 title=title, 500 heading=heading).encode('utf-8') 501 for filename, machines in self.dbcurs.get_sourcefiles(pattern): 502 yield htmltmpl.SOURCEFILE_LIST_ROW.substitute( 503 sourcefile=self.linked_title(filename, True), 504 machines=cgi.escape('%d' % (machines, ))).encode('utf-8') 505 yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-sourcefiles').encode('utf-8') 506 507 def sourcefile_page(self, id): 508 yield htmltmpl.SOURCEFILE_PROLOGUE.substitute( 509 assets=cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True), 510 filename=cgi.escape(self.filename), 511 title=self.linked_title(self.filename)).encode('utf-8') 512 513 first = True 514 for machine_info in self.dbcurs.get_sourcefile_machines(id): 515 if first: 516 yield \ 517 '<table id="tbl-machines">\n' \ 518 ' <thead>\n' \ 519 ' <tr>\n' \ 520 ' <th>Short name</th>\n' \ 521 ' <th>Description</th>\n' \ 522 ' <th>Year</th>\n' \ 523 ' <th>Manufacturer</th>\n' \ 524 ' <th>Runnable</th>\n' \ 525 ' <th>Parent</th>\n' \ 526 ' </tr>\n' \ 527 ' </thead>\n' \ 528 ' <tbody>\n'.encode('utf-8') 529 first = False 530 yield self.machine_row(machine_info) 531 if first: 532 yield '<p>No machines found.</p>\n'.encode('utf-8') 533 else: 534 yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-machines').encode('utf-8') 535 536 yield '</body>\n</html>\n'.encode('utf-8') 537 538 def linked_title(self, filename, linkfinal=False): 539 parts = filename.split('/') 540 final = parts[-1] 541 del parts[-1] 542 uri = urlparse.urljoin(self.application_uri, 'sourcefile') 543 title = '' 544 for part in parts: 545 uri = urlparse.urljoin(uri + '/', urlquote(part)) 546 title += '<a href="{0}">{1}</a>/'.format(cgi.escape(uri, True), cgi.escape(part)) 547 if linkfinal: 548 uri = urlparse.urljoin(uri + '/', urlquote(final)) 549 return title + '<a href="{0}">{1}</a>'.format(cgi.escape(uri, True), cgi.escape(final)) 550 else: 551 return title + final 552 553 def machine_row(self, machine_info): 554 return (htmltmpl.SOURCEFILE_ROW_PARENT if machine_info['cloneof'] is None else htmltmpl.SOURCEFILE_ROW_CLONE).substitute( 555 machinehref=self.machine_href(machine_info['shortname']), 556 parenthref=self.machine_href(machine_info['cloneof'] or '__invalid'), 557 shortname=cgi.escape(machine_info['shortname']), 558 description=cgi.escape(machine_info['description']), 559 year=cgi.escape(machine_info['year'] or ''), 560 manufacturer=cgi.escape(machine_info['manufacturer'] or ''), 561 runnable=cgi.escape('Yes' if machine_info['runnable'] else 'No'), 562 parent=cgi.escape(machine_info['cloneof'] or '')).encode('utf-8') 563 564 565class SoftwareListHandler(QueryPageHandler): 566 def __init__(self, app, application_uri, environ, start_response, **kwargs): 567 super(SoftwareListHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs) 568 self.shortname = wsgiref.util.shift_path_info(environ) 569 self.software = wsgiref.util.shift_path_info(environ) 570 571 def __iter__(self): 572 if self.environ['PATH_INFO']: 573 self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 574 return self.error_page(404) 575 elif self.software and ('*' not in self.software) and ('?' not in self.software): 576 software_info = self.dbcurs.get_software_details(self.shortname, self.software).fetchone() 577 if not software_info: 578 self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 579 return self.error_page(404) 580 elif self.environ['REQUEST_METHOD'] != 'GET': 581 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')]) 582 return self.error_page(405) 583 else: 584 self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 585 return self.software_page(software_info) 586 elif self.software or (self.shortname and ('*' not in self.shortname) and ('?' not in self.shortname)): 587 softwarelist_info = self.dbcurs.get_softwarelist_details(self.shortname, self.software or None).fetchone() 588 if not softwarelist_info: 589 self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 590 return self.error_page(404) 591 elif self.environ['REQUEST_METHOD'] != 'GET': 592 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')]) 593 return self.error_page(405) 594 else: 595 self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 596 return self.softwarelist_page(softwarelist_info, self.software or None) 597 else: 598 if self.environ['REQUEST_METHOD'] != 'GET': 599 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')]) 600 return self.error_page(405) 601 else: 602 self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 603 return self.softwarelist_listing_page(self.shortname or None) 604 605 def softwarelist_listing_page(self, pattern): 606 if not pattern: 607 title = heading = 'All Software Lists' 608 else: 609 title = heading = 'Software Lists: ' + cgi.escape(pattern) 610 yield htmltmpl.SOFTWARELIST_LIST_PROLOGUE.substitute( 611 assets=cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True), 612 title=title, 613 heading=heading).encode('utf-8') 614 for shortname, description, total, supported, partiallysupported, unsupported in self.dbcurs.get_softwarelists(pattern): 615 yield htmltmpl.SOFTWARELIST_LIST_ROW.substitute( 616 href=self.softwarelist_href(shortname), 617 shortname=cgi.escape(shortname), 618 description=cgi.escape(description), 619 total=cgi.escape('%d' % (total, )), 620 supported=cgi.escape('%.1f%%' % (supported * 100.0 / (total or 1), )), 621 partiallysupported=cgi.escape('%.1f%%' % (partiallysupported * 100.0 / (total or 1), )), 622 unsupported=cgi.escape('%.1f%%' % (unsupported * 100.0 / (total or 1), ))).encode('utf-8') 623 yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-softwarelists').encode('utf-8') 624 625 def softwarelist_page(self, softwarelist_info, pattern): 626 if not pattern: 627 title = 'Software List: %s (%s)' % (cgi.escape(softwarelist_info['description']), cgi.escape(softwarelist_info['shortname'])) 628 heading = cgi.escape(softwarelist_info['description']) 629 else: 630 title = 'Software List: %s (%s): %s' % (cgi.escape(softwarelist_info['description']), cgi.escape(softwarelist_info['shortname']), cgi.escape(pattern)) 631 heading = '<a href="%s">%s</a>: %s' % (self.softwarelist_href(softwarelist_info['shortname']), cgi.escape(softwarelist_info['description']), cgi.escape(pattern)) 632 yield htmltmpl.SOFTWARELIST_PROLOGUE.substitute( 633 assets=cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True), 634 title=title, 635 heading=heading, 636 shortname=cgi.escape(softwarelist_info['shortname']), 637 total=cgi.escape('%d' % (softwarelist_info['total'], )), 638 supported=cgi.escape('%d' % (softwarelist_info['supported'], )), 639 supportedpc=cgi.escape('%.1f' % (softwarelist_info['supported'] * 100.0 / (softwarelist_info['total'] or 1), )), 640 partiallysupported=cgi.escape('%d' % (softwarelist_info['partiallysupported'], )), 641 partiallysupportedpc=cgi.escape('%.1f' % (softwarelist_info['partiallysupported'] * 100.0 / (softwarelist_info['total'] or 1), )), 642 unsupported=cgi.escape('%d' % (softwarelist_info['unsupported'], )), 643 unsupportedpc=cgi.escape('%.1f' % (softwarelist_info['unsupported'] * 100.0 / (softwarelist_info['total'] or 1), ))).encode('utf-8') 644 645 first = True 646 for machine_info in self.dbcurs.get_softwarelist_machines(softwarelist_info['id']): 647 if first: 648 yield htmltmpl.SOFTWARELIST_MACHINE_TABLE_HEADER.substitute().encode('utf-8') 649 first = False 650 yield htmltmpl.SOFTWARELIST_MACHINE_TABLE_ROW.substitute( 651 machinehref=self.machine_href(machine_info['shortname']), 652 shortname=cgi.escape(machine_info['shortname']), 653 description=cgi.escape(machine_info['description']), 654 year=cgi.escape(machine_info['year'] or ''), 655 manufacturer=cgi.escape(machine_info['manufacturer'] or ''), 656 status=cgi.escape(machine_info['status'])).encode('utf-8') 657 if not first: 658 yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-machines').encode('utf-8') 659 660 first = True 661 for software_info in self.dbcurs.get_softwarelist_software(softwarelist_info['id'], self.software or None): 662 if first: 663 yield htmltmpl.SOFTWARELIST_SOFTWARE_TABLE_HEADER.substitute().encode('utf-8') 664 first = False 665 yield self.software_row(software_info) 666 if first: 667 yield '<p>No software found.</p>\n'.encode('utf-8') 668 else: 669 yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-software').encode('utf-8') 670 671 yield '</body>\n</html>\n'.encode('utf-8') 672 673 def software_page(self, software_info): 674 yield htmltmpl.SOFTWARE_PROLOGUE.substitute( 675 assets=cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True), 676 title=cgi.escape(software_info['description']), 677 heading=cgi.escape(software_info['description']), 678 softwarelisthref=self.softwarelist_href(self.shortname), 679 softwarelistdescription=cgi.escape(software_info['softwarelistdescription']), 680 softwarelist=cgi.escape(self.shortname), 681 shortname=cgi.escape(software_info['shortname']), 682 year=cgi.escape(software_info['year']), 683 publisher=cgi.escape(software_info['publisher'])).encode('utf-8') 684 if software_info['parent'] is not None: 685 yield (' <tr><th>Parent:</th><td><a href="%s">%s</a></td>\n' % (self.software_href(software_info['parentsoftwarelist'], software_info['parent']), cgi.escape(software_info['parentdescription']))).encode('utf-8') 686 yield (' <tr><th>Supported:</th><td>%s</td>\n' % (self.format_supported(software_info['supported']), )).encode('utf-8') 687 for name, value in self.dbcurs.get_software_info(software_info['id']): 688 yield (' <tr><th>%s:</th><td>%s</td>\n' % (cgi.escape(name), cgi.escape(value))).encode('utf-8') 689 yield '</table>\n\n'.encode('utf-8') 690 691 first = True 692 for clone_info in self.dbcurs.get_software_clones(software_info['id']): 693 if first: 694 yield htmltmpl.SOFTWARE_CLONES_PROLOGUE.substitute().encode('utf-8') 695 first = False 696 yield self.clone_row(clone_info) 697 if not first: 698 yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-clones').encode('utf-8') 699 700 parts = self.dbcurs.get_software_parts(software_info['id']).fetchall() 701 first = True 702 for id, partname, interface, part_id in parts: 703 if first: 704 yield '<h2>Parts</h2>\n'.encode('utf-8') 705 first = False 706 yield htmltmpl.SOFTWARE_PART_PROLOGUE.substitute( 707 heading=cgi.escape(('%s (%s)' % (part_id, partname)) if part_id is not None else partname), 708 shortname=cgi.escape(partname), 709 interface=cgi.escape(interface)).encode('utf-8') 710 for name, value in self.dbcurs.get_softwarepart_features(id): 711 yield (' <tr><th>%s:</th><td>%s</td>\n' % (cgi.escape(name), cgi.escape(value))).encode('utf-8') 712 yield '</table>\n\n'.encode('utf-8') 713 714 yield '</body>\n</html>\n'.encode('utf-8') 715 716 def software_row(self, software_info): 717 parent = software_info['parent'] 718 return htmltmpl.SOFTWARELIST_SOFTWARE_ROW.substitute( 719 softwarehref=self.software_href(self.shortname, software_info['shortname']), 720 shortname=cgi.escape(software_info['shortname']), 721 description=cgi.escape(software_info['description']), 722 year=cgi.escape(software_info['year']), 723 publisher=cgi.escape(software_info['publisher']), 724 supported=self.format_supported(software_info['supported']), 725 parts=cgi.escape('%d' % (software_info['parts'], )), 726 baddumps=cgi.escape('%d' % (software_info['baddumps'], )), 727 parent='<a href="%s">%s</a>' % (self.software_href(software_info['parentsoftwarelist'], parent), cgi.escape(parent)) if parent is not None else '').encode('utf-8') 728 729 def clone_row(self, clone_info): 730 return htmltmpl.SOFTWARE_CLONES_ROW.substitute( 731 href=self.software_href(clone_info['softwarelist'], clone_info['shortname']), 732 shortname=cgi.escape(clone_info['shortname']), 733 description=cgi.escape(clone_info['description']), 734 year=cgi.escape(clone_info['year']), 735 publisher=cgi.escape(clone_info['publisher']), 736 supported=self.format_supported(clone_info['supported'])).encode('utf-8') 737 738 @staticmethod 739 def format_supported(supported): 740 return 'Yes' if supported == 0 else 'Partial' if supported == 1 else 'No' 741 742 743class RomIdentHandler(QueryPageHandler): 744 def __init__(self, app, application_uri, environ, start_response, **kwargs): 745 super(QueryPageHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs) 746 self.dbcurs = app.dbconn.cursor() 747 748 def __iter__(self): 749 if self.environ['PATH_INFO']: 750 self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 751 return self.error_page(404) 752 elif self.environ['REQUEST_METHOD'] != 'GET': 753 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')]) 754 return self.error_page(405) 755 else: 756 self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 757 return self.form_page() 758 759 def form_page(self): 760 yield htmltmpl.ROMIDENT_PAGE.substitute( 761 app=self.js_escape(cgi.escape(self.application_uri, True)), 762 assets=self.js_escape(cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True))).encode('utf-8') 763 764 765class BiosRpcHandler(MachineRpcHandlerBase): 766 def data_page(self, machine): 767 result = { } 768 for name, description, isdefault in self.dbcurs.get_biossets(machine): 769 result[name] = { 'description': description, 'isdefault': True if isdefault else False } 770 yield json.dumps(result).encode('utf-8') 771 772 773class FlagsRpcHandler(MachineRpcHandlerBase): 774 def data_page(self, machine): 775 yield json.dumps(self.flags_data(machine)).encode('utf-8') 776 777 778class SlotsRpcHandler(MachineRpcHandlerBase): 779 def data_page(self, machine): 780 yield json.dumps(self.slot_data(machine)).encode('utf-8') 781 782 783class SoftwareListsRpcHandler(MachineRpcHandlerBase): 784 def data_page(self, machine): 785 yield json.dumps(self.softwarelist_data(machine)).encode('utf-8') 786 787 788class RomDumpsRpcHandler(QueryPageHandler): 789 def __init__(self, app, application_uri, environ, start_response, **kwargs): 790 super(RomDumpsRpcHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs) 791 792 def __iter__(self): 793 if self.environ['PATH_INFO']: 794 self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 795 return self.error_page(404) 796 elif self.environ['REQUEST_METHOD'] != 'GET': 797 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')]) 798 return self.error_page(405) 799 else: 800 try: 801 args = urlparse.parse_qs(self.environ['QUERY_STRING'], keep_blank_values=True, strict_parsing=True) 802 crc = args.get('crc') 803 sha1 = args.get('sha1') 804 if (len(args) == 2) and (crc is not None) and (len(crc) == 1) and (sha1 is not None) and (len(sha1) == 1): 805 crc = int(crc[0], 16) 806 sha1 = sha1[0] 807 self.start_response('200 OK', [('Content-type', 'application/json; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 808 return self.data_page(crc, sha1) 809 except BaseException as e: 810 pass 811 self.start_response('500 %s' % (self.STATUS_MESSAGE[500], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')]) 812 return self.error_page(500) 813 814 def data_page(self, crc, sha1): 815 machines = { } 816 for shortname, description, label, bad in self.dbcurs.get_rom_dumps(crc, sha1): 817 machine = machines.get(shortname) 818 if machine is None: 819 machine = { 'description': description, 'matches': [ ] } 820 machines[shortname] = machine 821 machine['matches'].append({ 'name': label, 'bad': bool(bad) }) 822 823 software = { } 824 for softwarelist, softwarelistdescription, shortname, description, part, part_id, label, bad in self.dbcurs.get_software_rom_dumps(crc, sha1): 825 listinfo = software.get(softwarelist) 826 if listinfo is None: 827 listinfo = { 'description': softwarelistdescription, 'software': { } } 828 software[softwarelist] = listinfo 829 softwareinfo = listinfo['software'].get(shortname) 830 if softwareinfo is None: 831 softwareinfo = { 'description': description, 'parts': { } } 832 listinfo['software'][shortname] = softwareinfo 833 partinfo = softwareinfo['parts'].get(part) 834 if partinfo is None: 835 partinfo = { 'matches': [ ] } 836 if part_id is not None: 837 partinfo['description'] = part_id 838 softwareinfo['parts'][part] = partinfo 839 partinfo['matches'].append({ 'name': label, 'bad': bool(bad) }) 840 841 result = { 'machines': machines, 'software': software } 842 yield json.dumps(result).encode('utf-8') 843 844 845class DiskDumpsRpcHandler(QueryPageHandler): 846 def __init__(self, app, application_uri, environ, start_response, **kwargs): 847 super(DiskDumpsRpcHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs) 848 849 def __iter__(self): 850 if self.environ['PATH_INFO']: 851 self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 852 return self.error_page(404) 853 elif self.environ['REQUEST_METHOD'] != 'GET': 854 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')]) 855 return self.error_page(405) 856 else: 857 try: 858 args = urlparse.parse_qs(self.environ['QUERY_STRING'], keep_blank_values=True, strict_parsing=True) 859 sha1 = args.get('sha1') 860 if (len(args) == 1) and (sha1 is not None) and (len(sha1) == 1): 861 sha1 = sha1[0] 862 self.start_response('200 OK', [('Content-type', 'application/json; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) 863 return self.data_page(sha1) 864 except BaseException as e: 865 pass 866 self.start_response('500 %s' % (self.STATUS_MESSAGE[500], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')]) 867 return self.error_page(500) 868 869 def data_page(self, sha1): 870 machines = { } 871 for shortname, description, label, bad in self.dbcurs.get_disk_dumps(sha1): 872 machine = machines.get(shortname) 873 if machine is None: 874 machine = { 'description': description, 'matches': [ ] } 875 machines[shortname] = machine 876 machine['matches'].append({ 'name': label, 'bad': bool(bad) }) 877 878 software = { } 879 for softwarelist, softwarelistdescription, shortname, description, part, part_id, label, bad in self.dbcurs.get_software_disk_dumps(sha1): 880 listinfo = software.get(softwarelist) 881 if listinfo is None: 882 listinfo = { 'description': softwarelistdescription, 'software': { } } 883 software[softwarelist] = listinfo 884 softwareinfo = listinfo['software'].get(shortname) 885 if softwareinfo is None: 886 softwareinfo = { 'description': description, 'parts': { } } 887 listinfo['software'][shortname] = softwareinfo 888 partinfo = softwareinfo['parts'].get(part) 889 if partinfo is None: 890 partinfo = { 'matches': [ ] } 891 if part_id is not None: 892 partinfo['description'] = part_id 893 softwareinfo['parts'][part] = partinfo 894 partinfo['matches'].append({ 'name': label, 'bad': bool(bad) }) 895 896 result = { 'machines': machines, 'software': software } 897 yield json.dumps(result).encode('utf-8') 898 899 900class MiniMawsApp(object): 901 JS_ESCAPE = re.compile('([\"\'\\\\])') 902 RPC_SERVICES = { 903 'bios': BiosRpcHandler, 904 'flags': FlagsRpcHandler, 905 'slots': SlotsRpcHandler, 906 'softwarelists': SoftwareListsRpcHandler, 907 'romdumps': RomDumpsRpcHandler, 908 'diskdumps': DiskDumpsRpcHandler } 909 910 def __init__(self, dbfile, **kwargs): 911 super(MiniMawsApp, self).__init__(**kwargs) 912 self.dbconn = dbaccess.QueryConnection(dbfile) 913 self.assetsdir = os.path.join(os.path.dirname(inspect.getfile(self.__class__)), 'assets') 914 if not mimetypes.inited: 915 mimetypes.init() 916 917 def __call__(self, environ, start_response): 918 application_uri = wsgiref.util.application_uri(environ) 919 if application_uri[-1] != '/': 920 application_uri += '/' 921 module = wsgiref.util.shift_path_info(environ) 922 if module == 'machine': 923 return MachineHandler(self, application_uri, environ, start_response) 924 elif module == 'sourcefile': 925 return SourceFileHandler(self, application_uri, environ, start_response) 926 elif module == 'softwarelist': 927 return SoftwareListHandler(self, application_uri, environ, start_response) 928 elif module == 'romident': 929 return RomIdentHandler(self, application_uri, environ, start_response) 930 elif module == 'static': 931 return AssetHandler(self.assetsdir, self, application_uri, environ, start_response) 932 elif module == 'rpc': 933 service = wsgiref.util.shift_path_info(environ) 934 if not service: 935 return ErrorPageHandler(403, self, application_uri, environ, start_response) 936 elif service in self.RPC_SERVICES: 937 return self.RPC_SERVICES[service](self, application_uri, environ, start_response) 938 else: 939 return ErrorPageHandler(404, self, application_uri, environ, start_response) 940 elif not module: 941 return ErrorPageHandler(403, self, application_uri, environ, start_response) 942 else: 943 return ErrorPageHandler(404, self, application_uri, environ, start_response) 944 945 def js_escape(self, str): 946 return self.JS_ESCAPE.sub('\\\\\\1', str).replace('\0', '\\0') 947