|
19 | 19 |
|
20 | 20 | from .base_handler import CoverSize, FSHandler |
21 | 21 |
|
| 22 | +LOCAL_FILE_SCHEMES = ("file://", "launchbox-file://") |
| 23 | + |
| 24 | + |
| 25 | +def _resolve_local_file_uri(uri: str) -> Path | None: |
| 26 | + """Resolve a local-file URI to an absolute Path, or None if unsafe/unknown. |
| 27 | +
|
| 28 | + `file://` resolves under the ROM library root. `launchbox-file://` resolves |
| 29 | + under the LaunchBox data root — LaunchBox metadata produces paths relative |
| 30 | + to `/romm/launchbox`, which is not the same as the library root. |
| 31 | + """ |
| 32 | + from handler.filesystem import fs_launchbox_handler, fs_rom_handler |
| 33 | + |
| 34 | + if uri.startswith("launchbox-file://"): |
| 35 | + try: |
| 36 | + return fs_launchbox_handler.validate_path(uri[len("launchbox-file://") :]) |
| 37 | + except ValueError: |
| 38 | + return None |
| 39 | + |
| 40 | + if uri.startswith("file://"): |
| 41 | + try: |
| 42 | + return fs_rom_handler.validate_path(uri[len("file://") :]) |
| 43 | + except ValueError: |
| 44 | + return None |
| 45 | + |
| 46 | + return None |
| 47 | + |
22 | 48 |
|
23 | 49 | def _content_type_essence(header_value: str) -> str: |
24 | 50 | """Return the MIME type token (before parameters), lowercased.""" |
@@ -95,27 +121,21 @@ async def _store_cover( |
95 | 121 | cover_file = f"{entity.fs_resources_path}/cover" |
96 | 122 | await self.make_directory(cover_file) |
97 | 123 |
|
98 | | - # Handle file:// URLs for gamelist.xml |
99 | | - if url_cover.startswith("file://"): |
| 124 | + # Handle local-file URIs from metadata handlers (gamelist, LaunchBox) |
| 125 | + if url_cover.startswith(LOCAL_FILE_SCHEMES): |
100 | 126 | try: |
101 | | - from handler.filesystem import fs_rom_handler |
102 | | - |
103 | | - validated = fs_rom_handler.validate_path( |
104 | | - url_cover[7:] # Remove "file://" prefix |
105 | | - ) |
106 | | - if await AnyioPath(validated).exists(): |
107 | | - # Copy the file to the resources directory |
108 | | - dest_path = f"{cover_file}/{size.value}.png" |
109 | | - await self.copy_file(validated, dest_path) |
110 | | - |
111 | | - if ENABLE_SCHEDULED_CONVERT_IMAGES_TO_WEBP: |
112 | | - self.image_converter.convert_to_webp( |
113 | | - self.validate_path(f"{cover_file}/{size.value}.png"), |
114 | | - force=True, |
115 | | - ) |
116 | | - else: |
117 | | - log.warning(f"Cover file not found: {str(validated)}") |
| 127 | + resolved = _resolve_local_file_uri(url_cover) |
| 128 | + if resolved is None or not await AnyioPath(resolved).exists(): |
| 129 | + log.warning(f"Cover file not found: {url_cover}") |
118 | 130 | return None |
| 131 | + dest_path = f"{cover_file}/{size.value}.png" |
| 132 | + await self.copy_file(resolved, dest_path) |
| 133 | + |
| 134 | + if ENABLE_SCHEDULED_CONVERT_IMAGES_TO_WEBP: |
| 135 | + self.image_converter.convert_to_webp( |
| 136 | + self.validate_path(f"{cover_file}/{size.value}.png"), |
| 137 | + force=True, |
| 138 | + ) |
119 | 139 | except Exception as exc: |
120 | 140 | log.error(f"Unable to copy cover file {url_cover}: {str(exc)}") |
121 | 141 | return None |
@@ -275,20 +295,14 @@ async def _store_screenshot(self, rom: Rom, url_screenhot: str, idx: int): |
275 | 295 | screenshot_path = f"{rom.fs_resources_path}/screenshots" |
276 | 296 | await self.make_directory(screenshot_path) |
277 | 297 |
|
278 | | - # Handle file:// URLs for gamelist.xml |
279 | | - if url_screenhot.startswith("file://"): |
| 298 | + # Handle local-file URIs from metadata handlers (gamelist, LaunchBox) |
| 299 | + if url_screenhot.startswith(LOCAL_FILE_SCHEMES): |
280 | 300 | try: |
281 | | - from handler.filesystem import fs_rom_handler |
282 | | - |
283 | | - validated = fs_rom_handler.validate_path( |
284 | | - url_screenhot[7:] # Remove "file://" prefix |
285 | | - ) |
286 | | - if await AnyioPath(validated).exists(): |
287 | | - # Copy the file to the resources directory |
288 | | - await self.copy_file(validated, f"{screenshot_path}/{idx}.jpg") |
289 | | - else: |
290 | | - log.warning(f"Screenshot file not found: {str(validated)}") |
| 301 | + resolved = _resolve_local_file_uri(url_screenhot) |
| 302 | + if resolved is None or not await AnyioPath(resolved).exists(): |
| 303 | + log.warning(f"Screenshot file not found: {url_screenhot}") |
291 | 304 | return None |
| 305 | + await self.copy_file(resolved, f"{screenshot_path}/{idx}.jpg") |
292 | 306 | except Exception as exc: |
293 | 307 | log.error(f"Unable to copy screenshot file {url_screenhot}: {str(exc)}") |
294 | 308 | return None |
@@ -395,20 +409,14 @@ async def _store_manual(self, rom: Rom, url_manual: str): |
395 | 409 | manual_path = f"{rom.fs_resources_path}/manual" |
396 | 410 | await self.make_directory(manual_path) |
397 | 411 |
|
398 | | - # Handle file:// URLs for gamelist.xml |
399 | | - if url_manual.startswith("file://"): |
| 412 | + # Handle local-file URIs from metadata handlers (gamelist, LaunchBox) |
| 413 | + if url_manual.startswith(LOCAL_FILE_SCHEMES): |
400 | 414 | try: |
401 | | - from handler.filesystem import fs_rom_handler |
402 | | - |
403 | | - validated = fs_rom_handler.validate_path( |
404 | | - url_manual[7:] # Remove "file://" prefix |
405 | | - ) |
406 | | - if await AnyioPath(validated).exists(): |
407 | | - # Copy the file to the resources directory |
408 | | - await self.copy_file(validated, f"{manual_path}/{rom.id}.pdf") |
409 | | - else: |
410 | | - log.warning(f"Manual file not found: {str(validated)}") |
| 415 | + resolved = _resolve_local_file_uri(url_manual) |
| 416 | + if resolved is None or not await AnyioPath(resolved).exists(): |
| 417 | + log.warning(f"Manual file not found: {url_manual}") |
411 | 418 | return None |
| 419 | + await self.copy_file(resolved, f"{manual_path}/{rom.id}.pdf") |
412 | 420 | except Exception as exc: |
413 | 421 | log.error(f"Unable to copy manual file {url_manual}: {str(exc)}") |
414 | 422 | return None |
@@ -542,17 +550,12 @@ async def store_media_file(self, url_media: str, dest_path: str) -> None: |
542 | 550 | # Ensure destination directory exists |
543 | 551 | await self.make_directory(directory) |
544 | 552 |
|
545 | | - # Handle file:// URLs for gamelist.xml |
546 | | - if url_media.startswith("file://"): |
| 553 | + # Handle local-file URIs from metadata handlers (gamelist, LaunchBox) |
| 554 | + if url_media.startswith(LOCAL_FILE_SCHEMES): |
547 | 555 | try: |
548 | | - from handler.filesystem import fs_rom_handler |
549 | | - |
550 | | - validated = fs_rom_handler.validate_path( |
551 | | - url_media[7:] # Remove "file://" prefix |
552 | | - ) |
553 | | - file_path = AnyioPath(validated) |
554 | | - if await file_path.exists(): |
555 | | - await self.copy_file(Path(str(file_path)), dest_path) |
| 556 | + resolved = _resolve_local_file_uri(url_media) |
| 557 | + if resolved is not None and await AnyioPath(resolved).exists(): |
| 558 | + await self.copy_file(resolved, dest_path) |
556 | 559 | except Exception as exc: |
557 | 560 | log.error(f"Unable to copy media file {url_media}: {str(exc)}") |
558 | 561 | return None |
|
0 commit comments