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( ' \n') +SOFTWARE_PROLOGUE = string.Template( + '\n' \ + '\n' \ + '\n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' ${title}\n' \ + '\n' \ + '\n' \ + '

${heading}

\n' \ + '\n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n'); + +SOFTWARE_PART_PROLOGUE = string.Template( + '

${heading}

\n' \ + '
Software list:${softwarelistdescription} (${softwarelist})
Short name:${shortname}
Year:${year}
Publisher:${publisher}
Suported:${supported}
\n' \ + ' \n' \ + ' \n') + + +SOFTWARELIST_PROLOGUE = string.Template( + '\n' \ + '\n' \ + '\n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' ${title}\n' \ + '\n' \ + '\n' \ + '

${heading}

\n' \ + '
Short name:${shortname}
Interface:${interface}
\n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + '
Short name:${shortname}
Total:${total}
Supported:${supported}(${supportedpc}%)
Partially supported:${partiallysupported}(${partiallysupportedpc}%)
Unsupported:${unsupported}(${unsupportedpc}%)
\n') + +SOFTWARELIST_ROW = string.Template( + ' \n' \ + ' ${shortname}\n' \ + ' ${description}\n' \ + ' ${year}\n' \ + ' ${publisher}\n' \ + ' ${supported}\n' \ + ' ${parts}\n' \ + ' \n') + + SOFTWARELIST_LIST_PROLOGUE = string.Template( '\n' \ '\n' \ @@ -229,5 +308,6 @@ ROMIDENT_PAGE = string.Template( '\n' \ '
\n' \ '
\n' \ + '
\n' \ '\n' \ '\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 ' \n\n\n\n\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 = '%s: %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 \ + '\n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \n'.encode('utf-8') + first = False + yield self.software_row(software_info) + if first: + yield '

No software found.

\n'.encode('utf-8') + else: + yield ' \n
Short nameDescriptionYearPublisherSupportedParts
\n\n'.encode('utf-8') + + yield '\n\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 (' %s:%s\n' % (cgi.escape(name), cgi.escape(value))).encode('utf-8') + yield '\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 (' %s:%s\n' % (cgi.escape(name), cgi.escape(value))).encode('utf-8') + yield '\n\n'.encode('utf-8') + + yield '\n\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')