mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-29 15:17:20 -05:00
fix: link file cards to the entry that reads them
A directory entry was joined to the collection by its own name, so the 11,976 files under alephone's Plugins/ and the cdogs, thextech and easyrpg trees named no emulator on their card. FileConsumers now expands the directory and records which entry reads each file. The emulator links on a file card land on that entry, and a directory card names its copies and the folder that holds them.
This commit is contained in:
1 parent
90e54dcb43
commit
0145e6bfc8
3 files changed
+158
-5
No files matched your search
@@ -27,6 +27,7 @@ from common import (
|
||||
ProfileSelectionError,
|
||||
_norm_system_id,
|
||||
directory_members,
|
||||
expand_directory_entries,
|
||||
get_mame_clone_map,
|
||||
list_emulator_profiles,
|
||||
list_registered_platforms,
|
||||
@@ -341,6 +342,10 @@ class FileConsumers:
|
||||
# Where each platform puts the file in its BIOS directory.
|
||||
self.platform_destinations: dict[str, dict[str, set[str]]] = {}
|
||||
self.emulators: dict[str, set[str]] = {}
|
||||
# The entry that reads each file, by its position in the profile's
|
||||
# files: a link lands on that entry's card, not the page top.
|
||||
self.emulator_entries: dict[str, dict[str, int]] = {}
|
||||
self.emulator_files: dict[str, list] = {}
|
||||
# Where each platform's pack installs the file, its cores' files
|
||||
# included: the platform's own list names a fraction of them.
|
||||
self.installed: dict[str, dict[str, set[str]]] = {}
|
||||
@@ -401,14 +406,49 @@ class FileConsumers:
|
||||
"""The collection path the database records for a file."""
|
||||
return str(self._db.get("files", {}).get(sha1, {}).get("path", ""))
|
||||
|
||||
def directory_members(self, entry: dict) -> dict[str, str]:
|
||||
"""{destination: sha1} of the files a ``type: directory`` entry reads."""
|
||||
if entry.get("type") != "directory":
|
||||
return {}
|
||||
return {
|
||||
member["path"]: member["sha1"]
|
||||
for member in expand_directory_entries([entry], self._db)
|
||||
if member is not entry
|
||||
}
|
||||
|
||||
def add_emulators(self, profiles: dict[str, dict]) -> None:
|
||||
"""Each file a profile entry resolves to, a directory's members included.
|
||||
|
||||
A directory entry was resolved by its own name and found nothing:
|
||||
11,976 files under alephone's Plugins/, cdogs' and thextech's trees
|
||||
named no emulator on their card.
|
||||
"""
|
||||
for name, profile in profiles.items():
|
||||
if profile.get("type") == "alias":
|
||||
continue
|
||||
for entry in profile.get("files", []):
|
||||
sha1 = self.sha1_of(entry, entry.get("path") or "", owner=name)
|
||||
if sha1:
|
||||
self.emulators.setdefault(sha1, set()).add(name)
|
||||
files = profile.get("files") or []
|
||||
self.emulator_files[name] = files
|
||||
# A file a profile names keeps its own entry over the directory
|
||||
# that also holds it.
|
||||
order = sorted(
|
||||
(i for i, entry in enumerate(files) if isinstance(entry, dict)),
|
||||
key=lambda i: files[i].get("type") == "directory",
|
||||
)
|
||||
for index in order:
|
||||
entry = files[index]
|
||||
members = self.directory_members(entry)
|
||||
if members:
|
||||
found = [
|
||||
self.sha1_of({**entry, "name": dest, "path": dest, "sha1": sha1},
|
||||
dest, owner=name)
|
||||
for dest, sha1 in members.items()
|
||||
]
|
||||
else:
|
||||
found = [self.sha1_of(entry, entry.get("path") or "", owner=name)]
|
||||
for sha1 in found:
|
||||
if sha1:
|
||||
self.emulators.setdefault(sha1, set()).add(name)
|
||||
self.emulator_entries.setdefault(sha1, {}).setdefault(name, index)
|
||||
|
||||
|
||||
def entry_source(f: dict, index: dict) -> str | None:
|
||||
|
||||
@@ -2415,6 +2415,36 @@ def _platform_links(sha1: str, consumers: FileConsumers) -> list[str]:
|
||||
return links
|
||||
|
||||
|
||||
_card_anchors: dict[str, tuple[list, list[str]]] = {}
|
||||
|
||||
|
||||
def _emulator_card_anchor(emulator: str, files: list, index: int) -> str:
|
||||
"""The id of a profile entry's card on its emulator page."""
|
||||
cached = _card_anchors.get(emulator)
|
||||
if cached is None or cached[0] is not files:
|
||||
cached = _card_anchors[emulator] = (files, emulator_file_anchors(files))
|
||||
return cached[1][index] if 0 <= index < len(cached[1]) else ""
|
||||
|
||||
|
||||
def _emulator_links(sha1: str, consumers: FileConsumers) -> list[str]:
|
||||
"""Each emulator reading the file, linked to the entry that reads it.
|
||||
|
||||
The platform links landed on their system section, and the emulator
|
||||
links on the page top: duckstation's scph5501.bin entry, with the line
|
||||
that accepts it, sat 260 lines down a page of 105 entries.
|
||||
"""
|
||||
entries = getattr(consumers, "emulator_entries", {}).get(sha1, {})
|
||||
files_of = getattr(consumers, "emulator_files", {})
|
||||
links = []
|
||||
for emulator in sorted(consumers.emulators.get(sha1, ())):
|
||||
index = entries.get(emulator)
|
||||
anchor = (_emulator_card_anchor(emulator, files_of[emulator], index)
|
||||
if index is not None and emulator in files_of else "")
|
||||
fragment = f"#{anchor}" if anchor else ""
|
||||
links.append(f"[{emulator}](../emulators/{emulator}.md{fragment})")
|
||||
return links
|
||||
|
||||
|
||||
def _installed_links(sha1: str, consumers: FileConsumers, declared: set[str]) -> list[str]:
|
||||
"""Each pack installing the file that its platform's list does not name,
|
||||
with the place the pack gives it."""
|
||||
@@ -2482,7 +2512,7 @@ def _system_file_card(
|
||||
# pack installs it for six cores.
|
||||
lines.append(f"- Also installed by: {', '.join(installed)}")
|
||||
if emus:
|
||||
lines.append(f"- Emulators: {', '.join(_emulator_link(e, '../') for e in emus)}")
|
||||
lines.append(f"- Emulators: {', '.join(_emulator_links(sha1_full, consumers))}")
|
||||
provenance = f.get("provenance", {})
|
||||
if provenance:
|
||||
lines.extend(_provenance_lines(provenance, "../"))
|
||||
@@ -2876,6 +2906,8 @@ def _render_emulator_file(
|
||||
details.append(
|
||||
f"Source: {_source_ref_markdown(profile, source_ref)}"
|
||||
)
|
||||
if consumers and f.get("type") == "directory":
|
||||
details.extend(_directory_copies(f, consumers))
|
||||
if consumers:
|
||||
sha1 = consumers.sha1_of(f, f.get("path") or "", owner=owner)
|
||||
if sha1 and in_repo:
|
||||
@@ -2945,6 +2977,27 @@ def _render_emulator_file(
|
||||
return lines
|
||||
|
||||
|
||||
def _directory_copies(entry: dict, consumers: FileConsumers) -> list[str]:
|
||||
"""What the collection holds under a directory entry, and where.
|
||||
|
||||
The card said "in repo" and linked none of the files: alephone's
|
||||
Plugins/ holds 393 of them, each with its own card on a system page.
|
||||
"""
|
||||
members = consumers.directory_members(entry)
|
||||
held = sorted({consumers.path_of(sha1) for sha1 in members.values()} - {""})
|
||||
if not held:
|
||||
return []
|
||||
folder = os.path.commonpath(held) if len(held) > 1 else os.path.dirname(held[0])
|
||||
parts = folder.split("/")
|
||||
where = _code(folder + "/")
|
||||
if len(parts) >= 3 and parts[0] == "bios":
|
||||
page = urllib.parse.quote(parts[1].lower().replace(" ", "-"))
|
||||
where = f"[{where}](../systems/{page}.md#{_slugify_anchor(parts[2])})"
|
||||
tree = f"{REPO_URL}/tree/main/{urllib.parse.quote(folder)}"
|
||||
return [f"Collection copies: {_count(len(members), 'file')} under {where} "
|
||||
f"([browse]({tree}))"]
|
||||
|
||||
|
||||
def _render_metadata_rows(profile: dict) -> list[str]:
|
||||
"""Scalar profile fields, one table row each.
|
||||
|
||||
|
||||
Reference in new issue
Block a user