Extends MediaServerReconciliationGuard (#477) with a second deterministic refusal: when the
enumeration that produced the incoming set silently dropped items whose projection THREW, the
file-not-found sweep is refused. A dropped item the server did return is indistinguishable
from a deletion at the reconcile step, so a projection regression could otherwise mass-flag a
healthy library FileNotFound (which EmptyTrash then deletes permanently).
Deliberate guard-clause skips (STRM files, virtual items, unsupported types) are explicitly NOT
failures and never suppress a sweep — counting them would permanently disable reconciliation for
any library holding a single STRM file.
The ratio / missing-fraction threshold is REJECTED, not deferred: it is a two-sided heuristic
with no tunable default and no telemetry, and the failure it approximates is exactly observable
via the projection-failure count (a genuine bulk deletion produces zero failures).
Seam is deliberately narrow — the private ProjectTo* contract inside each api client changed from
Option<T> to MediaServerProjectionResult<T> (projected/skipped/failed), the paged helper counts
IsFailure in one place, and the scanner reads it through an optional trailing
MediaServerProjectionFailureCounter on only the five library-level methods that feed a sweep.
The counter is per-enumeration state created by the scanner, never a field on an api client.
fixes#484
The remove-stale + add-new reconcile idiom materializes its add set with
.ToList() BEFORE the loop mutates the existing collection, so the add filter
(`incoming.All(x2 => x2.Name != x.Name)`) is evaluated against a snapshot. Two
identically-named incoming entries whose name is not yet on the existing item
therefore BOTH passed the filter and BOTH inserted — a duplicate row.
Deduplicate the incoming set on the same key the filter compares (Name; Guid
for Guids), in both copies of the idiom:
- PlexMovieLibraryScanner.UpdateMetadata (the original) — genres, studios,
actors, directors, writers, guids, tags.
- JellyfinMusicVideoLibraryScanner.Reconcile{Genres,Tags,Studios,Artists}
(added in #497, mirrors the Plex pattern verbatim).
Plex ACTORS are the exception and get an artwork-preferring dedup hoisted out
and shared with the remove filter, because that filter is keyed on
(Name, artwork-presence) — it is the mechanism that drops an artwork-less actor
so the add loop can re-add it WITH artwork. A bare DistinctBy(a => a.Name)
there keeps the FIRST duplicate, so Plex listing the artwork-less copy first
discarded the artwork; worse, the remove filter would still see the
artwork-less duplicate, making its upgrade clause false, so the stale row was
never removed and the artwork never arrived on ANY later scan either. Actor
also carries Role/Order, which first-wins would silently drop too. Caught by
the cold review of the first version of this commit.
For the Jellyfin scanner the dedup sits at the incoming-list declaration, which
also covers the remove filter — safe because all four of those filters only ask
"is this name present at all", an answer duplicates cannot change.
Tests: duplicate-collapse for both paths, the two Actors cases above, and a
POSITIVE CONTROL proving distinct entries are still all added and stale ones
still removed (without it, a mis-keyed dedup that collapsed genuinely different
entries would pass every other assertion). Each proven non-vacuous.
The Plex tests drive the protected UpdateMetadata through a minimal test-only
subclass, as MediaServerMovieLibraryScannerTests already does.
Low likelihood in practice (a media server emitting two identically-named
genres for one item is unusual); this is defensive, with no observed occurrence.
The same idiom is copied into 8 further scanners/repositories that this change
deliberately does not touch (the issue scoped it to two paths) — filed as #600
so the class of bug is tracked rather than silently left in the majority of its
instances. The dedup rule is recorded under scan.musicvideo-reconciliation.
fixes#500
* move plex artwork removal to its own repository
* clean up old local movie artwork
* clien up old music video/artist artwork
* clean up old remote stream artwork
* clean up old song artwork
* clean up old show artwork; properly update season artwork
* add external chapter file scanning
Support Matroska chapter xml files next to media file with extension .xml or .chapters
* only update chapters in db
---------
Co-authored-by: Jeff Slutter <MrMustard@gmail.com>
Co-authored-by: Jason Dove <1695733+jasongdove@users.noreply.github.com>
* fix validation in new form layout
* pin mediatr to last oss version
* update dependencies
* cleanup code in core
* cleanup code in ffmpeg
* cleanup code in infra
* cleanup code in scanner
* cleanup code in application
* cleanup main code
* cleanup test code
* solution-wide code cleanup
* fix embedded subtitles from media servers
* fix plex external subtitles
* fix artwork bug, delete orphaned subtitles
* jellyfin subtitles work again
* emby subtitles work
* rescan all media server libraries
* start to stream directly from plex
* update metadata and statistics with one plex api call
* stream movies from plex
* scanning bug fix; update changelog
* start moving local scans to separate process
* send progress updates to main process
* move scanners and tests
* simplify dependencies; sync search index
* commit search index more often when scanning
* support forced scan and cancellation
* use scanner process for plex libraries
* update changelog
* update dockerfiles
* fix search index for local folder scanning
* rework plex scanners
* rework scanner handlers
* emby works again
* sync jellyfin
* cleanup
* update build
* update changelog
* remove scanner dependency in pr and artifacts workflows
* fix mac sed syntax
* fix pr build