fix(plex): the watch reconcile clears only what it walked, and doubts a mirrored 0
A library left out of the allowlist is never scanned, so its watch states were cleared on every reconcile; clearing is now limited to the sections walked. A section we mirror items from that says it holds 0 is not believed complete. The page size is no longer read as the section total when totalSize is missing (measured: the server always sends totalSize on a paged request, 0 for an empty one).
This commit is contained in:
@@ -79,11 +79,16 @@ def test_a_section_page_asks_for_guids_and_reports_the_server_total(plex):
|
||||
assert params["includeGuids"] == "1" and params["type"] == "1"
|
||||
assert params["X-Plex-Container-Start"] == "200" and params["X-Plex-Container-Size"] == "50"
|
||||
|
||||
plex.routes["/library/sections/7/all"] = {"Metadata": [], "size": 0}
|
||||
# Measured on the real server (E32 review): a paged request carries `totalSize` even when the
|
||||
# page is empty, past the end, or the section holds nothing — `size: 0, totalSize: 0`.
|
||||
plex.routes["/library/sections/7/all"] = {"Metadata": [], "size": 0, "totalSize": 0}
|
||||
assert plex.section_items("7", 2) == ([], 0)
|
||||
# No total at all is not "0": the caller must not read it as a complete, empty section.
|
||||
plex.routes["/library/sections/7/all"] = {"Metadata": []}
|
||||
assert plex.section_items("7", 2) == ([], None)
|
||||
# …and `size` is THIS page's count, never the section's: one full page is not the whole section.
|
||||
plex.routes["/library/sections/7/all"] = {"Metadata": [{"ratingKey": "1"}], "size": 1}
|
||||
assert plex.section_items("7", 2) == ([{"ratingKey": "1"}], None)
|
||||
|
||||
|
||||
def test_related_flattens_the_hubs_and_degrades_to_empty(plex):
|
||||
|
||||
@@ -157,6 +157,51 @@ def test_a_genuinely_empty_second_section_still_lets_the_reconcile_clear(db, mon
|
||||
assert out["cleared"] == 1 and "incomplete" not in out
|
||||
|
||||
|
||||
def test_a_section_we_mirror_that_says_0_is_not_believed(db, monkeypatch, world):
|
||||
# A total of 0 from the very section our items come from is a Plex that lost the library for a
|
||||
# moment, not one that un-watched all of it (E32 review round 2): the shows section below says
|
||||
# 0 and lists nothing — while we hold an episode from it.
|
||||
state, rows = world
|
||||
_local_states(state)
|
||||
shows = PlexLibrary(plex_key="2", title="Shows", kind="show", enabled=True)
|
||||
db.add(shows)
|
||||
db.flush()
|
||||
db.add(PlexItem(rating_key="e1", library_id=shows.id, kind="episode", title="e1"))
|
||||
db.commit()
|
||||
out = _run(db, monkeypatch, _FakePlex(_LISTED, more=[("show", [], 0)]))
|
||||
assert rows()["m1"] == ("watched", 0, True)
|
||||
assert out["cleared"] == 0 and out["incomplete"] == 1
|
||||
|
||||
|
||||
def test_a_library_the_allowlist_leaves_out_keeps_its_watch_states(db, monkeypatch, world):
|
||||
# Its items are absent from every scan because the scan never walks it — not because Plex
|
||||
# un-watched them. It cleared them on every run (E32 review round 2).
|
||||
state, _rows = world
|
||||
other = PlexLibrary(plex_key="2", title="Shows", kind="show", enabled=True)
|
||||
db.add(other)
|
||||
db.flush()
|
||||
ep = PlexItem(rating_key="e1", library_id=other.id, kind="episode", title="e1")
|
||||
db.add(ep)
|
||||
db.flush()
|
||||
user_id = db.execute(select(PlexLink.user_id)).scalar_one()
|
||||
db.add(PlexState(user_id=user_id, item_id=ep.id, status="watched", synced_to_plex=True))
|
||||
_local_states(state)
|
||||
sysconfig.set_value(db, "plex_libraries", "1")
|
||||
db.commit()
|
||||
out = _run(db, monkeypatch, _FakePlex(_LISTED, more=[("show", [], 0)]))
|
||||
db.expire_all()
|
||||
left = {
|
||||
rk
|
||||
for rk in db.execute(
|
||||
select(PlexItem.rating_key).join(PlexState, PlexState.item_id == PlexItem.id)
|
||||
).scalars()
|
||||
}
|
||||
assert "m1" not in left # the walked section still clears
|
||||
kept = db.execute(select(PlexState).where(PlexState.item_id == ep.id)).scalar_one_or_none()
|
||||
assert kept is not None and kept.status == "watched"
|
||||
assert out["cleared"] == 1
|
||||
|
||||
|
||||
def test_a_cut_short_section_is_not_rescued_by_a_complete_one_after_it(db, monkeypatch, world):
|
||||
# Completeness is every section's, not the last one's: movies cut short (5 said, 3 handed),
|
||||
# then a complete shows listing — m1 may simply not have been reached.
|
||||
|
||||
Reference in New Issue
Block a user