Add Logiqx XML DAT parsing (Redump) and clear stale file matches on re-ingest
This commit is contained in:
+62
-6
@@ -1,6 +1,8 @@
|
|||||||
"""Parser for clrmamepro (.dat) files.
|
"""Parser for clrmamepro / Logiqx XML (.dat) files.
|
||||||
|
|
||||||
Handles the clrmamepro text DAT format used by Redump and No-Intro::
|
Supports both DAT layouts the project uses:
|
||||||
|
|
||||||
|
* clrmamepro text DAT (No-Intro): parenthesised blocks, e.g.::
|
||||||
|
|
||||||
clrmamepro (
|
clrmamepro (
|
||||||
name "Sony - PlayStation Portable"
|
name "Sony - PlayStation Portable"
|
||||||
@@ -13,11 +15,15 @@ Handles the clrmamepro text DAT format used by Redump and No-Intro::
|
|||||||
rom ( name "WipEout Pure (Europe).iso" size 123 crc ABCD1234 md5 ... sha1 ... )
|
rom ( name "WipEout Pure (Europe).iso" size 123 crc ABCD1234 md5 ... sha1 ... )
|
||||||
)
|
)
|
||||||
|
|
||||||
``parse_dat_text`` returns ``(header, entries)``. Each entry is a dict with
|
* Logiqx XML DAT (Redump): ``<game>`` elements with ``<rom>`` children.
|
||||||
scalar keys (``name``, ``description``, ``region``, ``serial``, ...) and
|
|
||||||
repeated nested blocks exposed as lists under their block key (``rom``).
|
Both ``parse_dat_text`` and ``parse_dat_xml`` return ``(header, entries)``.
|
||||||
|
Each entry is a dict with scalar keys (``name``, ``description``, ``region``,
|
||||||
|
``serial``, ...) and repeated nested blocks exposed as lists under their block
|
||||||
|
key (``rom``).
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
_TOKEN_RE = re.compile(
|
_TOKEN_RE = re.compile(
|
||||||
@@ -124,5 +130,55 @@ def parse_dat_text(text):
|
|||||||
return header, entries
|
return header, entries
|
||||||
|
|
||||||
|
|
||||||
|
def _xml_text(element, key):
|
||||||
|
child = element.find(key)
|
||||||
|
if child is not None and child.text:
|
||||||
|
return child.text.strip()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def parse_dat_xml(text):
|
||||||
|
"""Parse a Logiqx XML DAT (the format redump.org uses).
|
||||||
|
|
||||||
|
Returns ``(header, entries)`` in the same shape as :func:`parse_dat_text`.
|
||||||
|
"""
|
||||||
|
root = ET.fromstring(text)
|
||||||
|
|
||||||
|
header = {}
|
||||||
|
header_el = root.find("header")
|
||||||
|
if header_el is not None:
|
||||||
|
for child in header_el:
|
||||||
|
if child.text:
|
||||||
|
header[child.tag] = child.text.strip()
|
||||||
|
|
||||||
|
entries = []
|
||||||
|
for game in root.iter():
|
||||||
|
if game.tag not in ("game", "machine"):
|
||||||
|
continue
|
||||||
|
entry = {"name": (game.get("name") or "").strip()}
|
||||||
|
for key in ("description", "serial", "region", "category"):
|
||||||
|
value = _xml_text(game, key)
|
||||||
|
if value:
|
||||||
|
entry[key] = value
|
||||||
|
entry["rom"] = [
|
||||||
|
{
|
||||||
|
"name": rom.get("name", ""),
|
||||||
|
"size": rom.get("size"),
|
||||||
|
"crc": rom.get("crc"),
|
||||||
|
"md5": rom.get("md5"),
|
||||||
|
"sha1": rom.get("sha1"),
|
||||||
|
"serial": rom.get("serial"),
|
||||||
|
}
|
||||||
|
for rom in game.findall("rom")
|
||||||
|
]
|
||||||
|
entries.append(entry)
|
||||||
|
|
||||||
|
return header, entries
|
||||||
|
|
||||||
|
|
||||||
def parse_dat_file(path):
|
def parse_dat_file(path):
|
||||||
return parse_dat_text(Path(path).read_text(encoding="utf-8-sig"))
|
text = Path(path).read_text(encoding="utf-8-sig")
|
||||||
|
stripped = text.lstrip()
|
||||||
|
if stripped.startswith("<?xml") or stripped.startswith("<"):
|
||||||
|
return parse_dat_xml(text)
|
||||||
|
return parse_dat_text(text)
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ def ingest_dat(conn, path):
|
|||||||
existing = conn.execute("SELECT id FROM dats WHERE name = ?", (dat_name,)).fetchone()
|
existing = conn.execute("SELECT id FROM dats WHERE name = ?", (dat_name,)).fetchone()
|
||||||
if existing:
|
if existing:
|
||||||
dat_id = existing["id"]
|
dat_id = existing["id"]
|
||||||
|
# Files that resolved to a release in this DAT must be re-identified
|
||||||
|
# against the new catalog (the release ids are about to be recycled).
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE files SET release_id = NULL WHERE release_id IN (SELECT id FROM releases WHERE dat_id = ?)",
|
||||||
|
(dat_id,),
|
||||||
|
)
|
||||||
conn.execute("DELETE FROM roms WHERE release_id IN (SELECT id FROM releases WHERE dat_id = ?)", (dat_id,))
|
conn.execute("DELETE FROM roms WHERE release_id IN (SELECT id FROM releases WHERE dat_id = ?)", (dat_id,))
|
||||||
conn.execute("DELETE FROM releases WHERE dat_id = ?", (dat_id,))
|
conn.execute("DELETE FROM releases WHERE dat_id = ?", (dat_id,))
|
||||||
conn.execute(
|
conn.execute(
|
||||||
|
|||||||
Reference in New Issue
Block a user