Merge pull request #2636 from rommapp/hotfix-media-import-gamelist-xml

[HOTFIX] Fix importing media from gamelist.xml
This commit is contained in:
Georges-Antoine Assi
2025-11-10 18:48:25 -05:00
committed by GitHub
4 changed files with 64 additions and 52 deletions

View File

@@ -276,7 +276,7 @@ class FSHandler:
# Async thread-safe directory listing
lock = await self._get_file_lock(str(target_directory))
async with lock:
if not target_directory.exists() or not target_directory.is_dir():
if not target_directory.is_dir():
raise FileNotFoundError(
f"Path does not exist or is not a directory: {str(target_directory)}"
)
@@ -300,7 +300,7 @@ class FSHandler:
# Async thread-safe directory removal
lock = await self._get_file_lock(str(target_directory))
async with lock:
if not target_directory.exists() or not target_directory.is_dir():
if not target_directory.is_dir():
raise FileNotFoundError(
f"Path does not exist or is not a directory: {str(target_directory)}"
)
@@ -414,7 +414,7 @@ class FSHandler:
# Async thread-safe file read
lock = await self._get_file_lock(str(full_path))
async with lock:
if not full_path.exists() or not full_path.is_file():
if not full_path.is_file():
raise FileNotFoundError(f"File not found: {full_path}")
async with await open_file(full_path, "rb") as f:
@@ -442,11 +442,42 @@ class FSHandler:
# Async thread-safe file stream
lock = await self._get_file_lock(str(full_path))
async with lock:
if not full_path.exists() or not full_path.is_file():
if not full_path.is_file():
raise FileNotFoundError(f"File not found: {full_path}")
return await open_file(full_path, "rb")
async def copy_file(self, source_full_path: Path, dest_path: str) -> None:
"""
Copy a file from source to destination.
Args:
source_full_path: Absolute path to the source file
dest_path: Relative path to the destination file
Raises:
FileNotFoundError: If source file does not exist
ValueError: If destination path is invalid
"""
if not source_full_path or not dest_path:
raise ValueError("Source and destination paths cannot be empty")
# Validate and normalize path
dest_full_path = self.validate_path(dest_path)
# Use locks for both source and destination
source_lock = await self._get_file_lock(str(source_full_path))
dest_lock = await self._get_file_lock(str(dest_full_path))
# Async thread-safe file copy
async with source_lock, dest_lock:
if not source_full_path.is_file():
raise FileNotFoundError(f"Source file not found: {source_full_path}")
# Create destination directory if needed
dest_full_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(str(source_full_path), str(dest_full_path))
async def move_file_or_folder(self, source_path: str, dest_path: str) -> None:
"""
Move a file from source to destination.
@@ -479,7 +510,6 @@ class FSHandler:
# Create destination directory if needed
dest_full_path.parent.mkdir(parents=True, exist_ok=True)
shutil.move(str(source_full_path), str(dest_full_path))
async def remove_file(self, file_path: str) -> None:
@@ -528,7 +558,7 @@ class FSHandler:
# Async thread-safe directory listing
lock = await self._get_file_lock(str(full_path))
async with lock:
if not full_path.exists() or not full_path.is_dir():
if not full_path.is_dir():
raise FileNotFoundError(f"Directory not found: {full_path}")
return [f for _, f in iter_files(str(full_path), recursive=False)]
@@ -552,7 +582,7 @@ class FSHandler:
# Async thread-safe existence check
lock = await self._get_file_lock(str(full_path))
async with lock:
return full_path.exists() and full_path.is_file()
return full_path.is_file()
async def get_file_size(self, file_path: str) -> int:
"""
@@ -576,7 +606,7 @@ class FSHandler:
# Async thread-safe file size retrieval
lock = await self._get_file_lock(str(full_path))
async with lock:
if not full_path.exists() or not full_path.is_file():
if not full_path.is_file():
raise FileNotFoundError(f"File not found: {full_path}")
return full_path.stat().st_size

View File

@@ -1,6 +1,5 @@
import gzip
import os
import shutil
from io import BytesIO
from pathlib import Path
@@ -69,7 +68,7 @@ class FSResourcesHandler(FSHandler):
size: size of the cover
"""
cover_file = f"{entity.fs_resources_path}/cover"
await self.make_directory(f"{cover_file}")
await self.make_directory(cover_file)
# Handle file:// URLs for gamelist.xml
if url_cover.startswith("file://"):
@@ -77,13 +76,16 @@ class FSResourcesHandler(FSHandler):
file_path = Path(url_cover[7:]) # Remove "file://" prefix
if file_path.exists():
# Copy the file to the resources directory
dest_path = self.validate_path(f"{cover_file}/{size.value}.png")
shutil.copy2(file_path, dest_path)
dest_path = f"{cover_file}/{size.value}.png"
await self.copy_file(file_path, dest_path)
if ENABLE_SCHEDULED_CONVERT_IMAGES_TO_WEBP:
self.image_converter.convert_to_webp(dest_path, force=True)
self.image_converter.convert_to_webp(
self.validate_path(f"{cover_file}/{size.value}.png"),
force=True,
)
else:
log.warning(f"File not found: {file_path}")
log.warning(f"Cover file not found: {file_path}")
return None
except Exception as exc:
log.error(f"Unable to copy cover file {url_cover}: {str(exc)}")
@@ -245,8 +247,7 @@ class FSResourcesHandler(FSHandler):
file_path = Path(url_screenhot[7:]) # Remove "file://" prefix
if file_path.exists():
# Copy the file to the resources directory
dest_path = self.validate_path(f"{screenshot_path}/{idx}.jpg")
shutil.copy2(file_path, dest_path)
await self.copy_file(file_path, f"{screenshot_path}/{idx}.jpg")
else:
log.warning(f"Screenshot file not found: {file_path}")
return None
@@ -357,8 +358,7 @@ class FSResourcesHandler(FSHandler):
file_path = Path(url_manual[7:]) # Remove "file://" prefix
if file_path.exists():
# Copy the file to the resources directory
dest_path = self.validate_path(f"{manual_path}/{rom.id}.pdf")
shutil.copy2(file_path, dest_path)
await self.copy_file(file_path, f"{manual_path}/{rom.id}.pdf")
else:
log.warning(f"Manual file not found: {file_path}")
return None
@@ -466,12 +466,12 @@ class FSResourcesHandler(FSHandler):
) -> str:
return os.path.join("roms", str(platform_id), str(rom_id), media_type.value)
async def store_media_file(self, url: str, path: str) -> None:
async def store_media_file(self, url: str, dest_path: str) -> None:
httpx_client = ctx_httpx_client.get()
directory, filename = os.path.split(path)
directory, filename = os.path.split(dest_path)
if await self.file_exists(path):
log.debug(f"Media file {path} already exists, skipping download")
if await self.file_exists(dest_path):
log.debug(f"Media file {dest_path} already exists, skipping download")
return
# Ensure destination directory exists
@@ -482,9 +482,7 @@ class FSResourcesHandler(FSHandler):
try:
file_path = Path(url[7:]) # Remove "file://" prefix
if file_path.exists():
# Validate the destination path
dest_path = self.validate_path(path)
shutil.copy2(file_path, dest_path)
await self.copy_file(file_path, dest_path)
except Exception as exc:
log.error(f"Unable to copy media file {url}: {str(exc)}")
return None

View File

@@ -326,27 +326,15 @@ class GamelistHandler(MetadataHandler):
gamelist_metadata=rom_metadata,
)
platform_dir = fs_platform_handler.get_plaform_fs_structure(
platform.fs_slug
)
# Choose which cover style to use
cover_path = rom_metadata["box2d_url"] or rom_metadata["image_url"]
if cover_path:
cover_path_path = fs_platform_handler.validate_path(
f"{platform_dir}/{cover_path}"
)
rom_data["url_cover"] = f"file://{str(cover_path_path)}"
cover_url = rom_metadata["box2d_url"] or rom_metadata["image_url"]
if cover_url:
rom_data["url_cover"] = cover_url
# Grab the manual
if (
rom_metadata["manual_url"]
and MetadataMediaType.MANUAL in preferred_media_types
):
manual_path = fs_platform_handler.validate_path(
f"{platform_dir}/{rom_metadata['manual_url']}"
)
rom_data["url_manual"] = f"file://{str(manual_path)}"
manual_url = rom_metadata["manual_url"]
if manual_url and MetadataMediaType.MANUAL in preferred_media_types:
rom_data["url_manual"] = manual_url
# Build list of screenshot URLs
url_screenshots = []
@@ -354,18 +342,12 @@ class GamelistHandler(MetadataHandler):
rom_metadata["screenshot_url"]
and MetadataMediaType.SCREENSHOT in preferred_media_types
):
screenshot_path = fs_platform_handler.validate_path(
f"{platform_dir}/{rom_metadata['screenshot_url']}"
)
url_screenshots.append(f"file://{str(screenshot_path)}")
url_screenshots.append(rom_metadata["screenshot_url"])
if (
rom_metadata["title_screen_url"]
and MetadataMediaType.TITLE_SCREEN in preferred_media_types
):
title_screen_path = fs_platform_handler.validate_path(
f"{platform_dir}/{rom_metadata['title_screen_url']}"
)
url_screenshots.append(f"file://{str(title_screen_path)}")
url_screenshots.append(rom_metadata["title_screen_url"])
rom_data["url_screenshots"] = url_screenshots
# Store by filename for matching

View File

@@ -276,7 +276,9 @@ async function stopScan() {
:fs-slug="item.raw.fs_slug"
:size="20"
/>
{{ item.raw.name }}
<div class="ml-1">
{{ item.raw.name }}
</div>
</v-chip>
</template>
</v-select>