diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c94de6f..d475ffe1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- **EPG program times shifted by the host system's UTC offset when `/etc/localtime` is bind-mounted into the container**. Mounting `/etc/localtime` from a non-UTC host causes PostgreSQL to silently resolve the `'UTC'` timezone name to the host's local timezone (e.g. CDT, CET) rather than actual UTC - even though `SHOW timezone` returns `UTC` and the zoneinfo file exists. This made PostgreSQL format all stored `timestamptz` values with the host's UTC offset, and psycopg2 returned datetimes shifted by that offset, causing every EPG program time to be read back N hours wrong and written to the XML output incorrectly. The fix registers a Django `connection_created` signal that issues `SET TIME ZONE 'UTC0'` on every new database connection. `UTC0` is a POSIX timezone string that bypasses the broken zoneinfo name-lookup path entirely; it resolves unconditionally to UTC+00 regardless of the host timezone or what files are mounted. (Fixes #651) - **Xtream Codes `player_api.php` missing `active_cons` and reporting wrong `max_connections`**. The `user_info` block returned by the XC API did not include the `active_cons` field, which Enigma2 clients (XStreamity, XKlass) read unconditionally and crash with `KeyError: 'active_cons'` when it is absent. `max_connections` was also hardcoded to the system-wide tuner count for every user, ignoring per-user `stream_limit` configuration. `xc_get_info` now reports `max_connections` as the user's `stream_limit` when set, falling back to the system tuner count for unlimited users; `active_cons` is the user's own active connection count when they have a per-user limit, or the system-wide active connection count when they do not (so unlimited clients can still see how much of the global tuner pool is in use). The existing `get_user_active_connections` helper was generalized to accept `user_id=None` for the system-wide query rather than duplicating its Redis scan logic. (Fixes #990) - **Plugin discovery re-running on every connect event**. The `PluginManager` cache hit check used `if self._registry and ...`, which always evaluated false when zero plugins were installed because an empty dict is falsy. Every Connect event (`recording_start`, `client_connect`, `channel_start`, etc.) was therefore triggering a full filesystem walk of `/data/plugins` and emitting `Discovering plugins (no DB sync) in /data/plugins` / `Discovered 0 plugin(s)` log lines. Tracked separately via a new `_discovery_completed` flag so the cache short-circuits subsequent calls regardless of registry contents, dropping discovery to once per worker process lifetime. - **Empty show/season folders left behind after deleting a recording**. Deleting a recording removes the MKV (or HLS working directory if still in progress) but previously left the parent show / season directories on disk even when they no longer contained any other recordings. After file cleanup, `RecordingViewSet.destroy` now walks up from the deleted file's parent directory and removes any now-empty directories, stopping at the `/data/recordings` library root so the root itself is never touched. diff --git a/core/apps.py b/core/apps.py index ee182e89..4b7fed5c 100644 --- a/core/apps.py +++ b/core/apps.py @@ -25,6 +25,14 @@ class CoreConfig(AppConfig): import core.signals from dispatcharr.app_initialization import should_skip_initialization + # Force UTC0 on every new DB connection. + from django.db.backends.signals import connection_created + + def _force_utc0(sender, connection, **kwargs): + connection.cursor().execute("SET TIME ZONE 'UTC0'") + + connection_created.connect(_force_utc0, dispatch_uid='force_db_utc0') + # Sync developer notifications and check for version updates on startup # Only run in the main process (not in management commands, migrations, or workers) if should_skip_initialization():