From: Christian Heller <c.heller@plomlompom.de> Date: Sun, 15 Dec 2024 07:27:41 +0000 (+0100) Subject: Close events stream earlier by checking for frequent client pings. X-Git-Url: https://plomlompom.com/repos/%7B%7Bdb.prefix%7D%7D/%7B%7B%20web_path%20%7D%7D/static/%7B%7Bprefix%7D%7D/coming?a=commitdiff_plain;h=87349b588639f09febcc0770f087e34bdda004cb;p=ytplom Close events stream earlier by checking for frequent client pings. --- diff --git a/src/templates/_base.tmpl b/src/templates/_base.tmpl index cbda2ab..0350ced 100644 --- a/src/templates/_base.tmpl +++ b/src/templates/_base.tmpl @@ -6,17 +6,26 @@ <script> const RETRY_INTERVAL_S = 5; +const PING_INTERVAL_S = 1; const PATH_EVENTS = '/{{page_names.events}}'; +const PATH_EVENTS_PING = '/{{page_names.events_ping}}'; const PATH_PLAYER = '/{{page_names.player}}'; const PATH_PLAYLIST = '/{{page_names.playlist}}'; const PATH_PREFIX_FILE = '/{{page_names.file}}/'; var event_handlers = []; var events_params = ''; +var client_id = null; + +setInterval(function() {if (client_id) { send_to({client_id: [client_id]}, PATH_EVENTS_PING); }}, + PING_INTERVAL_S * 1000); function connect_events() { const events_stream = new EventSource(`${PATH_EVENTS}?${events_params}`); events_stream.onmessage = function(event) { const data = JSON.parse(event.data); + if (data.your_id) { + client_id = data.your_id; + return; } for (let i = 0; i < event_handlers.length; i++) { event_handlers[i](data); }} events_stream.onerror = function(error) { diff --git a/src/ytplom/http.py b/src/ytplom/http.py index de89502..4dbe651 100644 --- a/src/ytplom/http.py +++ b/src/ytplom/http.py @@ -2,11 +2,12 @@ from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler from json import dumps as json_dumps, loads as json_loads from pathlib import Path -from time import sleep +from time import time, sleep from typing import Any, Generator, Optional from urllib.parse import parse_qs, urlparse from urllib.request import urlretrieve from urllib.error import HTTPError +from uuid import uuid4 from jinja2 import ( # type: ignore Environment as JinjaEnv, FileSystemLoader as JinjaFSLoader) from ytplom.db import Hash, DbConn @@ -36,6 +37,7 @@ _NAME_TEMPLATE_PLAYLIST = Path('playlist.tmpl') PAGE_NAMES: dict[str, Path] = { 'download': Path('dl'), 'events': Path('events'), + 'events_ping': Path('events_ping'), 'file': Path('file'), 'files': Path('files'), 'missing': Path('missing'), @@ -48,6 +50,7 @@ PAGE_NAMES: dict[str, Path] = { } # misc +_MAX_PING_AGE_S = 2 _HEADER_CONTENT_TYPE = 'Content-Type' _HEADER_APP_JSON = 'application/json' @@ -94,6 +97,7 @@ class Server(ThreadingHTTPServer): *args, **kwargs) self.config = config self.jinja = JinjaEnv(loader=JinjaFSLoader(_PATH_TEMPLATES)) + self.event_pings: dict[str, float] = {} self.player = Player(config.whitelist_tags_display, config.whitelist_tags_prefilter, config.needed_tags_prefilter) @@ -139,6 +143,12 @@ class _TaskHandler(BaseHTTPRequestHandler): self._receive_yt_query(QueryText(postvars.first_for('query'))) elif PAGE_NAMES['player'] == page_name: self._receive_player_command(postvars) + elif PAGE_NAMES['events_ping'] == page_name: + self._receive_events_ping(postvars.first_for('client_id')) + + def _receive_events_ping(self, client_id: str) -> None: + self.server.event_pings[client_id] = time() + self._send_http('OK', code=200) def _receive_player_command(self, postvars: _ReqMap) -> None: command = postvars.first_for('command') @@ -368,9 +378,18 @@ class _TaskHandler(BaseHTTPRequestHandler): self._send_http(headers=[(_HEADER_CONTENT_TYPE, 'text/event-stream'), ('Cache-Control', 'no-cache'), ('Connection', 'keep-alive')]) + client_id = str(uuid4()) playing: Optional[VideoFile] = None last_sent = '' + init_msg = {'your_id': client_id} + self.wfile.write(f'data: {json_dumps(init_msg)}\n\n'.encode()) + self.wfile.flush() while True: + min_ping_time = time() - _MAX_PING_AGE_S + if (client_id in self.server.event_pings + and self.server.event_pings[client_id] < min_ping_time): + del self.server.event_pings[client_id] + break if not self.server.player.current_digest: playing = None elif ((not playing) @@ -403,5 +422,5 @@ class _TaskHandler(BaseHTTPRequestHandler): f'data: {json_dumps(data)}\n\n'.encode()) self.wfile.flush() except BrokenPipeError: - return + break sleep(0.25)