New JellyfinMediaSourcesController (/api/media-sources/jellyfin, J1-J9) and
EmbyMediaSourcesController (/api/media-sources/emby, E1-E9), wrapping the
existing Jellyfin/Emby MediatR commands per the #202 design doc §A.3/§A.4.
Secure connection contract (§C3/§B, finding 1): the connection GET returns
only { address, hasApiKey } — the API key never crosses the wire. The PUT
retains the existing key when the incoming key is blank, sets a new one when
non-blank, and 422s "API key is required" on a blank first connect.
Finding 7 (lock-release discipline): DisconnectJellyfinHandler and
DisconnectEmbyHandler now wrap their work in try/finally so a throw from any
awaited dependency (repo delete, search-index commit, secret store) still
releases the family lock instead of wedging every future disconnect at 409.
Findings 2c/8 (path-replacement cross-source guard): UpdateJellyfinPathReplacementsHandler
and UpdateEmbyPathReplacementsHandler now reject, before any write, an incoming
positive Id that isn't owned by the route's media source, a null item, or a
blank RemotePath/LocalPath — all 422 with no partial mutation. Defense-in-depth
repo fix: the Jellyfin/Emby path-replacement UPDATE SQL in MediaSourceRepository
now scopes by {Jellyfin,Emby}MediaSourceId (was previously unscoped by Id alone,
allowing a PUT to one source to silently overwrite another source's row). The
Plex path-replacement method (~line 397) is untouched — that's slice S2's file.
Library preferences (§C4a): the controller validates the incoming id set
against the source's known libraries (reject foreign ids, require full
coverage, no Id=0) before dispatch, then — for §C7 — LockLibrary + enqueues
the SynchronizeXLibraries/SynchronizeXLibraryByIdIfNeeded pair per enabled
library (compensating unlock if the enqueue throws), and returns the reloaded
list (ids are not stable across a disable).
404s on id-taking endpoints come from a controller pre-check (GetXMediaSourceById
is None), not a handler NotFoundError, since Either.Apply/ToEitherAsync join any
NotFoundError into a flat 422 (finding 9).
Tests: controller route/404/409/422 tests for both families; disconnect
fault-injection tests proving the lock releases even when a dependency throws;
path-replacement handler tests for cross-source-id/blank/null-item rejection
and correct add/update/delete merge; a repository-level test proving the SQL
fix stops a same-family cross-source path-replacement overwrite.
No new commands, no DB migration, no OpenAPI regen (gated until S1-S3 merge
per the design doc's build-slice plan).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New PlexMediaSourcesController (/api/media-sources/plex) P1-P8 wrapping
existing MediatR commands: state GET, pin-flow, sign-out, per-server
libraries/path-replacements GET+PUT, and refresh — VMs projected to the
S0 shared DTOs, ApiResults mapping, 404 controller pre-checks, #215-style
409 lock guards, [EndpointGroupName("general")].
Lock-lifecycle hardening (the tricky part):
- TryCompletePlexPinFlowHandler now releases the Plex lock ONLY on its
non-handoff exits (timeout-throw, poll exception, enqueue exception, the
dead return-false) via try/catch — NOT an unconditional finally. On
success the lock is handed off to SynchronizePlexMediaSources (the sole
releaser after discovery); a finally would double-release and release
before discovery, re-opening the finding-5 poll race. Fixes the latent
leak where an abandoned pin flow wedged Plex locked until restart.
- StartPlexPinFlow controller compensates UnlockPlex on the Left branch AND
any thrown dispatch/enqueue; only the Right/200 path holds the lock.
- SignOutOfPlexHandler wraps its work in try/finally { UnlockPlex() } — a
terminal handler with no handoff, so unconditional release is correct.
- Post-save library sync enqueues SynchronizePlexLibraryByIdIfNeeded
(Unlock:false) then SynchronizePlexNetworks (Unlock:true) — one lock, one
release on the last message, compensating-unlock if the 2nd enqueue throws
(corrects the Blazor Unlock-ordering bug, finding 6).
Data-integrity hardening:
- UpdatePlexPathReplacementsHandler rejects (422, no mutation) any positive
Id not owned by the route source, blank RemotePath/LocalPath, and null
list/items (findings 2c/8).
- MediaSourceRepository Plex path-replacement UPDATE gains
AND PlexMediaSourceId = @id (Jellyfin/Emby untouched — slice S3).
- ReplaceLibraryPreferences controller validates the id set against the
source's libraries (rejects unowned + Id=0), returns the reloaded list
(ids change on disable).
Tests (NUnit/Shouldly/NSubstitute), 34 new, all green: pin-flow lock
released on thrown-cancellation/poll-throw/enqueue-throw AND held on
success (no double-release); sign-out finally-release under a throwing
dependency; cross-source/nonblank/null path-replacement 422s; library-prefs
id-not-owned 422; post-save enqueue exact messages + Unlock flags; full
controller route/404/409/422 coverage.
Refs #202
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Allow Other Videos Library Type on Plex to be sync
* Migrating database: Creating PlexOtherVideo table
* Using Plex Media path to create tags for OtherVideos
* missed these in the merge
* Getting PlexLibrary for Tag set on OtherVideo
* fix migrations
* set tag metadata on plex other videos
* update changelog
---------
Co-authored-by: Jason Dove <1695733+jasongdove@users.noreply.github.com>
* add deco groups and decos; set default deco for block playout
* use block playout default deco for watermark
* add deco templates, groups and deco template editor
* associate deco template with playout template
* use deco template item watermark for playback
* update changelog for decos
* wip
* start to add jellyfin tables to db
* code cleanup
* finish adding jellyfin media source
* sync jellyfin libraries
* display list of jellyfin libraries
* toggle jellyfin library sync
* edit jellyfin path replacements
* noop jellyfin scanners
* get jellyfin admin user id on startup
* implement jellyfin disconnect
* add jellyfin libraries to list; start to query jellyfin library items
* code cleanup
* start to project jellyfin movies
* save new jellyfin movies to db
* basic jellyfin movie update
* load jellyfin actor artwork
* load jellyfin movie poster and fan art
* more jellyfin artwork fixes, sync audio streams
* jellyfin playback sort of works
* skip jellyfin movies that are inaccessible
* use ffprobe for jellyfin movie statistics
* code cleanup
* store jellyfin operating system
* more jellyfin movie updates
* update jellyfin movie poster and fan art
* add jellyfin tv types
* sync jellyfin shows
* sync jellyfin seasons
* sync jellyfin episodes
* remove missing jellyfin television items
* delete empty jellyfin seasons and shows
* fix jellyfin updates
* fix indexing jellyfin movie and show languages
* add letter bar with no links
* use lucene for search, add paged search results
* add search index version
* index library_name; rebuild index when folder is missing
* maintain index as local movies change
* fix tests
* maintain index as local shows change
* maintain index as plex movies change
* maintain index as plex shows change
* code cleanup
* add duplicate filter to search
* add links to letter bar
* code cleanup
* starting database redesign
* set season and episode numbers
* use datetimes in db (utc); update movie metadata
* get movie cards from new table
* copy show/episode metadata
* remove old movie metadata type
* rename new movie metadata type
* code cleanup
* start to remove old television classes
* remove old television tables from database
* fix playout building
* fix collection views
* fix show/season views
* clean up movie metadata table
* fix scanner tests
* add libraries ui
* code cleanup
* fix movie scanning/metadata
* add library scan button to ui
* delete library path from ui
* temp disable movie scanning
* remove orphan media items and prevent duplicate paths
* attach artwork to metadata
* fix split show/season display
* fix television artwork
* store year distinct from release date
* fix collections ui
* code cleanup
* add library paths from ui
* fix adding to collections from ui
* fix schedule items loading
* schedule editing works again
* remove some todos
* more cleanup
* fix unit tests
* fix episode sorting
* fix deleting show library paths
* remove unused class
* fix playout list in ui
* fix log viewer
* start to use version/file instead of statistics
* clean up old columns
* fix playout display (time zone)
* fix playback
* fix channel guide time zone
* cascade more deletes
* fix compiler warnings
* fix adding new seasons
* use artwork for channel logo
* clean cache folder on startup (move channel logos, delete everything else)
* log database migration
* update homepage docs for libraries
* fix adding new channel with logo
* fix episode numbers in epg