diff --git a/backend/config/config_manager.py b/backend/config/config_manager.py index 16883284a..54bbedec5 100644 --- a/backend/config/config_manager.py +++ b/backend/config/config_manager.py @@ -139,12 +139,16 @@ class Config: self.__dict__.update(entries) @functools.cached_property - def has_structure_path_b(self) -> bool: - # Structure A (roms/{platform}) takes priority: if the top-level roms - # folder exists, never claim Structure B even if some platform dirs - # happen to contain a roms sub-folder. + def has_structure_path_a(self) -> bool: + # Structure A ({roms_folder}/{platform}) takes priority: if the top-level roms + # folder exists, claim Structure A even if some platform dirs happen to + # contain a {roms_folder} sub-folder. roms_path = os.path.join(LIBRARY_BASE_PATH, self.ROMS_FOLDER_NAME) - if os.path.isdir(roms_path): + return os.path.isdir(roms_path) + + @functools.cached_property + def has_structure_path_b(self) -> bool: + if self.has_structure_path_a: return False pattern = os.path.join( @@ -153,6 +157,7 @@ class Config: for match in glob.iglob(pattern): if os.path.isdir(match): return True + return False diff --git a/backend/handler/filesystem/platforms_handler.py b/backend/handler/filesystem/platforms_handler.py index 8d1c4f5f2..7e204e4d7 100644 --- a/backend/handler/filesystem/platforms_handler.py +++ b/backend/handler/filesystem/platforms_handler.py @@ -33,9 +33,9 @@ class FSPlatformsHandler(FSHandler): def detect_library_structure(self) -> LibraryStructure | None: """Detects the library structure type. - Structure A (roms/{platform}) takes priority over Structure B - ({platform}/roms) so that existing libraries are not broken when a - stray {platform}/roms directory happens to exist alongside them. + Structure A ({roms_folder}/{platform}) takes priority over Structure B + ({platform}/{roms_folder}) so that existing libraries are not broken when a + stray {platform}/{roms_folder} directory happens to exist alongside them. Returns: "LibraryStructure.A" for Structure A (roms/{platform}) when the @@ -47,8 +47,7 @@ class FSPlatformsHandler(FSHandler): """ cnfg = cm.get_config() - roms_path = os.path.join(LIBRARY_BASE_PATH, cnfg.ROMS_FOLDER_NAME) - if os.path.exists(roms_path): + if cnfg.has_structure_path_a: return LibraryStructure.A if cnfg.has_structure_path_b: @@ -59,7 +58,10 @@ class FSPlatformsHandler(FSHandler): def get_platforms_directory(self) -> str: cnfg = cm.get_config() - # Fallback to config hint when detection is inconclusive + # Fallback to config hint when detection is inconclusive: default to + # Structure A (roms/{platform}) so a malformed library fails loudly + # (FolderStructureNotMatchException) rather than treating the bare + # library root as a flat list of platforms. return "" if cnfg.has_structure_path_b else cnfg.ROMS_FOLDER_NAME def get_platform_fs_structure(self, fs_slug: str) -> str: diff --git a/backend/tests/handler/filesystem/test_platforms_handler.py b/backend/tests/handler/filesystem/test_platforms_handler.py index 4743b7c30..5a6615c73 100644 --- a/backend/tests/handler/filesystem/test_platforms_handler.py +++ b/backend/tests/handler/filesystem/test_platforms_handler.py @@ -101,6 +101,7 @@ class TestFSPlatformsHandler: self, handler: FSPlatformsHandler, config ): """Test get_platforms_directory with Structure B ({platform}/roms)""" + config.has_structure_path_a = False config.has_structure_path_b = True with patch( "handler.filesystem.platforms_handler.cm.get_config", return_value=config @@ -203,7 +204,7 @@ class TestFSPlatformsHandler: self, handler: FSPlatformsHandler, config ): """Test that get_platforms calls list_directories with correct path""" - config.has_structure_path_b = False + config.has_structure_path_a = True with patch( "handler.filesystem.platforms_handler.cm.get_config", return_value=config ): @@ -217,6 +218,8 @@ class TestFSPlatformsHandler: self, handler: FSPlatformsHandler, config ): """Test that get_platforms calls list_directories with empty path for normal structure""" + config.has_structure_path_a = False + config.has_structure_path_b = True with patch( "handler.filesystem.platforms_handler.cm.get_config", return_value=config ): @@ -315,66 +318,62 @@ class TestFSPlatformsHandler: self, handler: FSPlatformsHandler, config ): """Test detect_library_structure detects Structure A (roms/{platform})""" - roms_path = f"{LIBRARY_BASE_PATH}/{config.ROMS_FOLDER_NAME}" + config.has_structure_path_a = True config.has_structure_path_b = False with patch( "handler.filesystem.platforms_handler.cm.get_config", return_value=config ): - with patch("os.path.exists") as mock_exists: - mock_exists.return_value = True - - result = handler.detect_library_structure() - assert result == LibraryStructure.A - mock_exists.assert_called_once_with(roms_path) + result = handler.detect_library_structure() + assert result == LibraryStructure.A def test_detect_library_structure_structure_b( self, handler: FSPlatformsHandler, config ): """Test detect_library_structure detects Structure B ({platform}/roms)""" + config.has_structure_path_a = False config.has_structure_path_b = True with patch( "handler.filesystem.platforms_handler.cm.get_config", return_value=config ): - with patch("os.path.exists", return_value=False): - result = handler.detect_library_structure() - assert result == LibraryStructure.B + result = handler.detect_library_structure() + assert result == LibraryStructure.B def test_detect_library_structure_a_takes_priority_over_b( self, handler: FSPlatformsHandler, config ): """Structure A is reported when the top-level roms folder exists, even when Structure B directories are also present.""" + config.has_structure_path_a = True config.has_structure_path_b = True with patch( "handler.filesystem.platforms_handler.cm.get_config", return_value=config ): - with patch("os.path.exists", return_value=True): - result = handler.detect_library_structure() - assert result == LibraryStructure.A + result = handler.detect_library_structure() + assert result == LibraryStructure.A def test_detect_library_structure_none(self, handler: FSPlatformsHandler, config): """Test detect_library_structure returns None when no structure detected""" + config.has_structure_path_a = False config.has_structure_path_b = False with patch( "handler.filesystem.platforms_handler.cm.get_config", return_value=config ): - with patch("os.path.exists", return_value=False): - result = handler.detect_library_structure() - assert result is None + result = handler.detect_library_structure() + assert result is None def test_detect_library_structure_empty_library( self, handler: FSPlatformsHandler, config ): """Test detect_library_structure with empty library directory""" + config.has_structure_path_a = False config.has_structure_path_b = False with patch( "handler.filesystem.platforms_handler.cm.get_config", return_value=config ): - with patch("os.path.exists", return_value=False): - result = handler.detect_library_structure() - assert result is None + result = handler.detect_library_structure() + assert result is None