- Add Array.isArray() guard in parseSettings for series_rules, matching the existing EPG ignore list pattern
- Include series_rules in arrayFields list in getChangedSettings to prevent stringification during settings round-trip
- Add 5 tests covering valid, corrupted, and missing series_rules data
- Rewrite ghost client tests to call real ChannelStatus and ClientManager code paths instead of reimplementing detection logic in test helpers
- Rewrite initializing state tests to invoke StreamManager.run() so the real finally block executes under test
- Replace hardcoded state list tests with assertions against ChannelState.PRE_ACTIVE frozenset
- Add missing SOURCE_BITRATE and FFMPEG_BITRATE constants to ChannelMetadataField (referenced by channel_status.py but never defined, causing AttributeError on detailed stats)
- Change ChannelState.PRE_ACTIVE from list to frozenset (immutable,
O(1) membership tests, no risk of silent mutation)
- Add optional client_ids parameter to ClientManager.remove_ghost_clients()
so callers that have already fetched SMEMBERS can pass it through,
eliminating a redundant Redis round-trip
- Pass pre-fetched client_ids from get_basic_channel_info() into
remove_ghost_clients() to remove the duplicate SMEMBERS call
- Sanitize series_rules on read (get_dvr_series_rules) and write (set_dvr_series_rules), filtering non-dict elements and non-list values
- Sanitize series_rules in CoreSettingsSerializer.update() to cover the generic settings API path (Settings page round-trip)
- Harden EPG ignore list getters (prefixes, suffixes, custom) with shared _safe_string_list helper to filter non-string/non-list values
- Add 13 unit tests covering all corruption scenarios
- Add MAX_KEEPALIVE_DURATION = 300s to TSConfig
- Track keepalive_start_time in _stream_data_generator; disconnect after cap is exceeded with a warning log
- Reset timer on real data so independent stalls don't accumulate
- Fix pre-existing test helper missing throttle attributes in NonOwnerWorkerKeepaliveTests
Previously only UnicodeDecodeError/UnicodeEncodeError were caught. Unexpected errors like disk full or permission denied would propagate
and crash the EPG refresh, which would have succeeded before the preprocessing step existed. Now all exceptions are caught and logged, leaving the original file untouched for lxml to handle as before.
lxml 6.0.2 with recover=True silently drops HTML named entities (e.g. é, î) that are not valid XML, causing channel names and program data to lose characters during EPG ingestion.
- Add preprocessing step in fetch_xmltv() that converts HTML named entities to Unicode while preserving XML-predefined entities
- Detect encoding from XML declaration, fall back to UTF-8 for unknown codecs, skip files that fail to decode cleanly
- Guard against entities resolving to XML-special characters and html.unescape partial matching edge cases
- Add 28 unit tests covering entity resolution, encoding detection, and file-level processing
- Write ERROR via ownership check OR state guard fallback when ownership TTL expired during retries
- Add INITIALIZING to cleanup task grace period monitoring
- Validate client SET entries against metadata hashes in orphaned channel cleanup; remove ghosts and clean up when no real clients remain
- Stats page self-heals by removing ghost SET entries on read
- Extract remove_ghost_clients() helper to ClientManager
- Add ChannelState.PRE_ACTIVE constant
- Fix 6 pre-existing NonOwnerWorkerKeepalive test failures