minimaws: fill in software list pages, load software list ROM information

This commit is contained in:
Vas Crabb 2019-12-15 21:38:54 +11:00
parent 601e0668ba
commit 202d06ffb0
6 changed files with 448 additions and 33 deletions

View File

@ -8,7 +8,7 @@ function sort_table(tbl, col, dir, numeric)
function (x, y)
{
if (numeric)
return dir * (parseInt(x.cells[col].textContent) - parseInt(y.cells[col].textContent));
return dir * (parseFloat(x.cells[col].textContent) - parseFloat(y.cells[col].textContent));
else
return dir * x.cells[col].textContent.localeCompare(y.cells[col].textContent);
})

View File

@ -142,7 +142,12 @@ function get_machine_table(shortname, description)
else
{
var div = document.getElementById('div-machines');
var heading = div.appendChild(document.createElement('h2'));
if (!div.hasChildNodes())
{
var heading = div.appendChild(document.createElement('h2'));
heading.textContent = 'Machines';
}
var heading = div.appendChild(document.createElement('h3'));
var link = heading.appendChild(document.createElement('a'));
link.textContent = description;
link.setAttribute('href', appurl + 'machine/' + encodeURIComponent(shortname));
@ -164,17 +169,17 @@ function request_dumps(name, group, crc, sha1, url, progress)
if (req.status == 200)
{
var machines = Object.create(null);
var matched = Object.keys(req.response);
var matched = Object.keys(req.response.machines);
if (matched.length > 0)
{
Object.keys(machine_info).forEach(
function (shortname)
{
var table = machine_info[shortname];
if (Object.hasOwnProperty.call(req.response, shortname))
if (Object.hasOwnProperty.call(req.response.machines, shortname))
{
machines[shortname] = req.response[shortname].matches;
add_matches(table, group.names, req.response[shortname].matches);
machines[shortname] = req.response.machines[shortname].matches;
add_matches(table, group.names, req.response.machines[shortname].matches);
}
else
{
@ -186,9 +191,9 @@ function request_dumps(name, group, crc, sha1, url, progress)
{
if (!Object.hasOwnProperty.call(machine_info, shortname))
{
var info = req.response[shortname];
var info = req.response.machines[shortname];
var table = get_machine_table(shortname, info.description);
machines[shortname] = req.response[shortname].matches;
machines[shortname] = req.response.machines[shortname].matches;
add_matches(table, group.names, info.matches);
}
});

View File

@ -221,6 +221,15 @@ class SchemaQueries(object):
' FOREIGN KEY (machine) REFERENCES machine (id),\n' \
' FOREIGN KEY (rom) REFERENCES rom (id),\n' \
' UNIQUE (machine, rom, name))'
CREATE_SOFTWAREROMDUMP = \
'CREATE TABLE softwareromdump (\n' \
' part INTEGER NOT NULL,\n' \
' rom INTEGER NOT NULL,\n' \
' name TEXT NOT NULL,\n' \
' bad INTEGER NOT NULL,\n' \
' FOREIGN KEY (part) REFERENCES softwarepart (id),\n' \
' FOREIGN KEY (rom) REFERENCES rom (id),\n' \
' UNIQUE (part, rom, name))'
CREATE_DISK = \
'CREATE TABLE disk (\n' \
' id INTEGER PRIMARY KEY,\n' \
@ -235,6 +244,15 @@ class SchemaQueries(object):
' FOREIGN KEY (machine) REFERENCES machine (id),\n' \
' FOREIGN KEY (disk) REFERENCES disk (id),\n' \
' UNIQUE (machine, disk, name))'
CREATE_SOFTWAREDISKDUMP = \
'CREATE TABLE softwarediskdump (\n' \
' part INTEGER NOT NULL,\n' \
' disk INTEGER NOT NULL,\n' \
' name TEXT NOT NULL,\n' \
' bad INTEGER NOT NULL,\n' \
' FOREIGN KEY (part) REFERENCES softwarepart (id),\n' \
' FOREIGN KEY (disk) REFERENCES disk (id),\n' \
' UNIQUE (part, disk, name))'
CREATE_TEMPORARY_DEVICEREFERENCE = 'CREATE TEMPORARY TABLE temp_devicereference (id INTEGER PRIMARY KEY, machine INTEGER NOT NULL, device TEXT NOT NULL, UNIQUE (machine, device))'
CREATE_TEMPORARY_SLOTOPTION = 'CREATE TEMPORARY TABLE temp_slotoption (id INTEGER PRIMARY KEY, slot INTEGER NOT NULL, device TEXT NOT NULL, name TEXT NOT NULL)'
@ -275,8 +293,16 @@ class SchemaQueries(object):
INDEX_SOFTWAREPARTFEATURE_FEATURETYPE_VALUE_PART = 'CREATE INDEX softwarepartfeature_featuretype_value_part ON softwarepartfeature (featuretype ASC, value ASC, part ASC)'
INDEX_ROMDUMP_ROM = 'CREATE INDEX romdump_rom ON romdump (rom ASC)'
INDEX_ROMDUMP_MACHINE_BAD = 'CREATE INDEX romdump_machine_bad ON romdump (machine ASC, bad ASC)'
INDEX_SOFTWAREROMDUMP_ROM = 'CREATE INDEX softwareromdump_rom ON softwareromdump (rom ASC)'
INDEX_SOFTWAREROMDUMP_PART_BAD = 'CREATE INDEX softwareromdump_part_bad ON softwareromdump (part ASC, bad ASC)'
INDEX_DISKDUMP_DISK = 'CREATE INDEX diskdump_disk ON diskdump (disk ASC)'
INDEX_DISKDUMP_MACHINE_BAD = 'CREATE INDEX diskdump_machine_bad ON diskdump (machine ASC, bad ASC)'
INDEX_SOFTWAREDISKDUMP_DISK = 'CREATE INDEX softwarediskdump_disk ON softwarediskdump (disk ASC)'
INDEX_SOFTWAREDISKDUMP_PART_BAD = 'CREATE INDEX softwarediskdump_part_bad ON softwarediskdump (part ASC, bad ASC)'
DROP_MACHINE_ISDEVICE_SHORTNAME = 'DROP INDEX IF EXISTS machine_isdevice_shortname'
DROP_MACHINE_ISDEVICE_DESCRIPTION = 'DROP INDEX IF EXISTS machine_isdevice_description'
@ -309,8 +335,16 @@ class SchemaQueries(object):
DROP_SOFTWAREPARTFEATURE_FEATURETYPE_VALUE_PART = 'DROP INDEX IF EXISTS softwarepartfeature_featuretype_value_part'
DROP_ROMDUMP_ROM = 'DROP INDEX IF EXISTS romdump_rom'
DROP_ROMDUMP_MACHINE_BAD = 'DROP INDEX IF EXISTS romdump_machine_bad'
DROP_SOFTWAREROMDUMP_ROM = 'DROP INDEX IF EXISTS softwareromdump_rom'
DROP_SOFTWAREROMDUMP_PART_BAD = 'DROP INDEX IF EXISTS softwareromdump_part_bad'
DROP_DISKDUMP_DISK = 'DROP INDEX IF EXISTS diskdump_disk'
DROP_DISKDUMP_MACHINE_BAD = 'DROP INDEX IF EXISTS diskdump_machine_bad'
DROP_SOFTWAREDISKDUMP_DISK = 'DROP INDEX IF EXISTS softwarediskdump_disk'
DROP_SOFTWAREDISKDUMP_PART_BAD = 'DROP INDEX IF EXISTS softwarediskdump_part_bad'
CREATE_TABLES = (
CREATE_FEATURETYPE,
@ -342,8 +376,10 @@ class SchemaQueries(object):
CREATE_SOFTWAREPARTFEATURE,
CREATE_ROM,
CREATE_ROMDUMP,
CREATE_SOFTWAREROMDUMP,
CREATE_DISK,
CREATE_DISKDUMP)
CREATE_DISKDUMP,
CREATE_SOFTWAREDISKDUMP)
CREATE_TEMPORARY_TABLES = (
CREATE_TEMPORARY_DEVICEREFERENCE,
@ -372,7 +408,13 @@ class SchemaQueries(object):
INDEX_SOFTWAREPART_INTERFACE_SOFTWARE,
INDEX_SOFTWAREPARTFEATURE_FEATURETYPE_VALUE_PART,
INDEX_ROMDUMP_ROM,
INDEX_DISKDUMP_DISK)
INDEX_ROMDUMP_MACHINE_BAD,
INDEX_SOFTWAREROMDUMP_ROM,
INDEX_SOFTWAREROMDUMP_PART_BAD,
INDEX_DISKDUMP_DISK,
INDEX_DISKDUMP_MACHINE_BAD,
INDEX_SOFTWAREDISKDUMP_DISK,
INDEX_SOFTWAREDISKDUMP_PART_BAD)
DROP_INDEXES = (
DROP_MACHINE_ISDEVICE_SHORTNAME,
@ -396,7 +438,13 @@ class SchemaQueries(object):
DROP_SOFTWAREPART_INTERFACE_SOFTWARE,
DROP_SOFTWAREPARTFEATURE_FEATURETYPE_VALUE_PART,
DROP_ROMDUMP_ROM,
DROP_DISKDUMP_DISK)
DROP_ROMDUMP_MACHINE_BAD,
DROP_SOFTWAREROMDUMP_ROM,
DROP_SOFTWAREROMDUMP_PART_BAD,
DROP_DISKDUMP_DISK,
DROP_DISKDUMP_MACHINE_BAD,
DROP_SOFTWAREDISKDUMP_DISK,
DROP_SOFTWAREDISKDUMP_PART_BAD)
class UpdateQueries(object):
@ -426,8 +474,10 @@ class UpdateQueries(object):
ADD_SOFTWAREPARTFEATURE = 'INSERT INTO softwarepartfeature (part, featuretype, value) SELECT ?, id, ? FROM softwarepartfeaturetype WHERE name = ?'
ADD_ROM = 'INSERT OR IGNORE INTO rom (crc, sha1) VALUES (?, ?)'
ADD_ROMDUMP = 'INSERT OR IGNORE INTO romdump (machine, rom, name, bad) SELECT ?, id, ?, ? FROM rom WHERE crc = ? AND sha1 = ?'
ADD_SOFTWAREROMDUMP = 'INSERT OR IGNORE INTO softwareromdump (part, rom, name, bad) SELECT ?, id, ?, ? FROM rom WHERE crc = ? AND sha1 = ?'
ADD_DISK = 'INSERT OR IGNORE INTO disk (sha1) VALUES (?)'
ADD_DISKDUMP = 'INSERT OR IGNORE INTO diskdump (machine, disk, name, bad) SELECT ?, id, ?, ? FROM disk WHERE sha1 = ?'
ADD_SOFTWAREDISKDUMP = 'INSERT OR IGNORE INTO softwarediskdump (part, disk, name, bad) SELECT ?, id, ?, ? FROM disk WHERE sha1 = ?'
ADD_TEMPORARY_DEVICEREFERENCE = 'INSERT OR IGNORE INTO temp_devicereference (machine, device) VALUES (?, ?)'
ADD_TEMPORARY_SLOTOPTION = 'INSERT INTO temp_slotoption (slot, device, name) VALUES (?, ?, ?)'
@ -545,7 +595,7 @@ class QueryCursor(object):
def get_machine_id(self, machine):
return (self.dbcurs.execute('SELECT id FROM machine WHERE shortname = ?', (machine, )).fetchone() or (None, ))[0]
def get_machine_info(self, machine):
def get_machine_details(self, machine):
return self.dbcurs.execute(
'SELECT machine.id AS id, machine.description AS description, machine.isdevice AS isdevice, machine.runnable AS runnable, sourcefile.filename AS sourcefile, system.year AS year, system.manufacturer AS manufacturer, cloneof.parent AS cloneof, romof.parent AS romof ' \
'FROM machine JOIN sourcefile ON machine.sourcefile = sourcefile.id LEFT JOIN system ON machine.id = system.id LEFT JOIN cloneof ON system.id = cloneof.id LEFT JOIN romof ON system.id = romof.id ' \
@ -644,20 +694,85 @@ class QueryCursor(object):
'ORDER BY ramoption.size',
(machine, ))
def get_softwarelist_id(self, shortname):
return (self.dbcurs.execute('SELECT id FROM softwarelist WHERE shortname = ?', (shortname, )).fetchone() or (None, ))[0]
def get_softwarelist_details(self, shortname, pattern):
if pattern is not None:
return self.dbcurs.execute(
'SELECT softwarelist.id AS id, softwarelist.shortname AS shortname, softwarelist.description AS description, COUNT(software.id) AS total, COUNT(CASE software.supported WHEN 0 THEN 1 ELSE NULL END) AS supported, COUNT(CASE software.supported WHEN 1 THEN 1 ELSE NULL END) AS partiallysupported, COUNT(CASE software.supported WHEN 2 THEN 1 ELSE NULL END) AS unsupported ' \
'FROM softwarelist LEFT JOIN software ON softwarelist.id = software.softwarelist ' \
'WHERE softwarelist.shortname = ? AND software.shortname GLOB ? ' \
'GROUP BY softwarelist.id',
(shortname, pattern))
else:
return self.dbcurs.execute(
'SELECT softwarelist.id AS id, softwarelist.shortname AS shortname, softwarelist.description AS description, COUNT(software.id) AS total, COUNT(CASE software.supported WHEN 0 THEN 1 ELSE NULL END) AS supported, COUNT(CASE software.supported WHEN 1 THEN 1 ELSE NULL END) AS partiallysupported, COUNT(CASE software.supported WHEN 2 THEN 1 ELSE NULL END) AS unsupported ' \
'FROM softwarelist LEFT JOIN software ON softwarelist.id = software.softwarelist ' \
'WHERE softwarelist.shortname = ? ' \
'GROUP BY softwarelist.id',
(shortname, ))
def get_softwarelist_software(self, id, pattern):
if pattern is not None:
return self.dbcurs.execute(
'SELECT software.shortname AS shortname, software.description AS description, software.year AS year, software.publisher AS publisher, software.supported AS supported, COUNT(*) AS parts ' \
'FROM software JOIN softwarepart ON software.id = softwarepart.software ' \
'WHERE software.softwarelist = ? AND software.shortname GLOB ? ' \
'GROUP BY software.id',
(id, pattern))
else:
return self.dbcurs.execute(
'SELECT software.shortname AS shortname, software.description AS description, software.year AS year, software.publisher AS publisher, software.supported AS supported, COUNT(*) AS parts ' \
'FROM software JOIN softwarepart ON software.id = softwarepart.software ' \
'WHERE software.softwarelist = ? ' \
'GROUP BY software.id',
(id, ))
def get_softwarelists(self, pattern):
if pattern is not None:
return self.dbcurs.execute(
'SELECT softwarelist.shortname AS shortname, softwarelist.description AS description, COUNT(*) AS total, COUNT(CASE software.supported WHEN 0 THEN 1 ELSE NULL END) AS supported, COUNT(CASE software.supported WHEN 1 THEN 1 ELSE NULL END) AS partiallysupported, COUNT(CASE software.supported WHEN 2 THEN 1 ELSE NULL END) AS unsupported ' \
'SELECT softwarelist.shortname AS shortname, softwarelist.description AS description, COUNT(software.id) AS total, COUNT(CASE software.supported WHEN 0 THEN 1 ELSE NULL END) AS supported, COUNT(CASE software.supported WHEN 1 THEN 1 ELSE NULL END) AS partiallysupported, COUNT(CASE software.supported WHEN 2 THEN 1 ELSE NULL END) AS unsupported ' \
'FROM softwarelist LEFT JOIN software ON softwarelist.id = software.softwarelist ' \
'WHERE softwarelist.shortname GLOB ? ' \
'GROUP BY softwarelist.id',
(pattern, ))
else:
return self.dbcurs.execute(
'SELECT softwarelist.shortname AS shortname, softwarelist.description AS description, COUNT(*) AS total, COUNT(CASE software.supported WHEN 0 THEN 1 ELSE NULL END) AS supported, COUNT(CASE software.supported WHEN 1 THEN 1 ELSE NULL END) AS partiallysupported, COUNT(CASE software.supported WHEN 2 THEN 1 ELSE NULL END) AS unsupported ' \
'SELECT softwarelist.shortname AS shortname, softwarelist.description AS description, COUNT(software.id) AS total, COUNT(CASE software.supported WHEN 0 THEN 1 ELSE NULL END) AS supported, COUNT(CASE software.supported WHEN 1 THEN 1 ELSE NULL END) AS partiallysupported, COUNT(CASE software.supported WHEN 2 THEN 1 ELSE NULL END) AS unsupported ' \
'FROM softwarelist LEFT JOIN software ON softwarelist.id = software.softwarelist ' \
'GROUP BY softwarelist.id')
def get_software_details(self, softwarelist, software):
return self.dbcurs.execute(
'SELECT software.id AS id, software.shortname AS shortname, software.supported AS supported, software.description AS description, software.year AS year, software.publisher AS publisher, softwarelist.shortname AS softwarelist, softwarelist.description AS softwarelistdescription ' \
'FROM software LEFT JOIN softwarelist ON software.softwarelist = softwarelist.id ' \
'WHERE software.softwarelist = (SELECT id FROM softwarelist WHERE shortname = ?) AND software.shortname = ?',
(softwarelist, software))
def get_software_info(self, software):
return self.dbcurs.execute(
'SELECT softwareinfotype.name AS name, softwareinfo.value AS value ' \
'FROM softwareinfo JOIN softwareinfotype ON softwareinfo.infotype = softwareinfotype.id ' \
'WHERE softwareinfo.software = ? ' \
'ORDER BY softwareinfotype.name ASC, softwareinfo.value ASC',
(software, ))
def get_software_parts(self, software):
return self.dbcurs.execute(
'SELECT softwarepart.id AS id, softwarepart.shortname AS shortname, softwarepart.interface AS interface, softwarepartfeature.value AS part_id ' \
'FROM softwarepart LEFT JOIN softwarepartfeature ON softwarepart.id = softwarepartfeature.part AND softwarepartfeature.featuretype = (SELECT id FROM softwarepartfeaturetype WHERE name = \'part_id\') ' \
'WHERE softwarepart.software = ?',
(software, ))
def get_softwarepart_features(self, part):
return self.dbcurs.execute(
'SELECT softwarepartfeaturetype.name AS name, softwarepartfeature.value AS value ' \
'FROM softwarepartfeature LEFT JOIN softwarepartfeaturetype ON softwarepartfeature.featuretype = softwarepartfeaturetype.id ' \
'WHERE softwarepartfeature.part = ? '
'ORDER BY softwarepartfeaturetype.name ASC',
(part, ))
def get_rom_dumps(self, crc, sha1):
return self.dbcurs.execute(
'SELECT machine.shortname AS shortname, machine.description AS description, romdump.name AS label, romdump.bad AS bad ' \
@ -665,6 +780,13 @@ class QueryCursor(object):
'WHERE romdump.rom = (SELECT id FROM rom WHERE crc = ? AND sha1 = ?)',
(crc, sha1))
def get_software_rom_dumps(self, crc, sha1):
return self.dbcurs.execute(
'SELECT softwarelist.shortname AS softwarelist, softwarelist.description AS softwarelistdescription, software.shortname AS shortname, software.description AS description, softwarepart.shortname AS part, softwarepartfeature.value AS part_id, softwareromdump.name AS label, softwareromdump.bad AS bad ' \
'FROM softwareromdump LEFT JOIN softwarepart ON softwareromdump.part = softwarepart.id LEFT JOIN softwarepartfeature ON softwarepart.id = softwarepartfeature.part AND softwarepartfeature.featuretype = (SELECT id FROM softwarepartfeaturetype WHERE name = \'part_id\') LEFT JOIN software ON softwarepart.software = software.id LEFT JOIN softwarelist ON software.softwarelist = softwarelist.id ' \
'WHERE softwareromdump.rom = (SELECT id FROM rom WHERE crc = ? AND sha1 = ?)',
(crc, sha1))
def get_disk_dumps(self, sha1):
return self.dbcurs.execute(
'SELECT machine.shortname AS shortname, machine.description AS description, diskdump.name AS label, diskdump.bad AS bad ' \
@ -672,6 +794,13 @@ class QueryCursor(object):
'WHERE diskdump.disk = (SELECT id FROM disk WHERE sha1 = ?)',
(sha1, ))
def get_software_disk_dumps(self, sha1):
return self.dbcurs.execute(
'SELECT softwarelist.shortname AS softwarelist, softwarelist.description AS softwarelistdescription, software.shortname AS shortname, software.description AS description, softwarepart.shortname AS part, softwarepartfeature.value AS part_id, softwarediskdump.name AS label, softwarediskdump.bad AS bad ' \
'FROM softwarediskdump LEFT JOIN softwarepart ON softwarediskdump.part = softwarepart.id LEFT JOIN softwarepartfeature ON softwarepart.id = softwarepartfeature.part AND softwarepartfeature.featuretype = (SELECT id FROM softwarepartfeaturetype WHERE name = \'part_id\') LEFT JOIN software ON softwarepart.software = software.id LEFT JOIN softwarelist ON software.softwarelist = softwarelist.id ' \
'WHERE softwarediskdump.disk = (SELECT id FROM disk WHERE sha1 = ?)',
(sha1, ))
class UpdateCursor(object):
def __init__(self, dbconn, **kwargs):
@ -791,6 +920,10 @@ class UpdateCursor(object):
self.dbcurs.execute(UpdateQueries.ADD_ROMDUMP, (machine, name, 1 if bad else 0, crc, sha1))
return self.dbcurs.lastrowid
def add_softwareromdump(self, part, name, crc, sha1, bad):
self.dbcurs.execute(UpdateQueries.ADD_SOFTWAREROMDUMP, (part, name, 1 if bad else 0, crc, sha1))
return self.dbcurs.lastrowid
def add_disk(self, sha1):
self.dbcurs.execute(UpdateQueries.ADD_DISK, (sha1, ))
return self.dbcurs.lastrowid
@ -799,6 +932,10 @@ class UpdateCursor(object):
self.dbcurs.execute(UpdateQueries.ADD_DISKDUMP, (machine, name, 1 if bad else 0, sha1))
return self.dbcurs.lastrowid
def add_softwarediskdump(self, part, name, sha1, bad):
self.dbcurs.execute(UpdateQueries.ADD_SOFTWAREDISKDUMP, (part, name, 1 if bad else 0, sha1))
return self.dbcurs.lastrowid
class QueryConnection(object):
def __init__(self, database, **kwargs):

View File

@ -165,6 +165,85 @@ SOURCEFILE_LIST_ROW = string.Template(
' </tr>\n')
SOFTWARE_PROLOGUE = string.Template(
'<!DOCTYPE html>\n' \
'<html>\n' \
'<head>\n' \
' <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n' \
' <meta http-equiv="Content-Style-Type" content="text/css">\n' \
' <meta http-equiv="Content-Script-Type" content="text/javascript">\n' \
' <link rel="stylesheet" type="text/css" href="${assets}/style.css">\n' \
' <script type="text/javascript">var assetsurl="${assets}"</script>\n' \
' <script type="text/javascript" src="${assets}/common.js"></script>\n' \
' <title>${title}</title>\n' \
'</head>\n' \
'<body>\n' \
'<h1>${heading}</h1>\n' \
'<table class="sysinfo">\n' \
' <tr><th>Software list:</th><td><a href="${softwarelisthref}">${softwarelistdescription} (${softwarelist})</a></td></tr>\n' \
' <tr><th>Short name:</th><td>${shortname}</td></tr>\n' \
' <tr><th>Year:</th><td>${year}</td></tr>\n' \
' <tr><th>Publisher:</th><td>${publisher}</td></tr>\n' \
' <tr><th>Suported:</th><td>${supported}</td></tr>\n');
SOFTWARE_PART_PROLOGUE = string.Template(
'<h2>${heading}</h2>\n' \
'<table class="sysinfo">\n' \
' <tr><th>Short name:</th><td>${shortname}</td></tr>\n' \
' <tr><th>Interface:</th><td>${interface}</td></tr>\n')
SOFTWARELIST_PROLOGUE = string.Template(
'<!DOCTYPE html>\n' \
'<html>\n' \
'<head>\n' \
' <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n' \
' <meta http-equiv="Content-Style-Type" content="text/css">\n' \
' <meta http-equiv="Content-Script-Type" content="text/javascript">\n' \
' <link rel="stylesheet" type="text/css" href="${assets}/style.css">\n' \
' <script type="text/javascript">var assetsurl="${assets}"</script>\n' \
' <script type="text/javascript" src="${assets}/common.js"></script>\n' \
' <title>${title}</title>\n' \
'</head>\n' \
'<body>\n' \
'<h1>${heading}</h1>\n' \
'<table class="sysinfo">\n' \
' <tr>\n' \
' <th>Short name:</th>\n' \
' <td>${shortname}</td>\n' \
' </tr>\n' \
' <tr>\n' \
' <th>Total:</th>\n' \
' <td style="text-align: right">${total}</td>\n' \
' </tr>\n' \
' <tr>\n' \
' <th>Supported:</th>\n' \
' <td style="text-align: right">${supported}</td>\n' \
' <td style="text-align: right">(${supportedpc}%)</td>\n' \
' </tr>\n' \
' <tr>\n' \
' <th>Partially supported:</th>\n' \
' <td style="text-align: right">${partiallysupported}</td>\n' \
' <td style="text-align: right">(${partiallysupportedpc}%)</td>\n' \
' </tr>\n' \
' <tr>\n' \
' <th>Unsupported:</th>\n' \
' <td style="text-align: right">${unsupported}</td>\n' \
' <td style="text-align: right">(${unsupportedpc}%)</td>\n' \
' </tr>\n' \
'</table>\n')
SOFTWARELIST_ROW = string.Template(
' <tr>\n' \
' <td><a href="${softwarehref}">${shortname}</a></td>\n' \
' <td><a href="${softwarehref}">${description}</a></td>\n' \
' <td>${year}</td>\n' \
' <td>${publisher}</td>\n' \
' <td>${supported}</td>\n' \
' <td style="text-align: right">${parts}</td>\n' \
' </tr>\n')
SOFTWARELIST_LIST_PROLOGUE = string.Template(
'<!DOCTYPE html>\n' \
'<html>\n' \
@ -229,5 +308,6 @@ ROMIDENT_PAGE = string.Template(
'<input id="input-dumps" type="file" multiple onchange="add_dump_files(this.files)" style="display: none">\n' \
'<div id="div-progress"></div>\n' \
'<div id="div-machines"></div>\n' \
'<div id="div-software"></div>\n' \
'</body>\n' \
'</html>\n')

View File

@ -286,7 +286,47 @@ class ListXmlHandler(ElementHandler):
pass
class DataAreaHandler(ElementHandler):
def __init__(self, parent, **kwargs):
super(DataAreaHandler, self).__init__(parent=parent, **kwargs)
self.dbcurs = parent.dbcurs
self.part = parent.id
def startChildElement(self, name, attrs):
if name == 'rom':
crc = attrs.get('crc')
sha1 = attrs.get('sha1')
if ('name' in attrs) and (crc is not None) and (sha1 is not None):
crc = int(crc, 16)
sha1 = sha1.lower()
self.dbcurs.add_rom(crc, sha1)
status = attrs.get('status', 'good')
self.dbcurs.add_softwareromdump(self.part, attrs['name'], crc, sha1, status != 'good')
self.setChildHandler(name, attrs, self.IGNORE)
class DiskAreaHandler(ElementHandler):
def __init__(self, parent, **kwargs):
super(DiskAreaHandler, self).__init__(parent=parent, **kwargs)
self.dbcurs = parent.dbcurs
self.part = parent.id
def startChildElement(self, name, attrs):
if name == 'disk':
sha1 = attrs.get('sha1')
if sha1 is not None:
sha1 = sha1.lower()
self.dbcurs.add_disk(sha1)
status = attrs.get('status', 'good')
self.dbcurs.add_softwarediskdump(self.part, attrs['name'], sha1, status != 'good')
self.setChildHandler(name, attrs, self.IGNORE)
class SoftwarePartHandler(ElementHandler):
CHILD_HANDLERS = {
'dataarea': DataAreaHandler,
'diskarea': DiskAreaHandler }
def __init__(self, parent, **kwargs):
super(SoftwarePartHandler, self).__init__(parent=parent, **kwargs)
self.dbcurs = parent.dbcurs
@ -296,10 +336,13 @@ class SoftwarePartHandler(ElementHandler):
self.id = self.dbcurs.add_softwarepart(self.software, attrs['name'], attrs['interface'])
def startChildElement(self, name, attrs):
if name == 'feature':
self.dbcurs.add_softwarepartfeaturetype(attrs['name'])
self.dbcurs.add_softwarepartfeature(self.id, attrs['name'], attrs['value'])
self.setChildHandler(name, attrs, self.IGNORE)
if name in self.CHILD_HANDLERS:
self.setChildHandler(name, attrs, self.CHILD_HANDLERS[name](self))
else:
if name == 'feature':
self.dbcurs.add_softwarepartfeaturetype(attrs['name'])
self.dbcurs.add_softwarepartfeature(self.id, attrs['name'], attrs['value'])
self.setChildHandler(name, attrs, self.IGNORE)
class SoftwareHandler(ElementHandler):

View File

@ -106,6 +106,9 @@ class QueryPageHandler(HandlerBase):
def softwarelist_href(self, softwarelist):
return cgi.escape(urlparse.urljoin(self.application_uri, 'softwarelist/%s' % (urlquote(softwarelist), )), True)
def software_href(self, softwarelist, software):
return cgi.escape(urlparse.urljoin(self.application_uri, 'softwarelist/%s/%s' % (urlquote(softwarelist), urlquote(software))), True)
class MachineRpcHandlerBase(QueryPageHandler):
def __init__(self, app, application_uri, environ, start_response, **kwargs):
@ -146,7 +149,7 @@ class MachineHandler(QueryPageHandler):
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()
machine_info = self.dbcurs.get_machine_details(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)
@ -432,18 +435,42 @@ class SourceFileHandler(QueryPageHandler):
class SoftwareListHandler(QueryPageHandler):
def __init__(self, app, application_uri, environ, start_response, **kwargs):
super(SoftwareListHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
self.shortname = wsgiref.util.shift_path_info(environ)
self.software = wsgiref.util.shift_path_info(environ)
def __iter__(self):
self.filename = self.environ['PATH_INFO']
if self.filename and (self.filename[0] == '/'):
self.filename = self.filename[1:]
if (not self.filename) or ('*' in self.filename) or ('?' in self.filename):
if 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)
elif self.software and ('*' not in self.software) and ('?' not in self.software):
software_info = self.dbcurs.get_software_details(self.shortname, self.software).fetchone()
if not software_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.software_page(software_info)
elif self.software or (self.shortname and ('*' not in self.shortname) and ('?' not in self.shortname)):
softwarelist_info = self.dbcurs.get_softwarelist_details(self.shortname, self.software or None).fetchone()
if not softwarelist_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.softwarelist_page(softwarelist_info, self.software or None)
else:
if 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.softwarelist_listing_page(self.filename if self.filename else None)
return self.softwarelist_listing_page(self.shortname or None)
def softwarelist_listing_page(self, pattern):
if not pattern:
@ -460,11 +487,94 @@ class SoftwareListHandler(QueryPageHandler):
shortname=cgi.escape(shortname),
description=cgi.escape(description),
total=cgi.escape('%d' % total),
supported=cgi.escape('%d' % supported),
partiallysupported=cgi.escape('%d' % partiallysupported),
unsupported=cgi.escape('%d' % unsupported)).encode('utf-8')
supported=cgi.escape('%.1f%%' % (supported * 100.0 / (total or 1), )),
partiallysupported=cgi.escape('%.1f%%' % (partiallysupported * 100.0 / (total or 1), )),
unsupported=cgi.escape('%.1f%%' % (unsupported * 100.0 / (total or 1), ))).encode('utf-8')
yield ' </tbody>\n</table>\n<script>make_table_sortable(document.getElementById("tbl-softwarelists"));</script>\n</body>\n</html>\n'.encode('utf-8')
def softwarelist_page(self, softwarelist_info, pattern):
if not pattern:
title = 'Software List: %s (%s)' % (cgi.escape(softwarelist_info['description']), cgi.escape(softwarelist_info['shortname']))
heading = cgi.escape(softwarelist_info['description'])
else:
title = 'Software List: %s (%s): %s' % (cgi.escape(softwarelist_info['description']), cgi.escape(softwarelist_info['shortname']), cgi.escape(pattern))
heading = '<a href="%s">%s</a>: %s' % (self.softwarelist_href(softwarelist_info['shortname']), cgi.escape(softwarelist_info['description']), cgi.escape(pattern))
yield htmltmpl.SOFTWARELIST_PROLOGUE.substitute(
assets=cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True),
title=title,
heading=heading,
shortname=cgi.escape(softwarelist_info['shortname']),
total=cgi.escape('%d' % (softwarelist_info['total'], )),
supported=cgi.escape('%d' % (softwarelist_info['supported'], )),
supportedpc=cgi.escape('%.1f' % (softwarelist_info['supported'] * 100.0 / (softwarelist_info['total'] or 1), )),
partiallysupported=cgi.escape('%d' % (softwarelist_info['partiallysupported'], )),
partiallysupportedpc=cgi.escape('%.1f' % (softwarelist_info['partiallysupported'] * 100.0 / (softwarelist_info['total'] or 1), )),
unsupported=cgi.escape('%d' % (softwarelist_info['unsupported'], )),
unsupportedpc=cgi.escape('%.1f' % (softwarelist_info['unsupported'] * 100.0 / (softwarelist_info['total'] or 1), ))).encode('utf-8')
first = True
for software_info in self.dbcurs.get_softwarelist_software(softwarelist_info['id'], self.software or None):
if first:
yield \
'<table id="tbl-software">\n' \
' <thead>\n' \
' <tr>\n' \
' <th>Short name</th>\n' \
' <th>Description</th>\n' \
' <th>Year</th>\n' \
' <th>Publisher</th>\n' \
' <th>Supported</th>\n' \
' <th class="numeric">Parts</th>\n' \
' </tr>\n' \
' </thead>\n' \
' <tbody>\n'.encode('utf-8')
first = False
yield self.software_row(software_info)
if first:
yield '<p>No software found.</p>\n'.encode('utf-8')
else:
yield ' </tbody>\n</table>\n<script>make_table_sortable(document.getElementById("tbl-software"));</script>\n'.encode('utf-8')
yield '</body>\n</html>\n'.encode('utf-8')
def software_page(self, software_info):
yield htmltmpl.SOFTWARE_PROLOGUE.substitute(
assets=cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True),
title=cgi.escape(software_info['description']),
heading=cgi.escape(software_info['description']),
softwarelisthref=self.softwarelist_href(self.shortname),
softwarelistdescription=cgi.escape(software_info['softwarelistdescription']),
softwarelist=cgi.escape(self.shortname),
shortname=cgi.escape(software_info['shortname']),
year=cgi.escape(software_info['year']),
publisher=cgi.escape(software_info['publisher']),
supported=cgi.escape('Yes' if software_info['supported'] == 0 else 'Partial' if software_info['supported'] == 1 else 'No')).encode('utf-8')
for name, value in self.dbcurs.get_software_info(software_info['id']):
yield (' <tr><th>%s:</th><td>%s</td>\n' % (cgi.escape(name), cgi.escape(value))).encode('utf-8')
yield '</table>\n\n'.encode('utf-8')
parts = self.dbcurs.get_software_parts(software_info['id']).fetchall()
for id, partname, interface, part_id in parts:
yield htmltmpl.SOFTWARE_PART_PROLOGUE.substitute(
heading=cgi.escape(('%s (%s)' % (part_id, partname)) if part_id is not None else partname),
shortname=cgi.escape(partname),
interface=cgi.escape(interface)).encode('utf-8')
for name, value in self.dbcurs.get_softwarepart_features(id):
yield (' <tr><th>%s:</th><td>%s</td>\n' % (cgi.escape(name), cgi.escape(value))).encode('utf-8')
yield '</table>\n\n'.encode('utf-8')
yield '</body>\n</html>\n'.encode('utf-8')
def software_row(self, software_info):
return htmltmpl.SOFTWARELIST_ROW.substitute(
softwarehref=self.software_href(self.shortname, software_info['shortname']),
shortname=cgi.escape(software_info['shortname']),
description=cgi.escape(software_info['description']),
year=cgi.escape(software_info['year']),
publisher=cgi.escape(software_info['publisher']),
supported=cgi.escape('Yes' if software_info['supported'] == 0 else 'Partial' if software_info['supported'] == 1 else 'No'),
parts=cgi.escape('%d' % software_info['parts'])).encode('utf-8')
class RomIdentHandler(QueryPageHandler):
def __init__(self, app, application_uri, environ, start_response, **kwargs):
@ -568,13 +678,33 @@ class RomDumpsRpcHandler(QueryPageHandler):
return self.error_page(500)
def data_page(self, crc, sha1):
result = { }
machines = { }
for shortname, description, label, bad in self.dbcurs.get_rom_dumps(crc, sha1):
machine = result.get(shortname)
machine = machines.get(shortname)
if machine is None:
machine = { 'description': description, 'matches': [ ] }
result[shortname] = machine
machines[shortname] = machine
machine['matches'].append({ 'name': label, 'bad': bool(bad) })
software = { }
for softwarelist, softwarelistdescription, shortname, description, part, part_id, label, bad in self.dbcurs.get_software_rom_dumps(crc, sha1):
listinfo = software.get(softwarelist)
if listinfo is None:
listinfo = { 'description': softwarelistdescription, 'software': { } }
software[softwarelist] = listinfo
softwareinfo = listinfo['software'].get(shortname)
if softwareinfo is None:
softwareinfo = { 'description': description, 'parts': { } }
listinfo['software'][shortname] = softwareinfo
partinfo = softwareinfo['parts'].get(part)
if partinfo is None:
partinfo = { 'matches': [ ] }
if part_id is not None:
partinfo['description'] = part_id
softwareinfo['parts'][part] = partinfo
partinfo['matches'].append({ 'name': label, 'bad': bool(bad) })
result = { 'machines': machines, 'software': software }
yield json.dumps(result).encode('utf-8')
@ -603,13 +733,33 @@ class DiskDumpsRpcHandler(QueryPageHandler):
return self.error_page(500)
def data_page(self, sha1):
result = { }
machines = { }
for shortname, description, label, bad in self.dbcurs.get_disk_dumps(sha1):
machine = result.get(shortname)
machine = machines.get(shortname)
if machine is None:
machine = { 'description': description, 'matches': [ ] }
result[shortname] = machine
machines[shortname] = machine
machine['matches'].append({ 'name': label, 'bad': bool(bad) })
software = { }
for softwarelist, softwarelistdescription, shortname, description, part, part_id, label, bad in self.dbcurs.get_software_disk_dumps(sha1):
listinfo = software.get(softwarelist)
if listinfo is None:
listinfo = { 'description': softwarelistdescription, 'software': { } }
software[softwarelist] = listinfo
softwareinfo = listinfo['software'].get(shortname)
if softwareinfo is None:
softwareinfo = { 'description': description, 'parts': { } }
listinfo['software'][shortname] = softwareinfo
partinfo = softwareinfo['parts'].get(part)
if partinfo is None:
partinfo = { 'matches': [ ] }
if part_id is not None:
partinfo['description'] = part_id
softwareinfo['parts'][part] = partinfo
partinfo['matches'].append({ 'name': label, 'bad': bool(bad) })
result = { 'machines': machines, 'software': software }
yield json.dumps(result).encode('utf-8')