Commit graph

1094 commits

Author SHA1 Message Date
SergeantPanda
39865d3732 Merge branch 'dev' of https://github.com/Dispatcharr/Dispatcharr into pr/CodeBormen/943 2026-03-08 18:04:11 -05:00
SergeantPanda
70a0b43c8a Cleanup frontend and backend if valid item ids change. 2026-03-08 17:05:26 -05:00
SergeantPanda
63234a4a46 Bug Fix: Restore integrations/system groups and fix channels reference after rebase 2026-03-08 16:35:34 -05:00
SergeantPanda
d5aaa0277c security: Do not allow user to give themselves admin or super_user privileges. 2026-03-08 16:30:48 -05:00
SergeantPanda
a4ad9a9135 Move python test into tests folder. Fix frontend test after merge changes. 2026-03-08 16:30:47 -05:00
SergeantPanda
cde39c0e13 Fixed logic with default orders. 2026-03-08 16:30:42 -05:00
Jeff Casimir
8fb88827ae Fix code review findings for navigation ordering PR
P1 (Critical):
- Replace undefined @swagger_auto_schema with @extend_schema
- Add custom_properties validation with size limits
- Add merge semantics in serializer update() to prevent data loss

P2 (Important):
- Add debouncing (800ms) for drag operations in NavOrderForm
- Add error handling with notifications for visibility toggle
- Use store getters in Sidebar instead of direct property access
- Cache hiddenNav before render loop

P3 (Nice-to-have):
- Add canHide: false to settings nav item
- Fix admin check to use >= instead of ==
- Fix ESLint unused variable warnings

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-08 16:28:29 -05:00
Jeff Casimir
edf81ef6c6 Add PATCH method to /me/ endpoint for user preferences
- Allow users to update their own profile via PATCH /api/accounts/users/me/
- Supports partial updates to custom_properties (including navOrder)
- Add comprehensive test coverage for the endpoint

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-08 16:25:47 -05:00
SergeantPanda
ea9f4f4bbd Merge branch 'dev' of https://github.com/Dispatcharr/Dispatcharr into pr/JCBird1012/1062 2026-03-08 12:00:46 -05:00
SergeantPanda
dfb91db987 Bug Fix: VOD orphan cleanup crashing with a ForeignKeyViolation (IntegrityError) when a concurrent refresh task created a new M3UMovieRelation or M3USeriesRelation for a movie/series between the orphan-detection query and the DELETE SQL. Both orphaned_movies.delete() and orphaned_series.delete() are now wrapped in try/except IntegrityError; affected records are skipped with a warning and will be cleaned up on the next scheduled run. 2026-03-08 11:56:52 -05:00
SergeantPanda
753bc1a893 fix(stream-preview): restore stream_name and stream_stats for stream previews
Stream.get_stream() was never called in the preview path of
generate_stream_url(), so the channel_stream and stream_profile
Redis keys were never written. This caused the stream_id to be
missing from channel metadata, which silently broke two things:

- Stats page showed no stream name or logo
- stream_stats were never saved to the database

Fix: replace the manual profile-selection loop in the preview path
with a direct call to Stream.get_stream(), matching the channel path
exactly. This also fixes a pre-existing non-atomic slot reservation
(read-then-check vs INCR-first) that could over-allocate connections
on concurrent previews.

Regression introduced in 49b7b9e2.
2026-03-08 11:46:31 -05:00
SergeantPanda
9876a25e62 Bug Fix: XC stream refresh crashing with a null value in column "name" database error when a provider returns streams with a null or empty name. Affected streams are now assigned a generated fallback name in the format <account name> - <stream_id> so the refresh completes successfully and the stream remains accessible. A warning is logged for each affected stream. 2026-03-07 18:33:17 -06:00
SergeantPanda
3d27161755 Merge branch 'dev' of https://github.com/Dispatcharr/Dispatcharr into pr/nickgerrer/1022 2026-03-06 17:22:44 -06:00
SergeantPanda
2373044adb Merge branch 'dev' of https://github.com/Dispatcharr/Dispatcharr into pr/CodeBormen/1052 2026-03-06 16:12:52 -06:00
Jonathan Caicedo
a51eb37075 fix: M3U EXTINF parsing for attributes ending in double equals
fixes: #1055
2026-03-06 08:10:30 -05:00
SergeantPanda
bf87c44a72 Merge branch 'dev' of https://github.com/Dispatcharr/Dispatcharr into pr/CodeBormen/1063 2026-03-05 16:48:34 -06:00
SergeantPanda
78aa8cf353 Enhancement: Change default new client behind seconds from 2 to 5 for stability. 2026-03-05 15:36:12 -06:00
SergeantPanda
37fa6aac20
Merge pull request #1023 from CodeBormen/fix/940-Duplicate-Recordings-With-Corruption
fix(dvr): fix duplicate recording execution and add manual stop
2026-03-05 14:11:41 -06:00
None
ce9f7ccbf9 fix(m3u): fix Celery worker memory leak during M3U/XC refresh
Restructure cleanup in refresh_single_m3u_account so del statements
run before gc.collect(), enabling cyclic garbage collection. Add worker_max_memory_per_child as a safety net against memory fragmentation.

Closes #1012, Closes #1053
2026-03-05 13:47:20 -06:00
Jonathan Caicedo
c6cf9443d2 fix: guard is_adult parsing against invalid values
or whatever other fun things providers might shove down the wire...
2026-03-05 14:16:21 -05:00
SergeantPanda
c02e060c28 Enhancement: EPG output when no days parameter is specified now excludes already-ended programs instead of returning all historical data. 2026-03-05 12:31:36 -06:00
SergeantPanda
f9d4f11cb2 Bug Fix: update EPG data filtering to use end_time instead of start_time 2026-03-05 12:25:38 -06:00
SergeantPanda
9805732582 Merge branch 'dev' of https://github.com/Dispatcharr/Dispatcharr into pr/CodeBormen/1023 2026-03-05 11:16:20 -06:00
SergeantPanda
06f864ad6d fix(stream_generator): optimize resource and health checks with throttling 2026-03-04 17:37:42 -06:00
None
68de1b0723 fix(dvr): add DVR search/filters, fix in-progress scheduling, prevent duplicate recordings
- Add search, channel, and status filter controls to DVR page (matching Guide/VODs pattern)
- Fix post_save signal to schedule recordings for currently-playing programs immediately instead of skipping them
- Remove duplicate evaluate_series_rules.delay() call from series rule creation endpoint to prevent race condition that created two recordings for the same program
2026-03-04 10:21:03 -06:00
None
9d08305dcb fix(dvr): fix recording modal crash, harden recovery pipeline, improve tests
Frontend:
- RecordingDetailsModal: fix ReferenceError crash (editing state used before declaration in useMemo — TDZ in production bundles broke all tile clicks)
- RecordingDetailsModal: fix focus-stealing on description edit by calling Movie()/Series() as plain functions instead of <Movie />/<Series />(inline component refs change every render, causing unmount/remount)
- RecordingDetailsModal: suppress sub_title in modal title after custom save
- ErrorBoundary: log caught errors and show message for easier debugging
- guideUtils: filter terminal recordings from mapRecordingsByProgramId so Guide red dots clear after recording finishes or rule is deleted
- RecordingDetailsModalUtils.test: add missing end_time to test fixtures (filterByUpcoming now uses end_time instead of start_time)

Backend:
- tasks.py: output file collision avoidance (check .mkv/.ts before writing)
- tasks.py: pre-restart TS segment concatenation (direct concat→MKV remux)
- tasks.py: recovery pipeline — recover crashed "recording" status, remux expired recordings, revoke stale tasks, deterministic task IDs, early lock release, 10-min lock TTL for large remux operations
- tasks.py: poster resolver skips external API when title matches channel name
- celery.py: trigger recover_recordings_on_startup via worker_ready signal

Tests:
- Renamed test_dvr_fixes.py → test_recording_pipeline.py (clarity)
- Renamed test_dvr_retry.py → test_db_retry.py (clarity), fixed mock paths for close_old_connections (resolved 3 test failures), replaced synthetic test
- test_recording_stop_cancel: replaced brittle time.sleep(0.5) with deterministic thread-capture pattern
2026-03-04 10:21:03 -06:00
None
bd14e48f8b fix(dvr): harden recording pipeline — idempotency guard, remux sanity checks, timezone fix
- Idempotency guard: fail closed on DB errors instead of proceeding, preventing duplicate tasks from overwriting valid recordings (#641)
- Remux sanity checks: reject MKV output that is <50% of a previous MKV (duplicate-task overwrite) or <10% of the source TS (corrupt first attempt); preserve .ts for manual recovery on all failure paths (#619)
- Recurring rule timezone fix: use user's configured timezone for date window calculation instead of UTC, fixing silent recording loss for users in UTC-negative timezones after 4pm local time (#1042)
- Include .ts path in final remux failure log for easier recovery
2026-03-04 10:21:03 -06:00
None
759688390b fix(dvr): fix series episode list, card layout, rule cleanup, and recording resilience
Backend:
- Add terminal status guard to stop() endpoint -prevents overwriting completed/interrupted/failed with "stopped" (returns 409 Conflict)
- Include currently-airing programs in series rule evaluation by filtering on end_time instead of start_time
- Clean up future recordings when deleting a series rule while preserving previously recorded episodes
- Add logo proxy negative cache to prevent repeated requests to unreachable hosts from blocking Daphne workers
- Add URL validation with HEAD-then-GET fallback and per-worker cache
- Add EPG time-slot matching (80% overlap threshold) to enrich manual recordings with program metadata
- Add multi-source poster resolution pipeline (EPG, VOD, TMDB, OMDb, TVMaze, iTunes) with URL validation at each stage
- Add editable recording metadata endpoint with user_edited flag to prevent EPG auto-overwrite
- Add refresh-artwork endpoint to re-run poster resolution on demand
- Batch-fetch PeriodicTask names in schedule_upcoming to avoid N+1

Frontend:
- Fix series modal showing only one episode — preserve _group_count from categorized prop when merging with store version so the episode list renders correctly
- Fix recording card button positioning — use flex column layout with marginTop auto so buttons align at the bottom regardless of content
- Add delete fallback in RecurringRuleModal for orphaned recordings from deleted rules
- Use end_time filter in upcoming episodes list to include currently-airing episodes
- Add inline metadata editing (title/description) in details modal
- Add refresh artwork button in details modal
- Replace hardcoded /logo.png fallbacks with imported default logo
- Add React.memo with custom comparator to RecordingCard

Tests:
- Add 21 EPG time-slot matching tests (overlap threshold, edge cases)
- Add 16 recording metadata tests (validation, artwork, logo cache)
- Add 17 URL validation tests (HEAD/GET fallback, caching, eviction)
2026-03-04 10:21:03 -06:00
None
cf3165ea88 fix(dvr): add DB retry resilience, fix recording card logos, prevent incorrect artwork
Add exponential backoff retry (_db_retry helper) for transient DB errors during final metadata save, startup recovery, and initial TS proxy
connection. Add FFmpeg remux retry for transient I/O errors. Retry same proxy base before falling back to next candidate on initial connection failure.

Fix DVR card logos not displaying: channel summary API returns logo_id (integer) but frontend expected a nested logo object with cache_url.

Add getChannelLogoUrl() helper that handles both data shapes. Backend artwork prefetch and run_recording now fall back to the channel's own logo when no show-specific artwork is found, ensuring poster_logo_id is always populated.

Prevent incorrect artwork by skipping external API searches (TVMaze, iTunes) when no program title is available - channel names like
"USA A&E SD*" returned unrelated results from fuzzy matching.

Add 13 tests for retry logic (test_dvr_retry.py)
2026-03-04 10:21:03 -06:00
None
e5206545dd fix(dvr): add stream reconnection resilience, fix comskip exit code handling
- Add reconnection loop to DVR streaming: on transient connectivity loss (ReadTimeout, ConnectionError, ChunkedEncodingError), the recording task retries the same TS proxy base up to 5 times with a 2-second delay, appending to the existing file. Counter resets on successful data receipt. TS container format tolerates brief discontinuities; MKV remux normalises timestamps.
- Extract _check_recording_cancelled() helper to consolidate repeated stop/delete DB checks across the streaming and reconnection paths.
- Fix comskip treating exit code 1 (no commercials detected) as a fatal error.
  Use check=False and handle return codes explicitly: 0 = commercials found,
  1 = no commercials (skipped), negative = signal, other = real error.
- Add human-readable reason messages for comskip 'skipped' notifications in
  WebSocket handler.
- Remove redundant conditional Channel import in stream_generator._cleanup() that shadowed the module-level import, causing UnboundLocalError when the conditional branch did not execute.
2026-03-04 10:21:02 -06:00
None
f28530a47b fix(dvr): add recording extend, fix cross-recording interference, artwork race, and keepalive gaps
File changes:

Backend

apps/channels/api_views.py — Added extend() action that uses queryset .update() to bypass signals, letting the running task pick up the new end_time via polling. Moved WS recording_stopped notification to synchronous (before HTTP response). Refactored destroy() to delete DB row first, send WS event immediately, defer cleanup to background thread.

apps/channels/signals.py — Added pre_save guard to skip task revocation when status is "recording". Added post_save reentrancy guard to skip artwork prefetch for internal field-only saves (custom_properties, task_id, end_time).

apps/channels/tasks.py — Added 2-second polling loop in streaming that checks for stop, deletion, and extended end_time. Added refresh_from_db() + merge before metadata save to prevent overwriting concurrent artwork prefetch. Replaced inline PeriodicTask cleanup with revoke_task()/_dvr_task_name(). Consolidated redundant DB queries in error path. Fixed TOCTOU os.path.exists + os.remove. Preserved "stopped" status in final-status logic instead of overwriting with "completed".

core/utils.py — Made send_websocket_update gevent-safe by detecting monkey-patching and offloading async_to_sync to gevent's native threadpool, fixing the event loop deadlock that caused cross-recording interference.

apps/proxy/ts_proxy/client_manager.py — Moved _trigger_stats_update to a background thread so remove_client() returns immediately.

apps/proxy/ts_proxy/stream_generator.py — Added Redis-based health check for non-owner workers in _should_send_keepalive(), so keepalives fire correctly even when stream_manager is None.

Frontend

frontend/src/WebSocket.jsx — Added 400ms debounced scheduleRecordingFetch() replacing all inline fetchRecordings() calls. Added recording_extended and recording_updated event handlers. recording_cancelled now does surgical removeRecording() by ID when available.

frontend/src/api.js — Added extendRecording(id, extraMinutes).

frontend/src/components/cards/RecordingCard.jsx — Added Extend menu (+15m/+30m/+1h) for in-progress recordings. Widened Comskip button to stopped/interrupted statuses.

frontend/src/components/forms/ProgramRecordingModal.jsx — Removed manual fetchRecordings() calls, relying on WS-driven debounced refresh.

frontend/src/components/forms/RecordingDetailsModal.jsx — Added stopped to playable statuses. Widened Comskip button. Removed manual fetchRecordings().

frontend/src/components/forms/RecurringRuleModal.jsx — Removed all manual fetchRecordings() calls, relying on WS events.

frontend/src/pages/Guide.jsx — Removed isLoading subscription that caused loading flash on debounced refresh. Removed manual fetchRecordings() after saving a series rule.

frontend/src/store/channels.jsx — Stripped isLoading/error state from fetchRecordings to prevent full-page loading indicators. Added no-op guards in removeRecording.

frontend/src/utils/cards/RecordingCardUtils.js — Added extendRecordingById wrapper.

Tests

apps/channels/tests/test_recording_extend.py (new) — 14 tests: extend endpoint validation, stacked extensions, signal bypass, pre_save guard behavior.

apps/channels/tests/test_recording_stop_cancel.py (new) — 18 tests: stop endpoint, DVR client teardown, cancel was_in_progress flag, run_recording race guards, signal reentrancy guards.

apps/channels/tests/test_ts_proxy_keepalive.py (new) — 21 tests: keepalive owner/non-owner logic, stats update error handling, client removal non-blocking, timing invariants.

apps/channels/tests/test_recording_scheduling.py — Updated mock assertion to match new _stop_dvr_clients call signature.

Summary:

Adds an Extend action for in-progress recordings that bypasses Django signals and lets the running task dynamically pick up the new end_time via its 2-second DB polling loop. Fixes cross-recording interference caused by send_websocket_update deadlocking the gevent event loop. Fixes an artwork race condition where run_recording overwrote concurrent prefetch data. Adds Redis-based keepalive health checks for non-owner workers to prevent DVR read timeouts during source transitions. Replaces manual fetchRecordings() calls throughout the frontend with a debounced WS-driven refresh. 53 new tests.
2026-03-04 10:21:02 -06:00
None
bd1e060bd2 fix(dvr): fix duplicate recording execution and add manual stop
Fixes #940: Duplicate DVR task executions were caused by ClockedSchedule objects being shared across PeriodicTasks, triggering run_recording multiple
times for the same recording. Added an idempotency guard in run_recording that exits early if status is already 'recording', 'completed', or 'stopped'.

revoke_task() now deletes the PeriodicTask and orphaned ClockedSchedule on execution rather than relying on Celery revocation alone.

Fixes #454: Added POST /api/channels/recordings/{id}/stop/ action that marks the recording as stopped and terminates only the specific DVR proxy client (identified via User-Agent: Dispatcharr-DVR/recording-{id}) without disrupting
simultaneous recordings on the same or other channels. The API returns
immediately; DVR client teardown and task revocation happen in a background thread to avoid 504 timeouts on the recordings list endpoint.

Additional changes:
- destroy() now only calls _stop_dvr_clients() for in-progress recordings, preventing accidental stream termination when deleting completed recordings
- WebSocket events differentiated: recording_stopped, recording_cancelled (in-progress cancel), and recording_cancelled (delete) with was_in_progress flag so the frontend shows distinct "Recording deleted" vs "Recording cancelled" notifications

- Frontend: Stop and Cancel split into separate buttons with confirmation
  modals; stopped recordings move immediately to Previously Recorded via
  optimistic status bucketing; removed dead 'Stopped' badge state

- 27 new unit tests covering scheduling, idempotency, revocation, and DVR client isolation
2026-03-04 10:21:02 -06:00
SergeantPanda
fc75a68195 Enhancement: new clients joining an active channel are now positioned a configurable number of seconds behind live rather than a fixed chunk count. The start position is determined by wall-clock chunk receive time (stored as a Redis sorted set alongside the buffer), so the buffer depth is consistent in seconds regardless of stream bitrate. Setting the value to 0 starts clients at live with no buffer. Defaults to 2 seconds. Existing chunk-count gating for the first client connecting to a channel is unchanged. The setting is exposed in Settings → Proxy as "New Client Buffer (seconds)". 2026-03-03 18:12:32 -06:00
None
6ff81e6287 Fix modular mode deployment issues (#1045)
- Fix Postgres version check failing with restricted DB users (use $POSTGRES_DB instead of hardcoded 'postgres')
- Fix DVR recording broken in modular mode (respect DISPATCHARR_PORT instead of hardcoding 9191)
- Remove flushdb() from wait_for_redis.py to prevent Redis data loss on container restart
- Add DISPATCHARR_PORT to celery environment in docker-compose.yml
- Add depends_on health conditions for proper service startup ordering
- Add extra_hosts for host.docker.internal resolution on Linux
- Harden celery entrypoint with timeouts for JWT wait (120s) and migration wait (300s)
- Replace fragile showmigrations grep with migrate --check
- Add unit tests for DVR port resolution and flushdb removal regression
2026-03-03 17:18:16 -06:00
SergeantPanda
d70450b7fc Merge branch 'dev' of https://github.com/Dispatcharr/Dispatcharr into pr/CodeBormen/949 2026-03-03 16:16:25 -06:00
SergeantPanda
e78c24aad7 Enhance Redis chunk management in StreamBuffer and StreamGenerator
- Register Lua scripts for optimized chunk retrieval in StreamBuffer.
- Implement atomic Lua binary search to find the oldest available chunk in Redis.
- Update StreamGenerator to handle expired chunks more efficiently, reducing TOCTOU race conditions.
2026-03-03 15:13:46 -06:00
None
7653859a9a Reset connection_allocated flag after redirect stream release
The redirect path in stream_ts() released the connection slot via release_stream() but did not reset the connection_allocated flag. Could have could caused a redundant no-op call in the exception handler.
2026-03-03 14:22:45 -06:00
None
c96603f923 Merge remote-tracking branch 'upstream/dev' into fix/947-Connection-capacity-leak-failed-stream-initialization-permanently-consumes-connection-slot 2026-03-03 14:02:52 -06:00
SergeantPanda
cd9d8d5d2a Merge branch 'dev' of https://github.com/Dispatcharr/Dispatcharr into pr/PFalko/1035 2026-03-03 11:07:10 -06:00
SergeantPanda
6d7130d71e Bug Fix: fix get_instance deadlock and non-atomic ownership acquisition
If ProxyServer() raises during singleton construction, _instance was left
as _INITIALIZING permanently, causing all subsequent get_instance() callers
to spin in an infinite gevent.sleep() loop. Wrap construction in try/except
and reset _instance to None on failure so callers can retry.

Also replace the non-atomic setnx() + expire() pair in try_acquire_ownership()
with a single atomic SET NX EX call, consistent with the approach already used
in extend_ownership() and eliminating the race window where a crash between the
two calls could leave a key with no TTL (permanent ownership lock).
2026-03-03 11:06:52 -06:00
SergeantPanda
c005a48162 Merge branch 'dev' of https://github.com/Dispatcharr/Dispatcharr into pr/JCBird1012/1027 2026-03-03 09:15:31 -06:00
SergeantPanda
b098deae76 Got rid of extra api call to query if any channels had no EPG assigned. Moved query to an EXISTS lookup during channel query. 2026-03-02 17:20:55 -06:00
Claude Code
7fed49a334 Address review feedback from CodeBormen
- Use atomic SET NX EX instead of separate SETNX + EXPIRE to prevent
  zombie locks if the process crashes between the two calls
- Replace time.sleep() with gevent.sleep() in get_instance() spin-wait
  to avoid blocking the greenlet hub
- Defer cleanup when ownership is lost but clients are still connected,
  giving the new owner time to establish its stream before we tear down

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 13:23:45 +00:00
SergeantPanda
565d335403 Bug Fix: The EPG filter dropdown was showing all EPG sources regardless of whether they had any channels assigned, and the "No EPG" option was never displayed. Fixed by annotating EPGSource records with a has_channels flag (via a lightweight EXISTS subquery) so only EPG sources actually in use appear as filter options. "No EPG" now appears only when at least one channel has no EPG assigned, determined via a page_size=1 query against the existing channel filter endpoint.
Some checks are pending
CI Pipeline / prepare (push) Waiting to run
CI Pipeline / docker (amd64, ubuntu-24.04) (push) Blocked by required conditions
CI Pipeline / docker (arm64, ubuntu-24.04-arm) (push) Blocked by required conditions
CI Pipeline / create-manifest (push) Blocked by required conditions
Build and Push Multi-Arch Docker Image / build-and-push (push) Waiting to run
Frontend Tests / test (push) Waiting to run
2026-03-01 16:54:05 -06:00
Jonathan Caicedo
a318d919d8 feat: filter channels that contain stale streams
closes: #1025
2026-02-28 12:30:21 -05:00
None
0e7eb916c2 Merge remote-tracking branch 'upstream/dev' into fix/947-Connection-capacity-leak-failed-stream-initialization-permanently-consumes-connection-slot 2026-02-27 20:59:30 -06:00
PFalko
8bd38ad71c Fix stream ownership bugs causing streams to die after 30-200s
Three interrelated bugs cause TS proxy streams to terminate prematurely
in multi-worker uWSGI deployments:

1. Double ProxyServer instantiation: ProxyConfig.ready() in apps/proxy/apps.py
   calls TSProxyServer() directly, bypassing get_instance(). The subsequent
   TSProxyConfig.ready() call to get_instance() creates a second instance.
   Each instance starts its own cleanup thread, but only one holds channel
   data — the orphaned cleanup thread cannot extend ownership.

2. Redis flushdb() on every client init: RedisClient.get_client() in
   core/utils.py calls flushdb() whenever a new connection is created.
   Celery autoscale workers spawning mid-stream nuke all Redis keys
   including ownership, client records, and channel metadata.

3. No recovery from expired ownership: get_channel_owner() has a TOCTOU
   bug (two separate GET calls in a lambda). extend_ownership() silently
   fails when keys expire. Non-owner cleanup unconditionally kills streams
   even when the worker holds the stream_manager.

Fixes:
- Use TSProxyServer.get_instance() in ProxyConfig.ready()
- Remove flushdb() from Redis client initialization
- Use sentinel pattern for gevent-safe singleton (threading.Lock does not
  work with gevent greenlets)
- Single GET in get_channel_owner() to avoid TOCTOU race
- Re-acquire expired ownership keys in extend_ownership()
- Attempt re-acquisition before cleanup in non-owner path

Relates to #992, #980
2026-02-27 16:40:28 +01:00
SergeantPanda
abba65816b Migrations: Add missing migrations. 2026-02-26 13:25:51 -06:00
SergeantPanda
216575a411
Merge pull request #1020 from Dispatcharr/enhanced-webhooks
enhanced webhooks to support custom headers and payload templates
2026-02-26 13:14:24 -06:00
SergeantPanda
60c165f422 Enhancement: Stats page "Now Playing" EPG lookup updated to use channel_uuids directly (the proxy stats already key active channels by UUID), removing the need for a UUID→integer ID conversion step introduced alongside the lazy channel-fetch refactor. Stream preview sessions (which use a content hash rather than a UUID as their channel ID) are now filtered out before any API call is made, preventing a backend ValidationError on both the current-programs and by-uuids endpoints when a stream preview is active on the Stats page. 2026-02-26 12:00:29 -06:00