files: direct file refresh skips ignored directories

Refreshing a specific file (the Files page Refresh button) bypassed the
ignored_directories check that directory and global refreshes apply.
Refreshing a hard link inside the playlists directory therefore created a
second Video for the same file.

The direct-file path now drops existing files that live in an ignored
directory before comparing against the database.  Deleted paths are kept
so stale records are still removed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Roland 2026-09-14 18:51:06 -06:00
parent d701846254
commit 191f3039bf
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
2 changed files with 69 additions and 1 deletions

View file

@ -1804,3 +1804,65 @@ async def test_refresh_sync_skips_post_processing_when_unchanged(async_client, t
file_worker._apply_post_processing = original
assert called is False, '_apply_post_processing should be skipped when no files changed'
@pytest.mark.asyncio
async def test_direct_file_refresh_skips_ignored_directory(
async_client, test_session, test_directory, make_files_structure, test_wrolpi_config, await_background_tasks
):
"""Refreshing a file directly must not index it when it lives in an ignored directory.
The playlists directory holds hard links of indexed videos. Indexing a link would create a second Video for the
same file.
"""
from wrolpi.common import get_wrolpi_config
get_wrolpi_config().ignored_directories = ['config', 'tags', 'playlists']
await await_background_tasks()
video, link = make_files_structure([
'videos/channel/video.mp4',
'playlists/Testing/0001_video.mp4',
])
task = FileTask(FileTaskType.refresh, [video, link])
file_worker.private_queue.put_nowait(task)
await file_worker.process_queue()
test_session.expire_all()
primary_paths = {str(i.primary_path) for i in test_session.query(FileGroup)}
assert primary_paths == {str(video)}
@pytest.mark.asyncio
async def test_directory_refresh_deletes_stale_ignored_file_groups(
async_client, test_session, test_directory, make_files_structure, test_wrolpi_config, await_background_tasks
):
"""A FileGroup that was indexed before its directory was ignored is removed by the next refresh."""
from wrolpi.common import get_wrolpi_config
get_wrolpi_config().ignored_directories = ['config', 'tags']
await await_background_tasks()
video, link = make_files_structure([
'videos/channel/video.mp4',
'playlists/Testing/0001_video.mp4',
])
task = FileTask(FileTaskType.refresh, [test_directory], count=2)
file_worker.private_queue.put_nowait(task)
await file_worker.process_queue()
test_session.expire_all()
assert test_session.query(FileGroup).count() == 2
# The user (or WROLPi) ignores the playlists directory, the stale record must go away.
get_wrolpi_config().ignored_directories = ['config', 'tags', 'playlists']
await await_background_tasks()
task = FileTask(FileTaskType.refresh, [test_directory], count=1)
file_worker.private_queue.put_nowait(task)
await file_worker.process_queue()
test_session.expire_all()
primary_paths = {str(i.primary_path) for i in test_session.query(FileGroup)}
assert primary_paths == {str(video)}

View file

@ -36,7 +36,7 @@ from wrolpi.files.lib import (
split_path_stem_and_suffix, _upsert_files, get_unique_files_by_stem, glob_shared_stem,
group_files_by_stem, get_primary_file, delete_directory, apply_indexers,
_move_file_group_files, _bulk_update_file_groups_db, MOVE_CHUNK_SIZE,
_bulk_update_file_groups_reorganize, get_normalized_ignored_directories,
_bulk_update_file_groups_reorganize, get_normalized_ignored_directories, remove_files_in_ignored_directories,
)
logger = logger.getChild(__name__)
@ -1849,6 +1849,12 @@ class FileWorker:
else:
all_fs_files.add(file_path)
# Never index files in ignored directories (playlists/tags hold hard links of already-indexed files, which
# would otherwise become duplicate FileGroups). Deleted paths are kept so stale records are still removed.
existing_files = [i for i in all_fs_files if i.exists()]
allowed_files = set(remove_files_in_ignored_directories(existing_files))
all_fs_files = {i for i in all_fs_files if not i.exists() or i in allowed_files}
if not all_fs_files:
return FileComparisonResult(unchanged=[], new=[], deleted=[], modified=[])