Skip to content

Commit 8446619

Browse files
authored
Merge pull request #4021 from rommapp/fix/fpkgi-unique-entry-names
Give each FPKGi package a unique name within its rom
2 parents 7d9ca7d + 9fb43e4 commit 8446619

2 files changed

Lines changed: 110 additions & 7 deletions

File tree

backend/endpoints/feeds.py

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import csv
22
import io
33
import re
4+
from collections import Counter
45
from collections.abc import Sequence
56
from datetime import datetime
67
from typing import Annotated
@@ -582,14 +583,40 @@ def format_release_date(timestamp: int | None) -> str | None:
582583
FPKGI_TITLE_ID_REGEX = re.compile(r"(?:CUSA|PPSA)\d{5}", re.IGNORECASE)
583584

584585

585-
def fpkgi_item_name(rom: Rom, file: RomFile, *, is_single_file: bool) -> str:
586-
"""Name shown in FPKGi, disambiguated when a rom holds several packages."""
586+
def fpkgi_name_candidates(rom_name: str, file: RomFile) -> list[str]:
587+
"""Names for a package, from the most readable to the most specific."""
588+
label = FPKGI_CATEGORY_LABELS.get(file.category) if file.category else None
589+
stem = file.file_name_no_ext
590+
591+
candidates = []
592+
if label:
593+
candidates.append(f"{rom_name} - {label}")
594+
candidates.append(f"{rom_name} - {stem}")
595+
if label:
596+
candidates.append(f"{rom_name} - {label} - {stem}")
597+
# Last resort: two categories can hold packages with the same file name
598+
candidates.append(f"{rom_name} - {stem} ({file.id})")
599+
return candidates
600+
601+
602+
def fpkgi_item_names(rom: Rom, files: list[RomFile]) -> dict[int, str]:
603+
"""Name shown in FPKGi per package, keyed by rom file id.
604+
605+
FPKGi downloads to `[<title_id>] <name>.pkg`, and packages of a rom share a
606+
title id, so two of them sharing a name overwrite each other on the console.
607+
Each package therefore takes the first of its candidate names that no other
608+
package in the rom lays claim to.
609+
"""
587610
rom_name = rom.name or rom.fs_name
588-
if is_single_file:
589-
return rom_name
611+
if len(files) == 1:
612+
return {files[0].id: rom_name}
590613

591-
label = FPKGI_CATEGORY_LABELS.get(file.category) if file.category else None
592-
return f"{rom_name} - {label or file.file_name_no_ext}"
614+
candidates = {f.id: fpkgi_name_candidates(rom_name, f) for f in files}
615+
claimed = Counter(name for names in candidates.values() for name in names)
616+
return {
617+
file_id: next(name for name in names if claimed[name] == 1)
618+
for file_id, names in candidates.items()
619+
}
593620

594621

595622
def fpkgi_title_id(rom: Rom, files: list[RomFile]) -> str:
@@ -656,6 +683,9 @@ def fpkgi_feed(
656683
if f.file_extension.lower() == "pkg" and not f.missing_from_fs
657684
]
658685
title_id = fpkgi_title_id(rom, pkg_files)
686+
# Named over every package of the rom, not just the filtered ones, so a
687+
# name means the same thing whichever CONTENT_URLS slot serves it
688+
item_names = fpkgi_item_names(rom, pkg_files)
659689
cover_url = (
660690
str(URLPath(rom.path_cover_large).make_absolute_url(request.base_url))
661691
if rom.path_cover_large
@@ -670,7 +700,7 @@ def fpkgi_feed(
670700

671701
download_url = generate_romfile_download_url(request, file)
672702
response_data[download_url] = FPKGiFeedItemSchema(
673-
name=fpkgi_item_name(rom, file, is_single_file=len(pkg_files) == 1),
703+
name=item_names[file.id],
674704
size=file.file_size_bytes,
675705
title_id=title_id,
676706
region=rom.regions[0] if rom.regions else None,

backend/tests/endpoints/feeds.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,79 @@ def test_fpkgi_feed_multi_file_rom(
327327
assert response.status_code == status.HTTP_400_BAD_REQUEST
328328

329329

330+
def test_fpkgi_feed_names_are_unique_within_a_rom(
331+
client: TestClient, access_token: str, platform: Platform, rom: Rom
332+
):
333+
platform = db_platform_handler.update_platform(
334+
platform.id, {"name": "PlayStation 4", "slug": UPS.PS4, "fs_slug": UPS.PS4}
335+
)
336+
rom = db_rom_handler.update_rom(
337+
rom.id,
338+
{
339+
"platform_id": platform.id,
340+
"name": "Test PS4",
341+
"fs_name": "Test PS4",
342+
"fs_name_no_tags": "Test PS4",
343+
"fs_name_no_ext": "Test PS4",
344+
"fs_extension": "",
345+
"fs_path": f"{platform.slug}/roms",
346+
"fs_size_bytes": 369,
347+
"regions": ["US"],
348+
},
349+
)
350+
for sub_path, file_name, category in (
351+
("", "Test PS4 base.pkg", None),
352+
("update", "Test PS4 patch.pkg", RomFileCategory.UPDATE),
353+
("dlc", "Test PS4 brawler.pkg", RomFileCategory.DLC),
354+
("dlc", "Test PS4 loadout.pkg", RomFileCategory.DLC),
355+
# Same file name in two categories, so the file name alone is ambiguous
356+
("dlc", "Test PS4 extra.pkg", RomFileCategory.DLC),
357+
("demo", "Test PS4 extra.pkg", RomFileCategory.DEMO),
358+
("demo", "Test PS4 trial.pkg", RomFileCategory.DEMO),
359+
):
360+
db_rom_handler.add_rom_file(
361+
RomFile(
362+
rom_id=rom.id,
363+
file_name=file_name,
364+
file_path=f"{rom.fs_path}/{rom.fs_name}/{sub_path}".rstrip("/"),
365+
file_size_bytes=123,
366+
category=category,
367+
)
368+
)
369+
370+
response = client.get(
371+
"/api/feeds/fpkgi/ps4",
372+
headers={"Authorization": f"Bearer {access_token}"},
373+
)
374+
assert response.status_code == status.HTTP_200_OK
375+
376+
data = response.json()["DATA"]
377+
assert len(data) == 7
378+
assert sorted(entry["name"] for entry in data.values()) == [
379+
"Test PS4 - DLC - Test PS4 extra",
380+
"Test PS4 - Demo - Test PS4 extra",
381+
"Test PS4 - Test PS4 base",
382+
"Test PS4 - Test PS4 brawler",
383+
"Test PS4 - Test PS4 loadout",
384+
"Test PS4 - Test PS4 trial",
385+
"Test PS4 - Update",
386+
]
387+
388+
# Filtering must not change the name a package is served under
389+
response = client.get(
390+
"/api/feeds/fpkgi/ps4?content_type=dlc",
391+
headers={"Authorization": f"Bearer {access_token}"},
392+
)
393+
assert response.status_code == status.HTTP_200_OK
394+
395+
dlc_data = response.json()["DATA"]
396+
assert sorted(entry["name"] for entry in dlc_data.values()) == [
397+
"Test PS4 - DLC - Test PS4 extra",
398+
"Test PS4 - Test PS4 brawler",
399+
"Test PS4 - Test PS4 loadout",
400+
]
401+
402+
330403
def test_kekatsu_feed(
331404
client: TestClient, access_token: str, platform: Platform, rom: Rom
332405
):

0 commit comments

Comments
 (0)