fix(plex): a listing cut short no longer clears watch state or Hungarian text
The daily watch reconcile reads "absent from Plex's scan" as "Plex un-watched it" and deletes the row — but it walked the sections without asking _paginate whether the walk reached the server's total, so a Plex hiccup mid-walk (an empty page early) un-watched every title it failed to reach until the next complete run. The scan now reports completeness; an incomplete one still pulls in what it saw, re-pushes dirty rows, and clears nothing (logged as plex.watch.reconcile_incomplete). The HU overlay had the same shape: clear every row's HU text, then refill from the listing. It now reads the listing first and clears only after a complete one. The overlay moved to plex/overlay.py (plex/sync.py drops under the 800-line limit and off the size ratchet's list). Tests: plex/paths (23% -> 100%: prefix boundary, longest prefix, dead mount), the reconcile's clear/keep/re-push rules, the cut-short scan and listing (each seen red with the guard removed).
This commit is contained in:
@@ -16,18 +16,21 @@ from app import sysconfig
|
||||
from app.db import engine
|
||||
from app.models import PlexItem, PlexLibrary, PlexShow
|
||||
from app.plex.genres import hu_genre, hu_genres
|
||||
from app.plex.sync import _apply_item, _canonical_guid, _sync_overlay
|
||||
from app.plex.overlay import sync_overlay
|
||||
from app.plex.sync import _apply_item, _canonical_guid
|
||||
from app.plex.query import content_libs
|
||||
|
||||
|
||||
class _StubPlex:
|
||||
"""Minimal PlexClient stand-in: _sync_overlay only calls section_items via _paginate."""
|
||||
"""Minimal PlexClient stand-in: sync_overlay only calls section_items via _paginate. `total`
|
||||
over the item count is a listing cut short (the server said more than it handed over)."""
|
||||
|
||||
def __init__(self, items: list[dict]):
|
||||
def __init__(self, items: list[dict], total: int | None = None):
|
||||
self._items = items
|
||||
self._total = len(items) if total is None else total
|
||||
|
||||
def section_items(self, key, item_type, start, size):
|
||||
return self._items[start : start + size], len(self._items)
|
||||
return self._items[start : start + size], self._total
|
||||
|
||||
|
||||
def _hu_movie(rk: str, title: str, summary: str, genres: list[str], guid: str | None) -> dict:
|
||||
@@ -71,7 +74,7 @@ def test_sync_overlay_writes_hu_onto_matched_en_rows(db):
|
||||
]
|
||||
)
|
||||
stats = {"overlaid": 0}
|
||||
_sync_overlay(db, stub, hu_lib, en_lib.id, stats)
|
||||
sync_overlay(db, stub, hu_lib, en_lib.id, stats)
|
||||
db.commit()
|
||||
|
||||
db.refresh(matched)
|
||||
@@ -185,13 +188,51 @@ def test_overlay_reconcile_clears_stale_hu_when_twin_vanishes(db):
|
||||
)
|
||||
db.add(row)
|
||||
db.commit()
|
||||
# The HU section no longer lists this film (empty overlay).
|
||||
_sync_overlay(db, _StubPlex([]), hu_lib, en_lib.id, {"overlaid": 0})
|
||||
# The HU section, read in full, no longer lists this film (only another one).
|
||||
other = _hu_movie("h9", "Más", "más", [], "tmdb://99")
|
||||
sync_overlay(db, _StubPlex([other]), hu_lib, en_lib.id, {"overlaid": 0})
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
assert row.title_hu is None and row.summary_hu is None and row.genres_hu is None
|
||||
|
||||
|
||||
def test_a_listing_cut_short_keeps_the_hungarian_text_it_did_not_reach(db):
|
||||
"""E32 S2: the reconcile clears HU text only after a COMPLETE listing. A walk a Plex hiccup cut
|
||||
short (an empty page before the server's total), or an empty section, says nothing about the
|
||||
titles it did not reach — clearing them dropped their Hungarian text until the next full sync."""
|
||||
en_lib = PlexLibrary(plex_key="4", title="Movies", kind="movie")
|
||||
hu_lib = PlexLibrary(plex_key="9", title="Filmek", kind="movie", metadata_lang="hu")
|
||||
db.add_all([en_lib, hu_lib])
|
||||
db.flush()
|
||||
hu_lib.overlay_of = en_lib.id
|
||||
kept = PlexItem(
|
||||
rating_key="m1",
|
||||
library_id=en_lib.id,
|
||||
kind="movie",
|
||||
title="Heat",
|
||||
guid="tmdb:1",
|
||||
title_hu="Szemtől szemben",
|
||||
summary_hu="régi",
|
||||
genres_hu=["Akció"],
|
||||
)
|
||||
fresh = PlexItem(rating_key="m2", library_id=en_lib.id, kind="movie", title="Up", guid="tmdb:2")
|
||||
db.add_all([kept, fresh])
|
||||
db.commit()
|
||||
stats: dict = {}
|
||||
reached = _hu_movie("h2", "Fel", "fel", [], "tmdb://2")
|
||||
sync_overlay(db, _StubPlex([reached], total=2), hu_lib, en_lib.id, stats)
|
||||
db.commit()
|
||||
db.refresh(kept)
|
||||
db.refresh(fresh)
|
||||
assert kept.title_hu == "Szemtől szemben" and kept.summary_hu == "régi"
|
||||
assert fresh.title_hu == "Fel" # what the listing did reach is still applied
|
||||
assert stats["overlay_incomplete"] == 1
|
||||
sync_overlay(db, _StubPlex([]), hu_lib, en_lib.id, {}) # an empty section: the same answer
|
||||
db.commit()
|
||||
db.refresh(kept)
|
||||
assert kept.title_hu == "Szemtől szemben"
|
||||
|
||||
|
||||
def test_apply_item_keeps_prior_guid_when_listing_lacks_guid():
|
||||
row = PlexItem(rating_key="m1", kind="movie", guid="tmdb:1")
|
||||
_apply_item(row, lib_id=7, kind="movie", meta={"title": "Heat"}) # no Guid array
|
||||
@@ -225,7 +266,7 @@ def test_overlay_applies_to_all_editions_sharing_a_guid(db):
|
||||
]
|
||||
)
|
||||
stats = {"overlaid": 0}
|
||||
_sync_overlay(db, stub, hu_lib, en_lib.id, stats)
|
||||
sync_overlay(db, stub, hu_lib, en_lib.id, stats)
|
||||
db.commit()
|
||||
db.refresh(ed1)
|
||||
db.refresh(ed2)
|
||||
@@ -291,7 +332,7 @@ def test_a_show_overlay_writes_hungarian_episode_titles_and_summaries(db):
|
||||
],
|
||||
}
|
||||
)
|
||||
_sync_overlay(db, stub, hu_lib, en_lib.id, {})
|
||||
sync_overlay(db, stub, hu_lib, en_lib.id, {})
|
||||
db.commit()
|
||||
db.refresh(ep)
|
||||
db.refresh(show)
|
||||
@@ -328,7 +369,7 @@ def test_the_overlay_gives_every_row_one_hungarian_word_per_genre(db):
|
||||
"h1", "Szemtől szemben", "HU.", ["Action", "Bűnügyi", "Drama", "Dráma"], "tmdb://1"
|
||||
)
|
||||
meta["tagline"] = "Magyar mottó."
|
||||
_sync_overlay(db, _StubPlex([meta]), hu_lib, en_lib.id, {})
|
||||
sync_overlay(db, _StubPlex([meta]), hu_lib, en_lib.id, {})
|
||||
db.commit()
|
||||
db.refresh(twin)
|
||||
db.refresh(lone)
|
||||
@@ -353,7 +394,7 @@ def test_a_twin_without_genres_keeps_the_rows_own_hungarian_list(db):
|
||||
)
|
||||
db.add(row)
|
||||
db.commit()
|
||||
_sync_overlay(
|
||||
sync_overlay(
|
||||
db,
|
||||
_StubPlex([_hu_movie("h1", "Szemtől szemben", "HU.", [], "tmdb://1")]),
|
||||
hu_lib,
|
||||
|
||||
Reference in New Issue
Block a user