fix(folder_scan): await NFO repair before folder rename
folder_rename_service depends on clean NFO files but repair tasks were fire-and-forget. Now collect all repair tasks and await them with asyncio.gather before validate_and_rename_series_folders runs. Also update tests that mock asyncio.create_task to also mock asyncio.gather since perform_nfo_repair_scan now awaits tasks.
This commit is contained in:
@@ -129,6 +129,7 @@ async def perform_nfo_repair_scan(background_loader=None) -> None:
|
|||||||
queued = 0
|
queued = 0
|
||||||
total = 0
|
total = 0
|
||||||
missing_nfo_count = 0
|
missing_nfo_count = 0
|
||||||
|
repair_tasks: list[asyncio.Task] = []
|
||||||
for series_dir in sorted(anime_dir.iterdir()):
|
for series_dir in sorted(anime_dir.iterdir()):
|
||||||
if not series_dir.is_dir():
|
if not series_dir.is_dir():
|
||||||
continue
|
continue
|
||||||
@@ -137,19 +138,31 @@ async def perform_nfo_repair_scan(background_loader=None) -> None:
|
|||||||
if not nfo_path.exists():
|
if not nfo_path.exists():
|
||||||
# Create minimal NFO for series without one
|
# Create minimal NFO for series without one
|
||||||
missing_nfo_count += 1
|
missing_nfo_count += 1
|
||||||
asyncio.create_task(
|
repair_tasks.append(
|
||||||
_create_missing_nfo(series_dir, series_name),
|
asyncio.create_task(
|
||||||
name=f"nfo_create:{series_name}",
|
_create_missing_nfo(series_dir, series_name),
|
||||||
|
name=f"nfo_create:{series_name}",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
total += 1
|
total += 1
|
||||||
if nfo_needs_repair(nfo_path):
|
if nfo_needs_repair(nfo_path):
|
||||||
queued += 1
|
queued += 1
|
||||||
asyncio.create_task(
|
repair_tasks.append(
|
||||||
_repair_one_series(series_dir, series_name),
|
asyncio.create_task(
|
||||||
name=f"nfo_repair:{series_name}",
|
_repair_one_series(series_dir, series_name),
|
||||||
|
name=f"nfo_repair:{series_name}",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if repair_tasks:
|
||||||
|
logger.info(
|
||||||
|
"NFO repair scan: waiting for %d repair/create tasks to complete",
|
||||||
|
len(repair_tasks),
|
||||||
|
)
|
||||||
|
await asyncio.gather(*repair_tasks, return_exceptions=True)
|
||||||
|
logger.info("NFO repair scan tasks completed")
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"NFO repair scan complete: %d of %d series queued for repair, %d missing NFOs queued for creation",
|
"NFO repair scan complete: %d of %d series queued for repair, %d missing NFOs queued for creation",
|
||||||
queued,
|
queued,
|
||||||
@@ -182,10 +195,10 @@ class FolderScanService:
|
|||||||
if not self._prerequisites_met():
|
if not self._prerequisites_met():
|
||||||
return
|
return
|
||||||
|
|
||||||
# 1.3 — Repair incomplete NFO files in the background.
|
# 1.3 — Repair incomplete NFO files (synchronous, waits for completion).
|
||||||
logger.info("Starting NFO repair scan as part of folder scan")
|
logger.info("Starting NFO repair scan as part of folder scan")
|
||||||
await perform_nfo_repair_scan(background_loader=None)
|
await perform_nfo_repair_scan(background_loader=None)
|
||||||
logger.info("NFO repair scan queued; repairs will continue in background")
|
logger.info("NFO repair scan complete")
|
||||||
|
|
||||||
# 1.4 — Validate and rename series folders after NFO repair.
|
# 1.4 — Validate and rename series folders after NFO repair.
|
||||||
logger.info("Starting folder rename validation")
|
logger.info("Starting folder rename validation")
|
||||||
|
|||||||
@@ -816,11 +816,14 @@ class TestPerformNfoRepairScan:
|
|||||||
return_value=mock_repair_service,
|
return_value=mock_repair_service,
|
||||||
), patch(
|
), patch(
|
||||||
"asyncio.create_task"
|
"asyncio.create_task"
|
||||||
) as mock_create_task:
|
) as mock_create_task, patch(
|
||||||
|
"asyncio.gather", new_callable=AsyncMock
|
||||||
|
) as mock_gather:
|
||||||
mock_factory_cls.return_value.create.return_value = MagicMock()
|
mock_factory_cls.return_value.create.return_value = MagicMock()
|
||||||
await perform_nfo_repair_scan(background_loader=AsyncMock())
|
await perform_nfo_repair_scan(background_loader=AsyncMock())
|
||||||
|
|
||||||
mock_create_task.assert_called_once()
|
mock_create_task.assert_called_once()
|
||||||
|
mock_gather.assert_called_once()
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_skips_complete_series(self, tmp_path):
|
async def test_skips_complete_series(self, tmp_path):
|
||||||
@@ -876,8 +879,11 @@ class TestPerformNfoRepairScan:
|
|||||||
return_value=mock_repair_service,
|
return_value=mock_repair_service,
|
||||||
), patch(
|
), patch(
|
||||||
"asyncio.create_task"
|
"asyncio.create_task"
|
||||||
) as mock_create_task:
|
) as mock_create_task, patch(
|
||||||
|
"asyncio.gather", new_callable=AsyncMock
|
||||||
|
) as mock_gather:
|
||||||
mock_factory_cls.return_value.create.return_value = MagicMock()
|
mock_factory_cls.return_value.create.return_value = MagicMock()
|
||||||
await perform_nfo_repair_scan(background_loader=None)
|
await perform_nfo_repair_scan(background_loader=None)
|
||||||
|
|
||||||
mock_create_task.assert_called_once()
|
mock_create_task.assert_called_once()
|
||||||
|
mock_gather.assert_called_once()
|
||||||
|
|||||||
Reference in New Issue
Block a user