summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author Vas Crabb <vas@vastheman.com>2019-12-15 21:38:54 +1100
committer Vas Crabb <vas@vastheman.com>2019-12-15 21:38:54 +1100
commit202d06ffb0d9a55c484dbc71c657e24b9323efe2 (patch)
treeb74c60ee1e06263d16f69d7bd7ca6516e316acbe
parent601e0668badf61b00ebd86aed995b3e3cf5c17e1 (diff)
minimaws: fill in software list pages, load software list ROM information
-rw-r--r--scripts/minimaws/lib/assets/common.js2
-rw-r--r--scripts/minimaws/lib/assets/romident.js19
-rw-r--r--scripts/minimaws/lib/dbaccess.py149
-rw-r--r--scripts/minimaws/lib/htmltmpl.py80
-rw-r--r--scripts/minimaws/lib/lxparse.py51
-rw-r--r--scripts/minimaws/lib/wsgiserve.py180
6 files changed, 448 insertions, 33 deletions
diff --git a/scripts/minimaws/lib/assets/common.js b/scripts/minimaws/lib/assets/common.js
index 79200217ddb..a9d1991e499 100644
--- a/scripts/minimaws/lib/assets/common.js
+++ b/scripts/minimaws/lib/assets/common.js
@@ -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);
})
diff --git a/scripts/minimaws/lib/assets/romident.js b/scripts/minimaws/lib/assets/romident.js
index cbe1af89fdf..20d5d2c2ba6 100644
--- a/scripts/minimaws/lib/assets/romident.js
+++ b/scripts/minimaws/lib/assets/romident.js
@@ -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);
}
});
diff --git a/scripts/minimaws/lib/dbaccess.py b/scripts/minimaws/lib/dbaccess.py
index d5fe557ff91..80bfabb92a5 100644
--- a/scripts/minimaws/lib/dbaccess.py
+++ b/scripts/minimaws/lib/dbaccess.py
@@ -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):
diff --git a/scripts/minimaws/lib/htmltmpl.py b/scripts/minimaws/lib/htmltmpl.py
index 627de7de2bc..5ba004a03b2 100644
--- a/scripts/minimaws/lib/htmltmpl.py
+++ b/scripts/minimaws/lib/htmltmpl.py
@@ -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')
diff --git a/scripts/minimaws/lib/lxparse.py b/scripts/minimaws/lib/lxparse.py
index 91208eb7fb1..62ff18e8d5a 100644
--- a/scripts/minimaws/lib/lxparse.py
+++ b/scripts/minimaws/lib/lxparse.py
@@ -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):
diff --git a/scripts/minimaws/lib/wsgiserve.py b/scripts/minimaws/lib/wsgiserve.py
index 7ea3823bb02..cbf4888e511 100644
--- a/scripts/minimaws/lib/wsgiserve.py
+++ b/scripts/minimaws/lib/wsgiserve.py
@@ -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')