recently_viewed.yaml: the no-delete merge covers files outside the window

Without a database version, newer history in the database wins (its
progress is kept; a newer plain view keeps its `viewed` and takes the
config's position).  That applied only to files among the newest views;
a file listed in the config that had fallen out of them was "added",
and its stale entry replaced the newer history.  prepare now reads the
database's history of added files and applies the same rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Roland 2026-10-25 14:23:15 -06:00
parent f11bd450ea
commit 9ea4468cc8
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
2 changed files with 49 additions and 15 deletions

View file

@ -278,28 +278,33 @@ class RecentlyViewedConfig(ConfigFile):
diff.removed = {i for i in diff.removed
if oldest and db_rows[i]['viewed'] >= oldest and i not in malformed}
# Without deletes (no database version), progress written after the config is kept; a newer plain view keeps
# its `viewed`, but it is not a newer place to resume.
if not self.import_deletes:
for key, row in list(diff.changed.items()):
if db_rows[key]['viewed'] >= row['viewed']:
if db_rows[key]['progress'] is not None:
del diff.changed[key]
else:
diff.changed[key] = dict(row, viewed=db_rows[key]['viewed'])
# The files of new entries which are not yet in the database; a deleted file's history goes with it.
# New entries which are in the database (outside the newest views), and which are not.
from wrolpi.files.lib import glob_shared_stem
from wrolpi.files.models import FileGroup
media_directory = get_media_directory()
known = set()
known = dict()
with get_db_session() as session:
for paths in chunks([str(media_directory / i) for i in diff.added], 500):
known.update(str(i) for i, in
session.query(FileGroup.primary_path).filter(FileGroup.primary_path.in_(paths)))
known.update((str(path.relative_to(media_directory)), _viewed_row(*values)) for path, *values in
session.query(FileGroup.primary_path, FileGroup.viewed, FileGroup.progress,
FileGroup.position).filter(FileGroup.primary_path.in_(paths)))
# Without deletes (no database version), progress written after the config is kept; a newer plain view keeps
# its `viewed`, but it is not a newer place to resume. This applies to a file that fell out of the newest
# views too.
if not self.import_deletes:
for rows, current in ((diff.changed, db_rows), (diff.added, known)):
for key, row in list(rows.items()):
if key in current and current[key]['viewed'] and current[key]['viewed'] >= row['viewed']:
if current[key]['progress'] is not None:
del rows[key]
else:
rows[key] = dict(row, viewed=current[key]['viewed'])
# The files of new entries which are not yet in the database; a deleted file's history goes with it.
for key, row in list(diff.added.items()):
absolute_path = media_directory / key
if str(absolute_path) in known:
if key in known:
continue
if absolute_path.is_file():
row['files'] = glob_shared_stem(absolute_path)

View file

@ -156,3 +156,32 @@ async def test_refresh_changing_primary_file_counts(test_session, test_directory
test_session.expire_all()
assert test_session.query(FileGroup).one().primary_path != test_directory / 'video.mp4'
assert get_config_version(test_session, 'recently_viewed.yaml') > 0
@pytest.mark.asyncio
async def test_without_deletes_newer_history_outside_the_window_is_kept(test_session, test_directory, async_client,
make_files_structure, monkeypatch):
"""Without a database version, a newer view in the database wins even when it fell out of the newest views: its
progress is kept, or (a plain view) its `viewed` is kept and the config's position is taken."""
monkeypatch.setattr('wrolpi.files.progress.RECENTLY_VIEWED_LIMIT', 2)
now = dates.now()
file_groups = {name: FileGroup.from_paths(test_session, *make_files_structure([name]))
for name in ('progress.txt', 'plain.txt', 'b.txt', 'c.txt')}
file_groups['progress.txt'].viewed, file_groups['progress.txt'].progress = now - timedelta(hours=3), 0.8
file_groups['plain.txt'].viewed = now - timedelta(hours=3)
file_groups['b.txt'].viewed, file_groups['c.txt'].viewed = now - timedelta(hours=2), now - timedelta(hours=1)
test_session.commit()
config = get_recently_viewed_config()
config.get_file().parent.mkdir(exist_ok=True)
config.get_file().write_text(yaml.dump(dict(version=1, files=[
dict(path='progress.txt', viewed=(now - timedelta(days=1)).isoformat(), progress=0.1),
dict(path='plain.txt', viewed=(now - timedelta(days=1)).isoformat(), progress=0.5,
position=dict(kind='time', seconds=60)),
])))
config.import_config()
found = viewed(test_session)
assert (found['progress.txt'].viewed, found['progress.txt'].progress) == (now - timedelta(hours=3), 0.8)
assert (found['plain.txt'].viewed, found['plain.txt'].progress, found['plain.txt'].position) == \
(now - timedelta(hours=3), 0.5, dict(kind='time', seconds=60.0))