Files
romm/backend/handler/filesystem/platforms_handler.py
Georges-Antoine Assi 49ef5097e4 fix: split structure detection into has_structure_path_a, fail loudly on bad layouts
Extract has_structure_path_a as its own cached property and have
has_structure_path_b delegate to it, removing duplicated isdir checks.
detect_library_structure and get_platforms_directory now read the named
properties instead of re-implementing the roms-path check inline.

Keep the inconclusive/bad-structure fallback defaulting to Structure A so
a malformed library raises FolderStructureNotMatchException rather than
listing the bare library root as a flat list of platforms.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 14:59:01 -04:00

116 lines
3.9 KiB
Python

import os
from anyio import Path as AnyioPath
from config import LIBRARY_BASE_PATH
from config.config_manager import config_manager as cm
from exceptions.fs_exceptions import (
FolderStructureNotMatchException,
PlatformAlreadyExistsException,
)
from .base_handler import FSHandler, LibraryStructure
class FSPlatformsHandler(FSHandler):
def __init__(self) -> None:
super().__init__(base_path=LIBRARY_BASE_PATH)
def _exclude_platforms(self, platforms: list):
cnfg = cm.get_config()
return [
platform
for platform in platforms
if platform not in cnfg.EXCLUDED_PLATFORMS
]
def create_library_structure(self) -> None:
"""Creates the library structure with a roms folder."""
cnfg = cm.get_config()
roms_path = os.path.join(LIBRARY_BASE_PATH, cnfg.ROMS_FOLDER_NAME)
os.makedirs(roms_path, exist_ok=True)
def detect_library_structure(self) -> LibraryStructure | None:
"""Detects the library structure type.
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
top-level roms folder exists.
"LibraryStructure.B" for Structure B ({platform}/roms) when no
top-level roms folder exists but at least one platform has a
roms subfolder.
None if no structure detected.
"""
cnfg = cm.get_config()
if cnfg.has_structure_path_a:
return LibraryStructure.A
if cnfg.has_structure_path_b:
return LibraryStructure.B
return None
def get_platforms_directory(self) -> str:
cnfg = cm.get_config()
# 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:
cnfg = cm.get_config()
# Fallback to config hint when detection is inconclusive
return (
f"{fs_slug}/{cnfg.ROMS_FOLDER_NAME}"
if cnfg.has_structure_path_b
else f"{cnfg.ROMS_FOLDER_NAME}/{fs_slug}"
)
async def add_platform(self, fs_slug: str) -> None:
"""Adds platform to the filesystem
Args:
fs_slug: platform slug
"""
platform_path = self.get_platform_fs_structure(fs_slug)
try:
await self.make_directory(platform_path)
except FileNotFoundError as e:
raise PlatformAlreadyExistsException(fs_slug) from e
async def get_platforms(self) -> list[str]:
"""Retrieves all platforms from the filesystem.
Returns:
List of platform slugs.
"""
cnfg = cm.get_config()
try:
platforms = await self.list_directories(path=self.get_platforms_directory())
except FileNotFoundError as e:
raise FolderStructureNotMatchException() from e
# For Structure B, only include directories that have a roms subfolder
structure = self.detect_library_structure()
if structure == LibraryStructure.B:
filtered_platforms: list[str] = []
for platform in platforms:
roms_path = AnyioPath(
os.path.join(LIBRARY_BASE_PATH, platform, cnfg.ROMS_FOLDER_NAME)
)
if await roms_path.exists():
filtered_platforms.append(platform)
platforms = filtered_platforms
return self._exclude_platforms(platforms)