mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-21 17:19:20 +00:00
Add end-to-end catch-up support for XC clients and native apps: provider proxy with failover and per-viewer session pooling, REST session minting for tokenless playback URLs, catch-up admin stats, combined connection stats, and Stats UI with dedicated cards plus websocket updates. Includes Redis namespace consolidation under timeshift:* (dropping legacy timeshift_ id prefixes), dedicated catch-up stop by session_id, and cleaner channel/client metadata split for stats keys. Closes #133
96 lines
2.7 KiB
Python
96 lines
2.7 KiB
Python
"""Redis key patterns and logical ids for catch-up (timeshift).
|
|
|
|
All catch-up state lives under the ``timeshift:`` prefix so live-proxy
|
|
janitors and live stats scans never see it.
|
|
"""
|
|
|
|
import re
|
|
import secrets
|
|
|
|
# Logical id: stats channel key segment ``timeshift:channel:{id}:...``
|
|
_STATS_CHANNEL_RE = re.compile(r"^(\d+)_(.+)$")
|
|
|
|
|
|
class TimeshiftRedisKeys:
|
|
@staticmethod
|
|
def channel_metadata(channel_id):
|
|
return f"timeshift:channel:{channel_id}:metadata"
|
|
|
|
@staticmethod
|
|
def clients(channel_id):
|
|
return f"timeshift:channel:{channel_id}:clients"
|
|
|
|
@staticmethod
|
|
def client_metadata(channel_id, client_id):
|
|
return f"timeshift:channel:{channel_id}:clients:{client_id}"
|
|
|
|
@staticmethod
|
|
def client_stop(channel_id, client_id):
|
|
return f"timeshift:channel:{channel_id}:client:{client_id}:stop"
|
|
|
|
@staticmethod
|
|
def pool(session_id):
|
|
return f"timeshift:pool:{session_id}"
|
|
|
|
@staticmethod
|
|
def pool_lock(session_id):
|
|
return f"timeshift:pool:{session_id}:lock"
|
|
|
|
@staticmethod
|
|
def pool_superseded(session_id):
|
|
return f"timeshift:pool:{session_id}:superseded"
|
|
|
|
@staticmethod
|
|
def pool_scan_pattern():
|
|
return "timeshift:pool:*"
|
|
|
|
@staticmethod
|
|
def api_session(session_id):
|
|
return f"timeshift:session:{session_id}"
|
|
|
|
@staticmethod
|
|
def stats_grace(stats_channel_id, client_id):
|
|
return f"timeshift:grace:{stats_channel_id}:{client_id}"
|
|
|
|
@staticmethod
|
|
def stream_generation(virtual_channel_id, client_id):
|
|
return f"timeshift:stream_gen:{virtual_channel_id}:{client_id}"
|
|
|
|
@staticmethod
|
|
def stream_generation_scan_pattern(client_id):
|
|
return f"timeshift:stream_gen:*:{client_id}"
|
|
|
|
@staticmethod
|
|
def format_cache(account_id):
|
|
return f"timeshift:format_idx:{account_id}"
|
|
|
|
|
|
def mint_session_id():
|
|
"""Opaque per-viewer session id (URL query param and pool key suffix)."""
|
|
return secrets.token_urlsafe(16)
|
|
|
|
|
|
def stats_channel_id(channel_id, session_id):
|
|
"""Stable stats/redis channel id for one viewer on a channel."""
|
|
return f"{channel_id}_{session_id}"
|
|
|
|
|
|
def programme_media_id(channel_id, safe_ts):
|
|
"""Catch-up position identity (channel + programme timestamp)."""
|
|
return f"{channel_id}_{safe_ts}"
|
|
|
|
|
|
def virtual_channel_id(channel_id, safe_ts, stream_id_value):
|
|
"""Programme virtual id for stop keys and stream generation."""
|
|
return f"{channel_id}_{safe_ts}_{stream_id_value}"
|
|
|
|
|
|
def parse_stats_channel_id(stats_channel_id):
|
|
"""Split a stats channel id into numeric channel id and session id."""
|
|
match = _STATS_CHANNEL_RE.match(str(stats_channel_id or ""))
|
|
if not match:
|
|
return None
|
|
return {
|
|
"channel_id": int(match.group(1)),
|
|
"session_id": match.group(2),
|
|
}
|