moer fixes

This commit is contained in:
Georges-Antoine Assi
2026-03-21 22:57:44 -04:00
parent 8e8baef6f6
commit 5bf3a435ee
6 changed files with 40 additions and 17 deletions

View File

@@ -173,18 +173,22 @@ class FSHandler:
@asynccontextmanager
async def _atomic_write(self, target_path: Path):
"""Context manager for atomic file writing using a system temp file."""
fd, temp_path_str = tempfile.mkstemp()
"""Context manager for atomic file writing.
Creates the temp file in the same directory as the target so that
os.rename is always an atomic same-filesystem operation.
"""
fd, temp_path_str = tempfile.mkstemp(
dir=str(target_path.parent), prefix=".romm_tmp_"
)
temp_path = Path(temp_path_str)
os.close(fd)
try:
yield temp_path
# Move to final location
shutil.move(str(temp_path), str(target_path))
os.replace(str(temp_path), str(target_path))
except Exception:
# Clean up temporary file on error
async_temp = AnyioPath(temp_path)
if await async_temp.exists():
await async_temp.unlink()

View File

@@ -143,9 +143,7 @@ def test_export_gamelist_xml_release_date(platform_with_roms):
release_date = game.find("releasedate")
assert release_date is not None
assert release_date.text is not None
# Should be in YYYYMMDDTHHMMSS format
assert release_date.text.startswith("19920623")
assert release_date.text == "19920623T120000"
def test_export_gamelist_xml_minimal_rom(platform_with_minimal_rom):

View File

@@ -151,7 +151,7 @@ class TestExportMetadata:
assert game["tag"] == "Retro"
assert game["description"] == "A classic platformer game."
assert game["rating"] == "92%"
assert isinstance(game["release"], str) and game["release"].startswith("1992-")
assert game["release"] == "1992-06-23"
assert game["x-region"] == "USA"
assert game["x-language"] == "en"
assert "x-romm-id" in game

View File

@@ -1,4 +1,4 @@
from datetime import datetime
from datetime import UTC, datetime
from xml.etree.ElementTree import ( # trunk-ignore(bandit/B405)
Element,
SubElement,
@@ -23,7 +23,9 @@ class GamelistExporter:
def _format_release_date(self, timestamp: int) -> str:
"""Format release date to YYYYMMDDTHHMMSS format"""
return datetime.fromtimestamp(timestamp / 1000).strftime("%Y%m%dT%H%M%S")
return datetime.fromtimestamp(timestamp / 1000, tz=UTC).strftime(
"%Y%m%dT%H%M%S"
)
def _create_game_element(self, rom: Rom, request: Request | None) -> Element:
"""Create a <game> element for a ROM"""
@@ -180,7 +182,7 @@ class GamelistExporter:
root = Element("gameList")
for rom in roms:
if rom and not rom.missing_from_fs and rom.fs_name != "gamelist.xml":
if rom and not rom.missing_from_fs:
game_element = self._create_game_element(rom, request=request)
root.append(game_element)

View File

@@ -1,5 +1,5 @@
import shutil
from datetime import datetime
from datetime import UTC, datetime
from pathlib import Path
from fastapi import Request
@@ -33,7 +33,7 @@ class PegasusExporter:
def _format_release_date(self, timestamp: int) -> str:
"""Format release date to YYYY-MM-DD format"""
return datetime.fromtimestamp(timestamp / 1000).strftime("%Y-%m-%d")
return datetime.fromtimestamp(timestamp / 1000, tz=UTC).strftime("%Y-%m-%d")
def _format_rating(self, average_rating: float) -> str:
"""Format rating as percentage (0-100%). Input is on 0-10 scale."""

View File

@@ -1,4 +1,5 @@
import enum
import fnmatch
import json
import os
from collections.abc import Sequence
@@ -117,14 +118,32 @@ def process_changes(changes: Sequence[Change]) -> None:
if not ENABLE_RESCAN_ON_FILESYSTEM_CHANGE:
return
# Filter for valid events, ignoring excluded files/dirs from config
# Filter for valid events, applying the same exclusion rules as the scanner:
# exact-match and fnmatch patterns for files, plus excluded directory names
# checked against every path component so events inside excluded dirs are ignored.
config = cm.get_config()
excluded_names = set(config.EXCLUDED_SINGLE_FILES + config.EXCLUDED_MULTI_FILES)
excluded_patterns = (
config.EXCLUDED_SINGLE_FILES
+ config.EXCLUDED_MULTI_FILES
+ config.EXCLUDED_MULTI_PARTS_FILES
)
def _is_excluded(path: str) -> bool:
parts = path.strip("/").split("/")
for part in parts:
if part.startswith(".romm_tmp_"):
return True
if any(
part == pat or fnmatch.fnmatch(part, pat) for pat in excluded_patterns
):
return True
return False
changes = [
change
for change in changes
if change[0] in VALID_EVENTS
and os.path.basename(os.fsdecode(change[1])) not in excluded_names
and not _is_excluded(os.fsdecode(change[1]).split(LIBRARY_BASE_PATH)[-1])
]
if not changes:
return