From bedf14005e736bf2b66136d3e9eb766313e89e33 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Wed, 23 Apr 2025 12:29:56 -0400 Subject: [PATCH 01/14] [ROM-1847] Use ss region covers as fallback --- backend/handler/metadata/ss_handler.py | 103 ++++++++++++++++++------- 1 file changed, 77 insertions(+), 26 deletions(-) diff --git a/backend/handler/metadata/ss_handler.py b/backend/handler/metadata/ss_handler.py index 043ab096a..89e40aa1f 100644 --- a/backend/handler/metadata/ss_handler.py +++ b/backend/handler/metadata/ss_handler.py @@ -422,35 +422,51 @@ class SSHandler(MetadataHandler): if res: break - if not res or not res.get("id", None): + if not res: return fallback_rom - ss_id: int = int(res.get("id", None)) + res_ss_id = res.get("id", None) + if not res_ss_id: + return fallback_rom - rom = { - "ss_id": ss_id, - "name": pydash.chain(res.get("noms", [])) + ss_id: int = int(res_ss_id) + + res_name = ( + pydash.chain(res.get("noms", [])) .filter({"region": "ss"}) .map("text") .head() - .value(), - "slug": pydash.chain(res.get("noms", [])) + .value() + ) + res_slug = ( + pydash.chain(res.get("noms", [])) .filter({"region": "ss"}) .map("text") .head() - .value(), - "summary": pydash.chain(res.get("synopsis", [])) + .value() + ) + res_summary = ( + pydash.chain(res.get("synopsis", [])) .filter({"langue": "en"}) .map("text") .head() - .value(), - "url_cover": pydash.chain(res.get("medias", [])) + .value() + ) + res_url_cover = ( + pydash.chain(res.get("medias", [])) .filter({"region": "us", "type": "box-2D", "parent": "jeu"}) .map("url") .head() .value() - or "", - "url_manual": pydash.chain(res.get("medias", [])) + or pydash.chain(res.get("medias", [])) + .filter({"region": "ss", "type": "box-2D", "parent": "jeu"}) + .map("url") + .head() + .value() + or "" + ) + res_url_manual = ( + pydash.chain(res.get("medias", [])) .filter( {"region": "us", "type": "manuel", "parent": "jeu", "format": "pdf"} ) @@ -464,7 +480,16 @@ class SSHandler(MetadataHandler): .map("url") .head() .value() - or "", + or "" + ) + + rom = { + "ss_id": ss_id, + "name": res_name, + "slug": res_slug, + "summary": res_summary, + "url_cover": res_url_cover, + "url_manual": res_url_manual, "url_screenshots": [], "ss_metadata": extract_metadata_from_ss_rom(res), } @@ -481,30 +506,42 @@ class SSHandler(MetadataHandler): if not res: return SSRom(ss_id=None) - rom = { - "ss_id": res.get("id"), - "name": pydash.chain(res.get("noms", [])) + res_name = ( + pydash.chain(res.get("noms", [])) .filter({"region": "ss"}) .map("text") .head() - .value(), - "slug": pydash.chain(res.get("noms", [])) + .value() + ) + res_slug = ( + pydash.chain(res.get("noms", [])) .filter({"region": "ss"}) .map("text") .head() - .value(), - "summary": pydash.chain(res.get("synopsis", [])) + .value() + ) + res_summary = ( + pydash.chain(res.get("synopsis", [])) .filter({"langue": "en"}) .map("text") .head() - .value(), - "url_cover": pydash.chain(res.get("medias", [])) + .value() + ) + res_url_cover = ( + pydash.chain(res.get("medias", [])) .filter({"region": "us", "type": "box-2D", "parent": "jeu"}) .map("url") .head() .value() - or "", - "url_manual": pydash.chain(res.get("medias", [])) + or pydash.chain(res.get("medias", [])) + .filter({"region": "ss", "type": "box-2D", "parent": "jeu"}) + .map("url") + .head() + .value() + or "" + ) + res_url_manual = ( + pydash.chain(res.get("medias", [])) .filter( {"region": "us", "type": "manuel", "parent": "jeu", "format": "pdf"} ) @@ -518,7 +555,16 @@ class SSHandler(MetadataHandler): .map("url") .head() .value() - or "", + or "" + ) + + rom = { + "ss_id": res.get("id"), + "name": res_name, + "slug": res_slug, + "summary": res_summary, + "url_cover": res_url_cover, + "url_manual": res_url_manual, "url_screenshots": [], "ss_metadata": extract_metadata_from_ss_rom(res), } @@ -584,6 +630,11 @@ class SSHandler(MetadataHandler): .map("url") .head() .value() + or pydash.chain(rom.get("medias", [])) + .filter({"region": "ss", "type": "box-2D", "parent": "jeu"}) + .map("url") + .head() + .value() or "" ) From 9679fdeb3cdd48b4bd391579509d26afe2b779f6 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Wed, 23 Apr 2025 13:31:02 -0400 Subject: [PATCH 02/14] [ROMM-1846] Rename file from client input --- backend/endpoints/rom.py | 31 ++++++------------- backend/endpoints/tests/test_rom.py | 1 - .../components/common/Game/Dialog/EditRom.vue | 1 - .../common/Game/Dialog/MatchRom.vue | 13 ++++---- frontend/src/services/api/rom.ts | 3 -- 5 files changed, 16 insertions(+), 33 deletions(-) diff --git a/backend/endpoints/rom.py b/backend/endpoints/rom.py index 0892b5097..c2fc319b4 100644 --- a/backend/endpoints/rom.py +++ b/backend/endpoints/rom.py @@ -476,7 +476,6 @@ async def get_rom_content( async def update_rom( request: Request, id: int, - rename_as_source: bool = False, remove_cover: bool = False, artwork: UploadFile | None = None, unmatch_metadata: bool = False, @@ -486,12 +485,11 @@ async def update_rom( Args: request (Request): Fastapi Request object id (Rom): Rom internal id - rename_as_source (bool, optional): Flag to rename rom file as matched IGDB game. Defaults to False. artwork (UploadFile, optional): Custom artwork to set as cover. Defaults to File(None). unmatch_metadata: Remove the metadata matches for this game. Defaults to False. Raises: - HTTPException: If a rom already have that name when enabling the rename_as_source flag + HTTPException: Rom not found in database Returns: DetailedRomSchema: Rom stored in the database @@ -651,33 +649,22 @@ async def update_rom( # Rename the file/folder if the name has changed should_update_fs = new_fs_name != rom.fs_name - try: - if rename_as_source: - new_fs_name = rom.fs_name.replace( - rom.fs_name_no_tags or rom.fs_name_no_ext, - rom.name or rom.fs_name, - ) + if should_update_fs: + try: new_fs_name = sanitize_filename(new_fs_name) fs_rom_handler.rename_fs_rom( old_name=rom.fs_name, new_name=new_fs_name, fs_path=rom.fs_path, ) - elif should_update_fs: - new_fs_name = sanitize_filename(new_fs_name) - fs_rom_handler.rename_fs_rom( - old_name=rom.fs_name, - new_name=new_fs_name, - fs_path=rom.fs_path, - ) - except RomAlreadyExistsException as exc: - log.error(exc) - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=exc - ) from exc + except RomAlreadyExistsException as exc: + log.error(exc) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=exc + ) from exc # Update the rom files with the new fs_name - if rename_as_source or should_update_fs: + if should_update_fs: for file in rom.files: db_rom_handler.update_rom_file( file.id, diff --git a/backend/endpoints/tests/test_rom.py b/backend/endpoints/tests/test_rom.py index 92c1d2a45..74cac13bd 100644 --- a/backend/endpoints/tests/test_rom.py +++ b/backend/endpoints/tests/test_rom.py @@ -49,7 +49,6 @@ def test_update_rom(rename_fs_rom_mock, get_rom_by_id_mock, client, access_token response = client.put( f"/api/roms/{rom.id}", headers={"Authorization": f"Bearer {access_token}"}, - params={"rename_as_source": True}, data={ "igdb_id": "236663", "name": "Metroid Prime Remastered", diff --git a/frontend/src/components/common/Game/Dialog/EditRom.vue b/frontend/src/components/common/Game/Dialog/EditRom.vue index 90043714b..60a0eb6f3 100644 --- a/frontend/src/components/common/Game/Dialog/EditRom.vue +++ b/frontend/src/components/common/Game/Dialog/EditRom.vue @@ -87,7 +87,6 @@ const noMetadataMatch = computed(() => { async function handleRomUpdate( options: { rom: UpdateRom; - renameAsSource?: boolean; removeCover?: boolean; unmatch?: boolean; }, diff --git a/frontend/src/components/common/Game/Dialog/MatchRom.vue b/frontend/src/components/common/Game/Dialog/MatchRom.vue index 0fc10b1ac..fda44d3f4 100644 --- a/frontend/src/components/common/Game/Dialog/MatchRom.vue +++ b/frontend/src/components/common/Game/Dialog/MatchRom.vue @@ -205,6 +205,9 @@ async function updateRom( // Set the properties from the selected rom rom.value = { ...rom.value, + fs_name: renameAsSource.value + ? `${selectedMatchRom.value.name}.${rom.value.fs_extension}` + : rom.value.fs_name, igdb_id: selectedRom.igdb_id || null, moby_id: selectedRom.moby_id || null, ss_id: selectedRom.ss_id || null, @@ -225,7 +228,7 @@ async function updateRom( } await romApi - .updateRom({ rom: rom.value, renameAsSource: renameAsSource.value }) + .updateRom({ rom: rom.value }) .then(({ data }) => { emitter?.emit("snackbarShow", { msg: "Rom updated successfully!", @@ -516,17 +519,15 @@ onBeforeUnmount(() => { t("rom.rename-file-part1", { source: selectedCover?.name }) }} - + {{ t("rom.rename-file-part2") }}
{{ t("rom.rename-file-part3") }}{{ rom?.fs_name_no_tags }}.{{ rom?.fs_extension }} + >{{ rom.fs_name }}
{{ t("rom.rename-file-part4") }}{{ selectedMatchRom?.name }}.{{ rom?.fs_extension }}{{ selectedMatchRom.name }}.{{ rom.fs_extension }}
{ @@ -213,7 +211,6 @@ async function updateRom({ return api.put(`/roms/${rom.id}`, formData, { params: { - rename_as_source: renameAsSource, remove_cover: removeCover, unmatch_metadata: unmatch, }, From 0a73bb1d3d3ffad15860fff025099270d7d380d7 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Wed, 23 Apr 2025 13:37:16 -0400 Subject: [PATCH 03/14] Fix type issues --- frontend/src/components/common/Game/Dialog/MatchRom.vue | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/common/Game/Dialog/MatchRom.vue b/frontend/src/components/common/Game/Dialog/MatchRom.vue index fda44d3f4..0830cd7af 100644 --- a/frontend/src/components/common/Game/Dialog/MatchRom.vue +++ b/frontend/src/components/common/Game/Dialog/MatchRom.vue @@ -205,9 +205,10 @@ async function updateRom( // Set the properties from the selected rom rom.value = { ...rom.value, - fs_name: renameAsSource.value - ? `${selectedMatchRom.value.name}.${rom.value.fs_extension}` - : rom.value.fs_name, + fs_name: + renameAsSource.value && selectedMatchRom.value + ? `${selectedMatchRom.value.name}.${rom.value.fs_extension}` + : rom.value.fs_name, igdb_id: selectedRom.igdb_id || null, moby_id: selectedRom.moby_id || null, ss_id: selectedRom.ss_id || null, @@ -502,7 +503,7 @@ onBeforeUnmount(() => { - + Date: Wed, 23 Apr 2025 13:38:10 -0400 Subject: [PATCH 04/14] rename renameAsSource --- .../components/common/Game/Dialog/MatchRom.vue | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/common/Game/Dialog/MatchRom.vue b/frontend/src/components/common/Game/Dialog/MatchRom.vue index 0830cd7af..dfa05fc4d 100644 --- a/frontend/src/components/common/Game/Dialog/MatchRom.vue +++ b/frontend/src/components/common/Game/Dialog/MatchRom.vue @@ -37,7 +37,7 @@ const matchedRoms = ref([]); const filteredMatchedRoms = ref(); const emitter = inject>("emitter"); const showSelectSource = ref(false); -const renameAsSource = ref(false); +const renameFromSource = ref(false); const selectedMatchRom = ref(); const selectedCover = ref(); const sources = ref([]); @@ -182,7 +182,7 @@ function confirm() { } function toggleRenameAsSource() { - renameAsSource.value = !renameAsSource.value; + renameFromSource.value = !renameFromSource.value; } function backToMatched() { @@ -190,7 +190,7 @@ function backToMatched() { selectedCover.value = undefined; selectedMatchRom.value = undefined; sources.value = []; - renameAsSource.value = false; + renameFromSource.value = false; } async function updateRom( @@ -206,7 +206,7 @@ async function updateRom( rom.value = { ...rom.value, fs_name: - renameAsSource.value && selectedMatchRom.value + renameFromSource.value && selectedMatchRom.value ? `${selectedMatchRom.value.name}.${rom.value.fs_extension}` : rom.value.fs_name, igdb_id: selectedRom.igdb_id || null, @@ -260,7 +260,7 @@ function closeDialog() { showSelectSource.value = false; selectedCover.value = undefined; selectedMatchRom.value = undefined; - renameAsSource.value = false; + renameFromSource.value = false; } onBeforeUnmount(() => { @@ -508,11 +508,11 @@ onBeforeUnmount(() => { {{ - selectedCover && renameAsSource + selectedCover && renameFromSource ? "mdi-checkbox-outline" : "mdi-checkbox-blank-outline" }} { t("rom.rename-file-part1", { source: selectedCover?.name }) }} - + {{ t("rom.rename-file-part2") }}
{{ t("rom.rename-file-part3") }} Date: Wed, 23 Apr 2025 13:47:23 -0400 Subject: [PATCH 05/14] [ROMM-1849] Fix excluding files in multi-file ROM --- backend/handler/filesystem/roms_handler.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/backend/handler/filesystem/roms_handler.py b/backend/handler/filesystem/roms_handler.py index 1c1e74216..994d6520b 100644 --- a/backend/handler/filesystem/roms_handler.py +++ b/backend/handler/filesystem/roms_handler.py @@ -273,14 +273,27 @@ class FSRomsHandler(FSHandler): abs_fs_path = f"{LIBRARY_BASE_PATH}/{roms_path}" # Absolute path to roms rom_files: list[RomFile] = [] + excluded_file_names = cm.get_config().EXCLUDED_MULTI_PARTS_FILES + excluded_file_exts = cm.get_config().EXCLUDED_MULTI_PARTS_EXT + # Check if rom is a multi-part rom if os.path.isdir(f"{abs_fs_path}/{rom}"): for f_path, file in iter_files(f"{abs_fs_path}/{rom}", recursive=True): + # Check if file is excluded + if file in excluded_file_names: + continue + if any(file.endswith(ext) for ext in excluded_file_exts): + continue + rom_files.append( self._build_rom_file(f_path.relative_to(LIBRARY_BASE_PATH), file) ) else: - rom_files.append(self._build_rom_file(Path(roms_path), rom)) + # Check if file is excluded + if rom not in excluded_file_names and not any( + rom.endswith(ext) for ext in excluded_file_exts + ): + rom_files.append(self._build_rom_file(Path(roms_path), rom)) return rom_files From cc899b54e2885fb6c50953b38de08c79bcd89d53 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Wed, 23 Apr 2025 13:52:11 -0400 Subject: [PATCH 06/14] refactor exclude_files fnct --- backend/handler/filesystem/base_handler.py | 7 +++---- backend/handler/filesystem/firmware_handler.py | 2 +- backend/handler/filesystem/roms_handler.py | 8 ++------ backend/handler/filesystem/tests/test_fs.py | 14 +++++--------- 4 files changed, 11 insertions(+), 20 deletions(-) diff --git a/backend/handler/filesystem/base_handler.py b/backend/handler/filesystem/base_handler.py index 297b3b017..70505902a 100644 --- a/backend/handler/filesystem/base_handler.py +++ b/backend/handler/filesystem/base_handler.py @@ -105,10 +105,9 @@ class FSHandler: match = EXTENSION_REGEX.search(file_name) return match.group(1) if match else "" - def _exclude_files(self, files, filetype) -> list[str]: - cnfg = cm.get_config() - excluded_extensions = getattr(cnfg, f"EXCLUDED_{filetype.upper()}_EXT") - excluded_names = getattr(cnfg, f"EXCLUDED_{filetype.upper()}_FILES") + def _exclude_single_files(self, files) -> list[str]: + excluded_extensions = cm.get_config().EXCLUDED_SINGLE_EXT + excluded_names = cm.get_config().EXCLUDED_SINGLE_FILES excluded_files: list = [] for file_name in files: diff --git a/backend/handler/filesystem/firmware_handler.py b/backend/handler/filesystem/firmware_handler.py index 82b70d798..9d5b565f1 100644 --- a/backend/handler/filesystem/firmware_handler.py +++ b/backend/handler/filesystem/firmware_handler.py @@ -43,7 +43,7 @@ class FSFirmwareHandler(FSHandler): except IndexError as exc: raise FirmwareNotFoundException(platform_fs_slug) from exc - return [f for f in self._exclude_files(fs_firmware_files, "single")] + return [f for f in self._exclude_single_files(fs_firmware_files)] def get_firmware_file_size(self, firmware_path: str, file_name: str): files = [f"{LIBRARY_BASE_PATH}/{firmware_path}/{file_name}"] diff --git a/backend/handler/filesystem/roms_handler.py b/backend/handler/filesystem/roms_handler.py index 994d6520b..5b436a23f 100644 --- a/backend/handler/filesystem/roms_handler.py +++ b/backend/handler/filesystem/roms_handler.py @@ -289,11 +289,7 @@ class FSRomsHandler(FSHandler): self._build_rom_file(f_path.relative_to(LIBRARY_BASE_PATH), file) ) else: - # Check if file is excluded - if rom not in excluded_file_names and not any( - rom.endswith(ext) for ext in excluded_file_exts - ): - rom_files.append(self._build_rom_file(Path(roms_path), rom)) + rom_files.append(self._build_rom_file(Path(roms_path), rom)) return rom_files @@ -437,7 +433,7 @@ class FSRomsHandler(FSHandler): fs_roms: list[dict] = [ {"multi": False, "fs_name": rom} - for rom in self._exclude_files(fs_single_roms, "single") + for rom in self._exclude_single_files(fs_single_roms) ] + [ {"multi": True, "fs_name": rom} for rom in self._exclude_multi_roms(fs_multi_roms) diff --git a/backend/handler/filesystem/tests/test_fs.py b/backend/handler/filesystem/tests/test_fs.py index 1cc9d9192..076556c79 100644 --- a/backend/handler/filesystem/tests/test_fs.py +++ b/backend/handler/filesystem/tests/test_fs.py @@ -39,7 +39,7 @@ def test_get_roms(): assert roms[1]["multi"] -def test_exclude_files(): +def test_exclude_single_files(): from config.config_manager import ConfigManager empty_config_file = os.path.join( @@ -52,50 +52,46 @@ def test_exclude_files(): cm.add_exclusion("EXCLUDED_SINGLE_FILES", "Super Mario 64 (J) (Rev A) [Part 1].z64") - filtered_files = fs_rom_handler._exclude_files( + filtered_files = fs_rom_handler._exclude_single_files( files=[ "Super Mario 64 (J) (Rev A) [Part 1].z64", "Super Mario 64 (J) (Rev A) [Part 2].z64", ], - filetype="single", ) assert len(filtered_files) == 1 cm.add_exclusion("EXCLUDED_SINGLE_EXT", "z64") - filtered_files = fs_rom_handler._exclude_files( + filtered_files = fs_rom_handler._exclude_single_files( files=[ "Super Mario 64 (J) (Rev A) [Part 1].z64", "Super Mario 64 (J) (Rev A) [Part 2].z64", ], - filetype="single", ) assert len(filtered_files) == 0 cm.add_exclusion("EXCLUDED_SINGLE_FILES", "*.z64") - filtered_files = fs_rom_handler._exclude_files( + filtered_files = fs_rom_handler._exclude_single_files( files=[ "Super Mario 64 (J) (Rev A) [Part 1].z64", "Super Mario 64 (J) (Rev A) [Part 2].z64", ], - filetype="single", ) assert len(filtered_files) == 0 cm.add_exclusion("EXCLUDED_SINGLE_FILES", "_.*") - filtered_files = fs_rom_handler._exclude_files( + filtered_files = fs_rom_handler._exclude_single_files( files=[ "Links Awakening.nsp", "_.Links Awakening.nsp", "Kirby's Adventure.nsp", "_.Kirby's Adventure.nsp", ], - filetype="single", ) assert len(filtered_files) == 2 From 9e6a29ef5d9ff943f243f3f94bc312eb78a00ebd Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Wed, 23 Apr 2025 14:11:35 -0400 Subject: [PATCH 07/14] [ROMM-1845] Sibling roms should include ss_id --- .../versions/0038_add_ssid_to_sibling_roms.py | 90 +++++++++++++++++++ .../src/components/Details/Info/FileInfo.vue | 13 +-- 2 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 backend/alembic/versions/0038_add_ssid_to_sibling_roms.py diff --git a/backend/alembic/versions/0038_add_ssid_to_sibling_roms.py b/backend/alembic/versions/0038_add_ssid_to_sibling_roms.py new file mode 100644 index 000000000..cc4bf9a7b --- /dev/null +++ b/backend/alembic/versions/0038_add_ssid_to_sibling_roms.py @@ -0,0 +1,90 @@ +"""empty message + +Revision ID: 0038_add_ssid_to_sibling_roms +Revises: 0037_virtual_rom_columns +Create Date: 2025-04-23 00:00:00.000000 + +""" + +import sqlalchemy as sa +from alembic import op +from utils.database import is_postgresql + +# revision identifiers, used by Alembic. +revision = "0038_add_ssid_to_sibling_roms" +down_revision = "0037_virtual_rom_columns" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + connection = op.get_bind() + null_safe_equal_operator = ( + "IS NOT DISTINCT FROM" if is_postgresql(connection) else "<=>" + ) + + connection.execute( + sa.text( + f""" + CREATE OR REPLACE VIEW sibling_roms AS + SELECT + r1.id AS rom_id, + r2.id AS sibling_rom_id, + r1.platform_id AS platform_id, + NOW() AS created_at, + NOW() AS updated_at, + CASE WHEN r1.igdb_id {null_safe_equal_operator} r2.igdb_id THEN r1.igdb_id END AS igdb_id, + CASE WHEN r1.moby_id {null_safe_equal_operator} r2.moby_id THEN r1.moby_id END AS moby_id, + CASE WHEN r1.ss_id {null_safe_equal_operator} r2.ss_id THEN r1.ss_id END AS ss_id + FROM + roms r1 + JOIN + roms r2 + ON + r1.platform_id = r2.platform_id + AND r1.id != r2.id + AND ( + (r1.igdb_id = r2.igdb_id AND r1.igdb_id IS NOT NULL) + OR + (r1.moby_id = r2.moby_id AND r1.moby_id IS NOT NULL) + OR + (r1.ss_id = r2.ss_id AND r1.ss_id IS NOT NULL) + ); + """ # nosec B608 + ), + ) + + +def downgrade() -> None: + connection = op.get_bind() + null_safe_equal_operator = ( + "IS NOT DISTINCT FROM" if is_postgresql(connection) else "<=>" + ) + + connection.execute( + sa.text( + f""" + CREATE VIEW sibling_roms AS + SELECT + r1.id AS rom_id, + r2.id AS sibling_rom_id, + r1.platform_id AS platform_id, + NOW() AS created_at, + NOW() AS updated_at, + CASE WHEN r1.igdb_id {null_safe_equal_operator} r2.igdb_id THEN r1.igdb_id END AS igdb_id, + CASE WHEN r1.moby_id {null_safe_equal_operator} r2.moby_id THEN r1.moby_id END AS moby_id + FROM + roms r1 + JOIN + roms r2 + ON + r1.platform_id = r2.platform_id + AND r1.id != r2.id + AND ( + (r1.igdb_id = r2.igdb_id AND r1.igdb_id IS NOT NULL) + OR + (r1.moby_id = r2.moby_id AND r1.moby_id IS NOT NULL) + ); + """ # nosec B608 + ), + ) diff --git a/frontend/src/components/Details/Info/FileInfo.vue b/frontend/src/components/Details/Info/FileInfo.vue index 1faed28e1..a82cfb510 100644 --- a/frontend/src/components/Details/Info/FileInfo.vue +++ b/frontend/src/components/Details/Info/FileInfo.vue @@ -171,16 +171,19 @@ watch( class="ml-2 text-grey-lighten-2" style="padding: 10px 14px" @click="toggleMainSibling" - > + {{ + > + {{ romUser.is_main_sibling ? "mdi-checkbox-outline" : "mdi-checkbox-blank-outline" - }}{{ romUser.is_main_sibling ? "" : t("rom.default") }} + }} + + {{ romUser.is_main_sibling ? "" : t("rom.default") }} +
From 1acb33fb683b17535032ec3c07758236ceef5f05 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Wed, 23 Apr 2025 14:17:29 -0400 Subject: [PATCH 08/14] fix downward migraiton --- backend/alembic/versions/0038_add_ssid_to_sibling_roms.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/alembic/versions/0038_add_ssid_to_sibling_roms.py b/backend/alembic/versions/0038_add_ssid_to_sibling_roms.py index cc4bf9a7b..9fa40e7b1 100644 --- a/backend/alembic/versions/0038_add_ssid_to_sibling_roms.py +++ b/backend/alembic/versions/0038_add_ssid_to_sibling_roms.py @@ -61,6 +61,8 @@ def downgrade() -> None: "IS NOT DISTINCT FROM" if is_postgresql(connection) else "<=>" ) + connection.execute(sa.text("DROP VIEW IF EXISTS sibling_roms;")) + connection.execute( sa.text( f""" From c2747c5185a38d7e1079a9cea7f607c71a12be49 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Wed, 23 Apr 2025 14:24:24 -0400 Subject: [PATCH 09/14] file name matching should be reverse match --- backend/handler/filesystem/base_handler.py | 11 +++++------ backend/handler/filesystem/firmware_handler.py | 2 +- backend/handler/filesystem/roms_handler.py | 18 +++++++++++++----- backend/handler/filesystem/tests/test_fs.py | 10 +++++----- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/backend/handler/filesystem/base_handler.py b/backend/handler/filesystem/base_handler.py index 70505902a..64de4f8d8 100644 --- a/backend/handler/filesystem/base_handler.py +++ b/backend/handler/filesystem/base_handler.py @@ -101,11 +101,11 @@ class FSHandler: file_name_no_extension = self.get_file_name_with_no_extension(file_name) return TAG_REGEX.split(file_name_no_extension)[0].strip() - def parse_file_extension(self, file_name) -> str: + def parse_file_extension(self, file_name: str) -> str: match = EXTENSION_REGEX.search(file_name) return match.group(1) if match else "" - def _exclude_single_files(self, files) -> list[str]: + def exclude_single_files(self, files: list[str]) -> list[str]: excluded_extensions = cm.get_config().EXCLUDED_SINGLE_EXT excluded_names = cm.get_config().EXCLUDED_SINGLE_FILES excluded_files: list = [] @@ -119,10 +119,9 @@ class FSHandler: excluded_files.append(file_name) # Additionally, check if the file name mathes a pattern in the excluded list. - if len(excluded_names) > 0: - for name in excluded_names: - if file_name == name or fnmatch.fnmatch(file_name, name): - excluded_files.append(file_name) + for name in excluded_names: + if file_name == name or fnmatch.fnmatch(file_name, name): + excluded_files.append(file_name) # Return files that are not in the filtered list. return [f for f in files if f not in excluded_files] diff --git a/backend/handler/filesystem/firmware_handler.py b/backend/handler/filesystem/firmware_handler.py index 9d5b565f1..897366a82 100644 --- a/backend/handler/filesystem/firmware_handler.py +++ b/backend/handler/filesystem/firmware_handler.py @@ -43,7 +43,7 @@ class FSFirmwareHandler(FSHandler): except IndexError as exc: raise FirmwareNotFoundException(platform_fs_slug) from exc - return [f for f in self._exclude_single_files(fs_firmware_files)] + return [f for f in self.exclude_single_files(fs_firmware_files)] def get_firmware_file_size(self, firmware_path: str, file_name: str): files = [f"{LIBRARY_BASE_PATH}/{firmware_path}/{file_name}"] diff --git a/backend/handler/filesystem/roms_handler.py b/backend/handler/filesystem/roms_handler.py index 5b436a23f..8293e52c9 100644 --- a/backend/handler/filesystem/roms_handler.py +++ b/backend/handler/filesystem/roms_handler.py @@ -1,5 +1,6 @@ import binascii import bz2 +import fnmatch import hashlib import os import re @@ -278,15 +279,22 @@ class FSRomsHandler(FSHandler): # Check if rom is a multi-part rom if os.path.isdir(f"{abs_fs_path}/{rom}"): - for f_path, file in iter_files(f"{abs_fs_path}/{rom}", recursive=True): + for f_path, file_name in iter_files(f"{abs_fs_path}/{rom}", recursive=True): # Check if file is excluded - if file in excluded_file_names: + ext = self.parse_file_extension(file_name) + if not ext or ext in excluded_file_exts: continue - if any(file.endswith(ext) for ext in excluded_file_exts): + + if any( + file_name == exc_name or fnmatch.fnmatch(file_name, exc_name) + for exc_name in excluded_file_names + ): continue rom_files.append( - self._build_rom_file(f_path.relative_to(LIBRARY_BASE_PATH), file) + self._build_rom_file( + f_path.relative_to(LIBRARY_BASE_PATH), file_name + ) ) else: rom_files.append(self._build_rom_file(Path(roms_path), rom)) @@ -433,7 +441,7 @@ class FSRomsHandler(FSHandler): fs_roms: list[dict] = [ {"multi": False, "fs_name": rom} - for rom in self._exclude_single_files(fs_single_roms) + for rom in self.exclude_single_files(fs_single_roms) ] + [ {"multi": True, "fs_name": rom} for rom in self._exclude_multi_roms(fs_multi_roms) diff --git a/backend/handler/filesystem/tests/test_fs.py b/backend/handler/filesystem/tests/test_fs.py index 076556c79..ba4178c3c 100644 --- a/backend/handler/filesystem/tests/test_fs.py +++ b/backend/handler/filesystem/tests/test_fs.py @@ -39,7 +39,7 @@ def test_get_roms(): assert roms[1]["multi"] -def test_exclude_single_files(): +def testexclude_single_files(): from config.config_manager import ConfigManager empty_config_file = os.path.join( @@ -52,7 +52,7 @@ def test_exclude_single_files(): cm.add_exclusion("EXCLUDED_SINGLE_FILES", "Super Mario 64 (J) (Rev A) [Part 1].z64") - filtered_files = fs_rom_handler._exclude_single_files( + filtered_files = fs_rom_handler.exclude_single_files( files=[ "Super Mario 64 (J) (Rev A) [Part 1].z64", "Super Mario 64 (J) (Rev A) [Part 2].z64", @@ -63,7 +63,7 @@ def test_exclude_single_files(): cm.add_exclusion("EXCLUDED_SINGLE_EXT", "z64") - filtered_files = fs_rom_handler._exclude_single_files( + filtered_files = fs_rom_handler.exclude_single_files( files=[ "Super Mario 64 (J) (Rev A) [Part 1].z64", "Super Mario 64 (J) (Rev A) [Part 2].z64", @@ -74,7 +74,7 @@ def test_exclude_single_files(): cm.add_exclusion("EXCLUDED_SINGLE_FILES", "*.z64") - filtered_files = fs_rom_handler._exclude_single_files( + filtered_files = fs_rom_handler.exclude_single_files( files=[ "Super Mario 64 (J) (Rev A) [Part 1].z64", "Super Mario 64 (J) (Rev A) [Part 2].z64", @@ -85,7 +85,7 @@ def test_exclude_single_files(): cm.add_exclusion("EXCLUDED_SINGLE_FILES", "_.*") - filtered_files = fs_rom_handler._exclude_single_files( + filtered_files = fs_rom_handler.exclude_single_files( files=[ "Links Awakening.nsp", "_.Links Awakening.nsp", From a6bec01a1789661c4ea768857185948957fd2859 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Wed, 23 Apr 2025 18:38:46 -0400 Subject: [PATCH 10/14] Create email entry in locales for labels --- .../Administration/Users/Dialog/CreateUser.vue | 16 ++++++++++------ .../Administration/Users/Dialog/EditUser.vue | 2 +- frontend/src/locales/de_DE/rom.json | 2 +- frontend/src/locales/de_DE/settings.json | 1 + frontend/src/locales/en_GB/rom.json | 2 +- frontend/src/locales/en_GB/settings.json | 1 + frontend/src/locales/en_US/rom.json | 2 +- frontend/src/locales/en_US/settings.json | 1 + frontend/src/locales/es_ES/rom.json | 2 +- frontend/src/locales/es_ES/settings.json | 1 + frontend/src/locales/fr_FR/rom.json | 2 +- frontend/src/locales/fr_FR/settings.json | 1 + frontend/src/locales/it_IT/rom.json | 2 +- frontend/src/locales/it_IT/settings.json | 1 + frontend/src/locales/ja_JP/rom.json | 2 +- frontend/src/locales/ja_JP/settings.json | 1 + frontend/src/locales/ko_KR/rom.json | 2 +- frontend/src/locales/ko_KR/settings.json | 1 + frontend/src/locales/pt_BR/rom.json | 2 +- frontend/src/locales/pt_BR/settings.json | 1 + frontend/src/locales/ro_RO/rom.json | 2 +- frontend/src/locales/ro_RO/settings.json | 1 + frontend/src/locales/ru_RU/rom.json | 2 +- frontend/src/locales/ru_RU/settings.json | 1 + frontend/src/locales/zh_CN/rom.json | 2 +- frontend/src/locales/zh_CN/settings.json | 1 + frontend/src/views/Auth/Setup.vue | 8 +++++--- frontend/src/views/Settings/UserProfile.vue | 2 +- 28 files changed, 41 insertions(+), 23 deletions(-) diff --git a/frontend/src/components/Settings/Administration/Users/Dialog/CreateUser.vue b/frontend/src/components/Settings/Administration/Users/Dialog/CreateUser.vue index 4c89b900d..3e7c4b9b7 100644 --- a/frontend/src/components/Settings/Administration/Users/Dialog/CreateUser.vue +++ b/frontend/src/components/Settings/Administration/Users/Dialog/CreateUser.vue @@ -5,9 +5,11 @@ import storeUsers from "@/stores/users"; import type { Events } from "@/types/emitter"; import type { Emitter } from "mitt"; import { inject, ref } from "vue"; +import { useI18n } from "vue-i18n"; import { useDisplay } from "vuetify"; // Props +const { t } = useI18n(); const user = ref({ username: "", password: "", @@ -60,7 +62,7 @@ function closeDialog() { @@ -109,14 +111,16 @@ function closeDialog() {