This update introduces a new helper function `_is_full_restart_range` to determine when a request indicates a full restart from byte 0. The existing `_should_preempt_plain_reconnect` function has been modified to utilize this new logic, ensuring that plain GET requests and open-ended byte ranges are handled correctly. Additionally, new tests have been added to validate the behavior of these functions, confirming that they correctly identify restart scenarios and preempt playback as expected.
This update refactors the logic for building timeshift statistics by removing unnecessary `select_related` calls and the `logo_url` field from the connections. The changes streamline the data retrieval process and ensure that only the `logo_id` is exposed in the session data. Corresponding tests have been updated to reflect these modifications.
This update modifies the timestamp parsing logic to accommodate various formats, including those using both dash and colon as separators for hours, minutes, and seconds. The changes ensure that timestamps like "HH-MM-SS" and mixed formats such as "HH-MM:SS" are correctly parsed and normalized. Additional tests have been added to validate these new parsing capabilities.
This update ensures that the Redis state for live channels is correctly cleared when a buffering-timeout failover occurs. Previously, the in-memory buffering flag could be cleared without updating Redis, leading to incorrect state reporting. The fix modifies the logic to clear the Redis state to 'active' when the buffering flag is reset, preventing stale 'buffering' states from persisting after recovery. Tests have been updated to validate this behavior.
This change ensures that plugin discovery does not force-reload on every connect event in multi-worker environments. By allowing local reloads in response to a newer `.reload_token`, the update prevents stale token issues that previously led to degraded worker performance. The explicit `force_reload=True` option is retained for installations, updates, or reloads, ensuring better resource management and stability. Tests have been updated to reflect this behavior.
This change modifies the "Copy URL" action in the Channels table to generate a plain proxy URL without including web player output profile parameters. The update ensures that copied links are suitable for external players, enhancing usability. Additionally, tests have been updated to verify the new behavior of the "Copy URL" feature.
This update introduces atomic mutations for active_streams using Redis Lua scripts, ensuring that concurrent byte-range requests do not leave stale session metadata. The changes also include improvements to the session state saving mechanism, preventing the recreation of zombie sessions after idle cleanup. Additionally, tests have been added to cover the new atomic behavior and ensure reliability across various scenarios.
- Enhanced the `formatApiError` function to better format failed API responses, preventing raw HTML error pages from being displayed in toasts. JSON error bodies are now prioritized for user-friendly messages, while HTML and empty bodies are collapsed to a concise status line. Long plain-text bodies are truncated for readability.
- Updated tests to cover new error formatting behavior, ensuring accurate extraction of error messages from various JSON structures.
- Updated the catch-up stats UI to advance to the next EPG programme when playback continues past the original programme's end, improving user experience.
- Enhanced the `get_programme_info` function to resolve the guide entry based on the playhead position, ensuring accurate display of the current programme.
- Introduced new utility functions for computing catch-up playback position and checking if the playhead is outside the displayed programme window, optimizing session management.
- Added tests to validate the new functionality and ensure correct behaviour of the catch-up features.
- Introduced a new endpoint `POST /api/catchup/sessions/<session_id>/position/` for native clients to report their playhead position and pause state during catch-up sessions.
- Updated the catch-up session handling to include a `paused` flag, allowing accurate tracking of playback state without seeking the provider stream.
- Enhanced the Redis storage mechanism to accommodate the new position and pause data, ensuring real-time updates for admin stats.
- Added tests to validate the new position reporting functionality and its integration with existing catch-up features.
- Introduced an optional `duration` parameter for catch-up sessions, allowing clients to specify the programme length in minutes. This value is preferred over EPG-derived durations and includes a buffer for provider lag.
- Updated the API views and serializers to accept and process the new `duration` field.
- Enhanced the catch-up proxy implementation to utilize the client-supplied duration, improving playback accuracy.
- Added tests to validate the new duration handling and ensure proper integration with existing functionality.
- Implemented a new `LockedSpectacularAPIView` to handle schema generation, ensuring it no longer fails due to concurrent gevent requests. The schema builds are now single-flight per process and cached in Django's cache, allowing all workers to share the result.
- Updated the `urls.py` to use the new view for schema generation, enhancing stability and performance.
- Improved multi-provider failover by preferring streams with sufficient catch-up days.
- Added new helper functions for calculating programme age and ordering catch-up streams.
- Removed deprecated XMLTV settings from the frontend and backend.
- Updated tests to validate new stream ordering logic and catch-up functionality.
Updated the catch-up timestamp normalization to reflect XC client specifications. Introduced a new mechanism to handle near-EOF duration probes, ensuring that playback statistics are preserved without reanchoring to the end of the file. Additionally, improved the handling of presentation lengths in the timeshift proxy to align with XC behavior. Updated tests to validate these changes and ensure robust playback functionality.
DB sessions were never actually pinned to UTC. Three stacked failures:
1. The connection_created receiver (_force_utc0 in core/apps.py) was a
nested closure connected with Django's default weak reference. It was
garbage-collected as soon as CoreConfig.ready() returned, so
SET TIME ZONE 'UTC0' fired into a dead weakref and never executed -
in any version since it landed (verified: the signal's receivers list
on a live 0.27.2 shows a dead weakref).
2. Sessions were UTC on older stacks anyway because Django's own
init_connection_state configured the timezone. Since native
psycopg-pool support (Django 5.1+), that path is gated on
`not self.pool` - and the geventpool mixin's `pool` property is
always truthy, so Django silently skips timezone (and role)
configuration for this backend on every connection. This is what
actually regressed at the psycopg2->3 / Django upgrade: the masking
layer disappeared, not the (already dead) signal.
3. psycopg3 logs "unknown PostgreSQL timezone: 'UTC0'" because the
POSIX spec is unresolvable in Python zoneinfo (cosmetic, but it
means 'UTC0' buys nothing on psycopg3).
Net effect: every session ran at the server-default timezone (verified
live: SHOW TimeZone through the pool returns 'Etc/UTC'). Deployments
whose Postgres default is non-UTC (e.g. /etc/localtime bind-mounts,
the original issue 651 report) get the EPG offset corruption back.
Fix: pin the GUC in the libpq startup packet in the pool backend's
get_connection_params():
- covers every connection the gevent pool creates; no signal, GC, or
Django-flow dependency (same pattern as the client_encoding pin the
pool already applies)
- startup-packet GUCs are the session default: they survive ROLLBACK,
and RESET TimeZone returns to UTC rather than the server default
- 'UTC' resolves cleanly in psycopg3's zoneinfo lookup, and PostgreSQL
resolves it against its own bundled tzdata, not host-mounted files
The dead signal is removed. Regression tests exercise the session
timezone through the real pool backend explicitly (the test runner's
default engine is the vanilla Django backend), and assert the
RESET-returns-to-UTC session-default property.
Tested: live 0.27.2 AIO (psycopg 3.3.4, PostgreSQL 17) - unpatched
backend+pool session shows 'Etc/UTC'; patched shows 'UTC' incl. after
RESET and ROLLBACK. Full A/B against the dev image with the server
default forced to Europe/Zurich: stock renders a 12:00Z source
programme as start="20260715140000 +0000" in /output/epg; patched
renders it correctly; the regression tests fail on stock and pass
when patched.
Three tests encoded the machine's timezone or locale into their
expectations and fail on any box east of UTC (observed on UTC+12/13;
CI's UTC and US-locale dev machines never see it):
- dateTimeUtils isSame: compared 10:00Z/11:00Z on the 15th as 'same
day', but isSame works on local calendar days and no UTC instant
falls on the same date in every timezone (at UTC+13 those are 23:00
on the 15th and 00:00 on the 16th). Use timezone-less datetimes,
which mean the same calendar day everywhere.
- RecordingUtils toDateString: formatted a UTC-noon instant and
expected the UTC date, but toDateString renders local time (UTC+12
renders the next day). Construct the Date with local components.
- SeriesModalUtils getEpisodeAirdate: the code renders the viewer's
locale via toLocaleDateString() by design, but the test hardcoded
the en-US shape (it tolerated the timezone day-shift with /1[4|5]/
yet not the day/month order). Compute the expectation with the same
API so the test asserts the wiring, not a specific locale.
No production code changes.
Failed API responses whose body is not JSON - Django's HTML "Server
Error (500)" page, nginx's HTML 502/504 pages when the backend is down
or timing out - were interpolated verbatim into the error toast,
showing users a wall of markup.
request() deliberately keeps the raw text when JSON.parse fails, and
errorNotification() rendered `${status} - ${body}` unconditionally.
Route the body through a new formatApiError() helper (in utils.js so it
is unit-testable without importing the store-heavy api module):
- JSON object bodies keep their existing pretty-printed formatting
- markup and empty bodies collapse to the response's own status line:
the declared Content-Type decides what counts as markup (body
sniffing only as fallback when the header is missing), and the label
comes from the fetch Response's statusText - the protocol's reason
phrase, defined for every status code, rather than a hardcoded status
map. HTTP/2+ transmits no reason phrase, so an empty statusText falls
back to a generic label. request() already attaches the Response to
the error, so no call-site changes are needed.
- the full suppressed body goes to console.debug so the markup stays
available for troubleshooting (deliberate, not a leftover debug
statement)
- plain-text bodies are truncated at 200 chars as a backstop
- errors without a status keep the error.message fallback
Added a new file entry to .gitignore for .uwsgi-reload and modified uwsgi.debug.ini to implement a touch-based worker reload mechanism, replacing the previous py-autoreload setting. This change enhances the management of API worker reloads during development.
Clients that build catch-up requests in QUERY layout (e.g. Open-TV /
Fred TV: /streaming/timeshift.php?username=...&stream=...&start=...)
had no matching urlpattern, so the request silently fell through to
the frontend's <path:unused_path> catch-all and got served index.html
(200 OK, wrong content) instead of reaching the proxy — no error, no
log line, the client just fails to play.
The PATH-style layout (timeshift/<user>/<pass>/<stream_id>/<ts>/<dur>)
already worked; QUERY-style autodetection already existed for outgoing
provider requests (helpers.build_timeshift_url_format_a/_b) but was
never mirrored to the incoming route.
Split timeshift_proxy into a shared _timeshift_proxy_impl plus two thin
entry points (PATH-style timeshift_proxy, new QUERY-style
timeshift_proxy_query) so both incoming layouts are recognized.
Reported against the predecessor plugin as dispatcharr_timeshift#10;
reproduced identically against dispatcharr:dev (confirmed via nginx
access log: a QUERY-style request returned 200 with a response body
exactly matching the size of frontend/dist/index.html).
Updated the session handling mechanism to improve user experience during reconnects. The first request without a `session_id` now receives a `301` redirect with a minted `session_id`, while reconnects that omit `session_id` but match an existing pool entry are served immediately without a redirect. Additionally, refined playback logic for plain GET requests to restart from byte 0, aligning with provider behavior. Updated tests to reflect these changes and ensure proper session reuse and playback anchoring.
- Added entry for extended frontend unit tests covering plugin, backup, and settings components.
- Updated Backup Manager to ensure the "Created" column refreshes when date/time format preferences change.
Updated the stream switching mechanism in the proxy server to ensure that the `stream_id` is persisted correctly and that the requesting worker waits for confirmation from the owning worker in multi-worker deployments. Improved error handling for stream switch failures, returning appropriate HTTP status codes (502/504) based on the confirmation status. Additionally, refined the metadata update process to handle cases where the requested URL is already in use, ensuring a successful response without unnecessary operations. This change addresses issues with stale metadata and enhances the robustness of the stream switching feature. (Fixes#1412)