Files
romm/backend/handler/metadata/launchbox_handler/handler.py
T
2026-03-08 17:26:53 -04:00

208 lines
6.8 KiB
Python

import re
from config import LAUNCHBOX_API_ENABLED
from handler.filesystem import fs_rom_handler
from handler.redis_handler import async_cache
from logger.logger import log
from utils.database import safe_int
from ..base_handler import MetadataHandler
from ..base_handler import UniversalPlatformSlug as UPS
from .local_source import LocalSource
from .media import build_rom, local_media_req, remote_media_req
from .platforms import get_platform
from .remote_source import RemoteSource
from .types import (
DASH_COLON_REGEX,
LAUNCHBOX_METADATA_NAME_KEY,
LAUNCHBOX_PLATFORMS_DIR,
LAUNCHBOX_TAG_REGEX,
LaunchboxPlatform,
LaunchboxRom,
)
class LaunchboxHandler(MetadataHandler):
def __init__(self) -> None:
self._local = LocalSource()
self._remote = RemoteSource()
@classmethod
def is_cloud_enabled(cls) -> bool:
return LAUNCHBOX_API_ENABLED
@classmethod
def is_local_enabled(cls) -> bool:
return LAUNCHBOX_PLATFORMS_DIR.exists()
@classmethod
def is_enabled(cls) -> bool:
return cls.is_cloud_enabled() or cls.is_local_enabled()
async def heartbeat(self) -> bool:
return self.is_enabled()
def get_platform(self, slug: str) -> LaunchboxPlatform:
return get_platform(slug)
async def get_rom(
self,
fs_name: str,
platform_slug: str,
keep_tags: bool = False,
*,
remote_enabled: bool = True,
) -> LaunchboxRom:
fallback_rom = LaunchboxRom(launchbox_id=None)
if not self.is_enabled():
return fallback_rom
local = await self._local.get_rom(fs_name, platform_slug)
remote_available = remote_enabled and bool(
await async_cache.exists(LAUNCHBOX_METADATA_NAME_KEY)
)
if local is not None:
launchbox_id_local = safe_int(local.get("DatabaseID"))
remote: dict | None = None
if remote_available:
if launchbox_id_local:
remote = await self._remote.get_by_id(launchbox_id_local)
if remote is None:
local_title = (local.get("Title") or "").strip()
if local_title:
remote = await self._remote.get_rom(
local_title,
platform_slug,
assume_cache_present=True,
)
platform_name = get_platform(platform_slug).get("name")
remote_images = await self._remote.fetch_images(
remote=remote, remote_enabled=remote_available
)
media_req = local_media_req(
platform_name=platform_name,
fs_name=fs_name,
local=local,
remote=remote,
remote_images=remote_images,
remote_enabled=remote_available,
)
return build_rom(
local=local,
remote=remote,
launchbox_id=launchbox_id_local
or (remote.get("DatabaseID") if remote else None),
media_req=media_req,
)
match = LAUNCHBOX_TAG_REGEX.search(fs_name)
launchbox_id_from_tag = int(match.group(1)) if match else None
if launchbox_id_from_tag is not None:
log.debug(f"Found LaunchBox ID tag in filename: {launchbox_id_from_tag}")
rom_by_id = await self.get_rom_by_id(
launchbox_id_from_tag, remote_enabled=remote_enabled
)
if rom_by_id["launchbox_id"]:
log.debug(
f"Successfully matched ROM by LaunchBox ID tag: {fs_name} -> {launchbox_id_from_tag}"
)
return rom_by_id
else:
log.warning(
f"LaunchBox ID {launchbox_id_from_tag} from filename tag not found in LaunchBox"
)
if not remote_available:
return fallback_rom
# `keep_tags` prevents stripping content that is considered a tag, e.g., anything between `()` or `[]`.
# By default, tags are still stripped to keep scan behavior consistent with previous versions.
# If `keep_tags` is True, the full `fs_name` is used for searching.
if not keep_tags:
search_term = fs_rom_handler.get_file_name_with_no_tags(fs_name)
else:
search_term = fs_name
# We replace " - "/"- " with ": " to match Launchbox's naming convention
search_term = re.sub(DASH_COLON_REGEX, ": ", search_term).lower()
# Check if game is scummvm shortname
if platform_slug == UPS.SCUMMVM:
search_term = await self._scummvm_format(search_term)
fallback_rom = LaunchboxRom(launchbox_id=None, name=search_term)
index_entry = await self._remote.get_rom(
search_term,
platform_slug,
assume_cache_present=True,
)
if not index_entry:
return fallback_rom
remote_images = await self._remote.fetch_images(
remote=index_entry, remote_enabled=remote_available
)
media_req = remote_media_req(
remote=index_entry,
remote_images=remote_images,
remote_enabled=remote_available,
)
return build_rom(
local=None,
remote=index_entry,
launchbox_id=index_entry["DatabaseID"],
media_req=media_req,
)
async def get_rom_by_id(
self, database_id: int, *, remote_enabled: bool = True
) -> LaunchboxRom:
if not self.is_enabled():
return LaunchboxRom(launchbox_id=None)
if not remote_enabled:
return LaunchboxRom(launchbox_id=None)
remote = await self._remote.get_by_id(database_id)
if not remote:
return LaunchboxRom(launchbox_id=None)
remote_images = await self._remote.fetch_images(
remote=remote, remote_enabled=remote_enabled
)
media_req = remote_media_req(
remote=remote,
remote_images=remote_images,
remote_enabled=remote_enabled,
)
return build_rom(
local=None,
remote=remote,
launchbox_id=database_id,
media_req=media_req,
)
async def get_matched_roms_by_name(
self, search_term: str, platform_slug: str
) -> list[LaunchboxRom]:
if not self.is_enabled():
return []
rom = await self.get_rom(search_term, platform_slug, keep_tags=True)
return [rom]
async def get_matched_rom_by_id(self, database_id: int) -> LaunchboxRom | None:
if not self.is_enabled():
return None
rom = await self.get_rom_by_id(database_id)
return rom if rom.get("launchbox_id") else None