diff --git a/backend/config/config_manager.py b/backend/config/config_manager.py index 06206bc63..a98bd14e2 100644 --- a/backend/config/config_manager.py +++ b/backend/config/config_manager.py @@ -76,6 +76,7 @@ class Config: EXCLUDED_MULTI_FILES: list[str] EXCLUDED_MULTI_PARTS_EXT: list[str] EXCLUDED_MULTI_PARTS_FILES: list[str] + GAMELIST_AUTO_EXPORT_ON_SCAN: bool PLATFORMS_BINDING: dict[str, str] PLATFORMS_VERSIONS: dict[str, str] ROMS_FOLDER_NAME: str @@ -223,6 +224,9 @@ def _parse_config(self): FIRMWARE_FOLDER_NAME=pydash.get( self._raw_config, "filesystem.firmware_folder", "bios" ), + GAMELIST_AUTO_EXPORT_ON_SCAN=pydash.get( + self._raw_config, "scan.export_gamelist", False + ), SKIP_HASH_CALCULATION=pydash.get( self._raw_config, "filesystem.skip_hash_calculation", False ), @@ -364,6 +368,9 @@ def _validate_config(self): "Invalid config.yml: exclude.roms.multi_file.parts.names must be a list" ) sys.exit(3) + if not isinstance(self.config.GAMELIST_AUTO_EXPORT_ON_SCAN, bool): + log.critical("Invalid config.yml: scan.export_gamelist must be a boolean") + sys.exit(3) if not isinstance(self.config.PLATFORMS_BINDING, dict): log.critical("Invalid config.yml: system.platforms must be a dictionary") @@ -570,6 +577,7 @@ def _update_config_file(self) -> None: "language": self.config.SCAN_LANGUAGE_PRIORITY, }, "media": self.config.SCAN_MEDIA, + "export_gamelist": self.config.GAMELIST_AUTO_EXPORT_ON_SCAN, }, } diff --git a/backend/endpoints/sockets/scan.py b/backend/endpoints/sockets/scan.py index 81bf69770..a2ff80cf5 100644 --- a/backend/endpoints/sockets/scan.py +++ b/backend/endpoints/sockets/scan.py @@ -50,6 +50,7 @@ from tasks.tasks import update_job_meta from utils import emoji from utils.context import initialize_context +from utils.gamelist_exporter import GamelistExporter STOP_SCAN_FLAG: Final = "scan:stop" @@ -683,6 +684,32 @@ async def stop_scan(): log.warning(f" - {p.slug} ({p.fs_slug})") log.info(f"{emoji.EMOJI_CHECK_MARK} Scan completed") + + # Export gamelist.xml if enabled in config + config = cm.get_config() + if config.GAMELIST_AUTO_EXPORT_ON_SCAN: + log.info("Auto-exporting gamelist.xml for all platforms...") + gamelist_exporter = GamelistExporter(local_export=True) + platforms_by_slug = { + p.fs_slug: p for p in db_platform_handler.get_platforms() + } + for platform_slug in platform_list: + platform = platforms_by_slug.get(platform_slug) + if platform: + export_success = await gamelist_exporter.export_platform_to_file( + platform.id, + request=None, + ) + if export_success: + log.info( + f"Auto-exported gamelist.xml for platform {platform.name} after scan" + ) + else: + log.warning( + f"Failed to auto-export gamelist.xml for platform {platform.name} after scan" + ) + log.info("Gamelist.xml auto-export completed.") + await socket_manager.emit("scan:done", scan_stats.to_dict()) except ScanStoppedException: await stop_scan() diff --git a/backend/utils/gamelist_exporter.py b/backend/utils/gamelist_exporter.py index 66987cce8..d091c2ff9 100644 --- a/backend/utils/gamelist_exporter.py +++ b/backend/utils/gamelist_exporter.py @@ -25,7 +25,7 @@ def _format_release_date(self, timestamp: int) -> str: """Format release date to YYYYMMDDTHHMMSS format""" return datetime.fromtimestamp(timestamp / 1000).strftime("%Y%m%dT%H%M%S") - def _create_game_element(self, rom: Rom, request: Request) -> Element: + def _create_game_element(self, rom: Rom, request: Request | None) -> Element: """Create a element for a ROM""" game = Element("game") @@ -33,6 +33,10 @@ def _create_game_element(self, rom: Rom, request: Request) -> Element: if self.local_export: SubElement(game, "path").text = f"./{rom.fs_name}" else: + if request is None: + raise ValueError( + "Request object must be provided for non-local exports" + ) SubElement(game, "path").text = str( request.url_for( "get_rom_content", @@ -155,7 +159,7 @@ def _create_game_element(self, rom: Rom, request: Request) -> Element: return game - def export_platform_to_xml(self, platform_id: int, request: Request) -> str: + def export_platform_to_xml(self, platform_id: int, request: Request | None) -> str: """Export a platform's ROMs to gamelist.xml format Args: @@ -188,7 +192,7 @@ def export_platform_to_xml(self, platform_id: int, request: Request) -> str: async def export_platform_to_file( self, platform_id: int, - request: Request, + request: Request | None, ) -> bool: """Export platform ROMs to gamelist.xml file in the platform's directory