mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-04-13 12:22:33 -05:00
fix: exporters use _dest fallback, merge colliding systems, per-platform subdirs
This commit is contained in:
@@ -16,6 +16,8 @@ from exporter import discover_exporters
|
|||||||
|
|
||||||
OUTPUT_FILENAMES: dict[str, str] = {
|
OUTPUT_FILENAMES: dict[str, str] = {
|
||||||
"retroarch": "System.dat",
|
"retroarch": "System.dat",
|
||||||
|
"lakka": "System.dat",
|
||||||
|
"retropie": "System.dat",
|
||||||
"batocera": "batocera-systems",
|
"batocera": "batocera-systems",
|
||||||
"recalbox": "es_bios.xml",
|
"recalbox": "es_bios.xml",
|
||||||
"retrobat": "batocera-systems.json",
|
"retrobat": "batocera-systems.json",
|
||||||
@@ -25,9 +27,16 @@ OUTPUT_FILENAMES: dict[str, str] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def output_filename(platform: str) -> str:
|
def output_path(platform: str, output_dir: str) -> str:
|
||||||
"""Return the native output filename for a platform."""
|
"""Return the full output path for a platform's native export.
|
||||||
return OUTPUT_FILENAMES.get(platform, f"{platform}_bios.dat")
|
|
||||||
|
Each platform gets its own subdirectory to avoid filename collisions
|
||||||
|
(e.g. retroarch, lakka, retropie all produce System.dat).
|
||||||
|
"""
|
||||||
|
filename = OUTPUT_FILENAMES.get(platform, f"{platform}_bios.dat")
|
||||||
|
plat_dir = Path(output_dir) / platform
|
||||||
|
plat_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return str(plat_dir / filename)
|
||||||
|
|
||||||
|
|
||||||
def run(
|
def run(
|
||||||
@@ -38,8 +47,6 @@ def run(
|
|||||||
) -> int:
|
) -> int:
|
||||||
"""Export truth to native formats, return exit code."""
|
"""Export truth to native formats, return exit code."""
|
||||||
exporters = discover_exporters()
|
exporters = discover_exporters()
|
||||||
output_path = Path(output_dir)
|
|
||||||
output_path.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
errors = 0
|
errors = 0
|
||||||
|
|
||||||
@@ -63,7 +70,7 @@ def run(
|
|||||||
except (FileNotFoundError, OSError):
|
except (FileNotFoundError, OSError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
dest = str(output_path / output_filename(platform))
|
dest = output_path(platform, output_dir)
|
||||||
exporter = exporter_cls()
|
exporter = exporter_cls()
|
||||||
exporter.export(truth_data, dest, scraped_data=scraped)
|
exporter.export(truth_data, dest, scraped_data=scraped)
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ class BaseExporter(ABC):
|
|||||||
"""Check if a filename is a placeholder pattern (not a real file)."""
|
"""Check if a filename is a placeholder pattern (not a real file)."""
|
||||||
return "<" in name or ">" in name or "*" in name
|
return "<" in name or ">" in name or "*" in name
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _dest(fe: dict) -> str:
|
||||||
|
"""Get destination path for a file entry, falling back to name."""
|
||||||
|
return fe.get("path") or fe.get("destination") or fe.get("name", "")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _display_name(
|
def _display_name(
|
||||||
sys_id: str, scraped_sys: dict | None = None,
|
sys_id: str, scraped_sys: dict | None = None,
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class Exporter(BaseExporter):
|
|||||||
name = fe.get("name", "")
|
name = fe.get("name", "")
|
||||||
if name.startswith("_") or self._is_pattern(name):
|
if name.startswith("_") or self._is_pattern(name):
|
||||||
continue
|
continue
|
||||||
dest = fe.get("destination", name)
|
dest = self._dest(fe)
|
||||||
md5 = fe.get("md5", "")
|
md5 = fe.get("md5", "")
|
||||||
if isinstance(md5, list):
|
if isinstance(md5, list):
|
||||||
md5 = md5[0] if md5 else ""
|
md5 = md5[0] if md5 else ""
|
||||||
@@ -85,7 +85,7 @@ class Exporter(BaseExporter):
|
|||||||
name = fe.get("name", "")
|
name = fe.get("name", "")
|
||||||
if name.startswith("_") or self._is_pattern(name):
|
if name.startswith("_") or self._is_pattern(name):
|
||||||
continue
|
continue
|
||||||
dest = fe.get("destination", name)
|
dest = self._dest(fe)
|
||||||
if dest not in content and name not in content:
|
if dest not in content and name not in content:
|
||||||
issues.append(f"missing: {name}")
|
issues.append(f"missing: {name}")
|
||||||
return issues
|
return issues
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ class Exporter(BaseExporter):
|
|||||||
else:
|
else:
|
||||||
# No MD5 hashes — check file existence
|
# No MD5 hashes — check file existence
|
||||||
for fe in sys_files[sys_id]:
|
for fe in sys_files[sys_id]:
|
||||||
dest = fe.get("destination", fe.get("name", ""))
|
dest = self._dest(fe)
|
||||||
if dest:
|
if dest:
|
||||||
lines.append(
|
lines.append(
|
||||||
f' if [ -f "$biosPath/{dest}" ]; then',
|
f' if [ -f "$biosPath/{dest}" ]; then',
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class Exporter(BaseExporter):
|
|||||||
if name.startswith("_") or self._is_pattern(name):
|
if name.startswith("_") or self._is_pattern(name):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
dest = fe.get("destination", name)
|
dest = self._dest(fe)
|
||||||
# Recalbox paths include system prefix
|
# Recalbox paths include system prefix
|
||||||
path = f"{native_id}/{dest}" if "/" not in dest else dest
|
path = f"{native_id}/{dest}" if "/" not in dest else dest
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ class Exporter(BaseExporter):
|
|||||||
name = fe.get("name", "")
|
name = fe.get("name", "")
|
||||||
if name.startswith("_") or self._is_pattern(name):
|
if name.startswith("_") or self._is_pattern(name):
|
||||||
continue
|
continue
|
||||||
dest = fe.get("destination", name)
|
dest = self._dest(fe)
|
||||||
if name not in exported_paths and dest not in exported_paths:
|
if name not in exported_paths and dest not in exported_paths:
|
||||||
issues.append(f"missing: {name}")
|
issues.append(f"missing: {name}")
|
||||||
return issues
|
return issues
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class Exporter(BaseExporter):
|
|||||||
name = fe.get("name", "")
|
name = fe.get("name", "")
|
||||||
if name.startswith("_") or self._is_pattern(name):
|
if name.startswith("_") or self._is_pattern(name):
|
||||||
continue
|
continue
|
||||||
dest = fe.get("destination", name)
|
dest = self._dest(fe)
|
||||||
md5 = fe.get("md5", "")
|
md5 = fe.get("md5", "")
|
||||||
if isinstance(md5, list):
|
if isinstance(md5, list):
|
||||||
md5 = md5[0] if md5 else ""
|
md5 = md5[0] if md5 else ""
|
||||||
@@ -68,10 +68,16 @@ class Exporter(BaseExporter):
|
|||||||
bios_files.append(entry)
|
bios_files.append(entry)
|
||||||
|
|
||||||
if bios_files:
|
if bios_files:
|
||||||
sys_entry: OrderedDict[str, object] = OrderedDict()
|
if native_id in output:
|
||||||
sys_entry["name"] = display_name
|
existing_files = {e.get("file") for e in output[native_id]["biosFiles"]}
|
||||||
sys_entry["biosFiles"] = bios_files
|
for entry in bios_files:
|
||||||
output[native_id] = sys_entry
|
if entry.get("file") not in existing_files:
|
||||||
|
output[native_id]["biosFiles"].append(entry)
|
||||||
|
else:
|
||||||
|
sys_entry: OrderedDict[str, object] = OrderedDict()
|
||||||
|
sys_entry["name"] = display_name
|
||||||
|
sys_entry["biosFiles"] = bios_files
|
||||||
|
output[native_id] = sys_entry
|
||||||
|
|
||||||
Path(output_path).write_text(
|
Path(output_path).write_text(
|
||||||
json.dumps(output, indent=2, ensure_ascii=False) + "\n",
|
json.dumps(output, indent=2, ensure_ascii=False) + "\n",
|
||||||
@@ -96,7 +102,7 @@ class Exporter(BaseExporter):
|
|||||||
name = fe.get("name", "")
|
name = fe.get("name", "")
|
||||||
if name.startswith("_") or self._is_pattern(name):
|
if name.startswith("_") or self._is_pattern(name):
|
||||||
continue
|
continue
|
||||||
dest = fe.get("destination", name)
|
dest = self._dest(fe)
|
||||||
if name not in exported_files and dest not in exported_files:
|
if name not in exported_files and dest not in exported_files:
|
||||||
issues.append(f"missing: {name}")
|
issues.append(f"missing: {name}")
|
||||||
return issues
|
return issues
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ class Exporter(BaseExporter):
|
|||||||
if name.startswith("_") or self._is_pattern(name):
|
if name.startswith("_") or self._is_pattern(name):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
dest = fe.get("destination", name)
|
dest = self._dest(fe)
|
||||||
path_token = _dest_to_path_token(dest)
|
path_token = _dest_to_path_token(dest)
|
||||||
|
|
||||||
md5 = fe.get("md5", "")
|
md5 = fe.get("md5", "")
|
||||||
@@ -167,10 +167,18 @@ class Exporter(BaseExporter):
|
|||||||
bios_entries.append(entry)
|
bios_entries.append(entry)
|
||||||
|
|
||||||
if bios_entries:
|
if bios_entries:
|
||||||
component = OrderedDict()
|
if native_id in manifest:
|
||||||
component["system"] = native_id
|
# Merge into existing component (multiple truth systems
|
||||||
component["bios"] = bios_entries
|
# may map to the same native ID)
|
||||||
manifest[native_id] = component
|
existing_names = {e["filename"] for e in manifest[native_id]["bios"]}
|
||||||
|
for entry in bios_entries:
|
||||||
|
if entry["filename"] not in existing_names:
|
||||||
|
manifest[native_id]["bios"].append(entry)
|
||||||
|
else:
|
||||||
|
component = OrderedDict()
|
||||||
|
component["system"] = native_id
|
||||||
|
component["bios"] = bios_entries
|
||||||
|
manifest[native_id] = component
|
||||||
|
|
||||||
Path(output_path).write_text(
|
Path(output_path).write_text(
|
||||||
json.dumps(manifest, indent=2, ensure_ascii=False) + "\n",
|
json.dumps(manifest, indent=2, ensure_ascii=False) + "\n",
|
||||||
|
|||||||
Reference in New Issue
Block a user