From: Christian Heller Date: Thu, 26 Dec 2024 09:11:12 +0000 (+0100) Subject: Re-organize http module code, mostly alphabetically. X-Git-Url: https://plomlompom.com/repos/%7B%7Bprefix%7D%7D/%7B%7B%20web_path%20%7D%7D/static/process_titles?a=commitdiff_plain;h=18a4a3a7840653995c10ee6413975044ff351220;p=ytplom Re-organize http module code, mostly alphabetically. --- diff --git a/src/ytplom/http.py b/src/ytplom/http.py index f1a919c..0a6fa85 100644 --- a/src/ytplom/http.py +++ b/src/ytplom/http.py @@ -30,12 +30,12 @@ _THUMBNAIL_URL_SUFFIX = '/default.jpg' # template paths _PATH_TEMPLATES = PATH_APP_DATA.joinpath('templates') -_NAME_TEMPLATE_QUERIES = Path('yt_queries.tmpl') -_NAME_TEMPLATE_RESULTS = Path('yt_results.tmpl') -_NAME_TEMPLATE_FILES = Path('files.tmpl') _NAME_TEMPLATE_FILE_DATA = Path('file_data.tmpl') -_NAME_TEMPLATE_YT_VIDEO = Path('yt_result.tmpl') +_NAME_TEMPLATE_FILES = Path('files.tmpl') _NAME_TEMPLATE_PLAYLIST = Path('playlist.tmpl') +_NAME_TEMPLATE_YT_QUERIES = Path('yt_queries.tmpl') +_NAME_TEMPLATE_YT_RESULT = Path('yt_result.tmpl') +_NAME_TEMPLATE_YT_RESULTS = Path('yt_results.tmpl') # page names PAGE_NAMES: dict[str, Path] = { @@ -48,9 +48,9 @@ PAGE_NAMES: dict[str, Path] = { 'playlist': Path('playlist'), 'purge': Path('purge'), 'thumbnails': Path('thumbnails'), - 'yt_result': Path('yt_result'), + 'yt_queries': Path('yt_queries'), 'yt_query': Path('yt_query'), - 'yt_queries': Path('yt_queries') + 'yt_result': Path('yt_result') } # misc @@ -60,6 +60,22 @@ _HEADER_CONTENT_TYPE = 'Content-Type' _HEADER_APP_JSON = 'application/json' +class Server(ThreadingHTTPServer): + """Extension of parent server providing for Player and DownloadsManager.""" + + def __init__(self, config: Config, *args, **kwargs) -> None: + super().__init__((config.host, config.port), _TaskHandler, + *args, **kwargs) + self.config = config + self.jinja = JinjaEnv(loader=JinjaFSLoader(_PATH_TEMPLATES)) + self.player = Player(config.whitelist_tags_display, + config.whitelist_tags_prefilter, + config.needed_tags_prefilter) + self.downloads = DownloadsManager() + self.downloads.clean_unfinished() + self.downloads.start_thread() + + class _ReqMap: """Wrapper over dictionary-like HTTP postings.""" @@ -92,22 +108,6 @@ class _ReqMap: yield k -class Server(ThreadingHTTPServer): - """Extension of parent server providing for Player and DownloadsManager.""" - - def __init__(self, config: Config, *args, **kwargs) -> None: - super().__init__((config.host, config.port), _TaskHandler, - *args, **kwargs) - self.config = config - self.jinja = JinjaEnv(loader=JinjaFSLoader(_PATH_TEMPLATES)) - self.player = Player(config.whitelist_tags_display, - config.whitelist_tags_prefilter, - config.needed_tags_prefilter) - self.downloads = DownloadsManager() - self.downloads.clean_unfinished() - self.downloads.start_thread() - - class _TaskHandler(BaseHTTPRequestHandler): """Handler for GET and POST requests to our server.""" server: Server @@ -136,23 +136,41 @@ class _TaskHandler(BaseHTTPRequestHandler): postvars = _ReqMap( self.rfile.read(int(self.headers['content-length'])).decode(), _HEADER_APP_JSON == self.headers[_HEADER_CONTENT_TYPE]) - if PAGE_NAMES['files'] == page_name: - self._receive_files_command(postvars) - elif PAGE_NAMES['file'] == page_name: + if PAGE_NAMES['file'] == page_name: self._receive_file_data(Hash.from_b64(toks_url[2]), postvars) - elif PAGE_NAMES['yt_queries'] == page_name: - self._receive_yt_query(QueryText(postvars.first_for('query'))) + elif PAGE_NAMES['files'] == page_name: + self._receive_files_command(postvars) elif PAGE_NAMES['player'] == page_name: self._receive_player_command(postvars) elif PAGE_NAMES['purge'] == page_name: self._purge_deleted_files() + elif PAGE_NAMES['yt_queries'] == page_name: + self._receive_yt_query(QueryText(postvars.first_for('query'))) - def _purge_deleted_files(self) -> None: + def _receive_file_data(self, digest: Hash, postvars: _ReqMap) -> None: + if not (self.server.config.allow_file_edit # also if whitelist, … + and self.server.config.whitelist_tags_display.empty): + self._send_http('no way', code=403) # … cuz input form under … + return # … this display filter might have suppressed set tags with DbConn() as conn: - VideoFile.purge_deleteds(conn) - self.server.player.load_files_and_mpv() + file = VideoFile.get_one(conn, digest) + if postvars.has_key('unlink'): + file.unlink_locally() + file.set_flags({FILE_FLAGS[FlagName(name)] + for name in postvars.all_for('flags')}) + file.tags = TagSet.from_str_list(postvars.all_for('tags')) + file.save(conn) conn.commit() - self._send_http('OK') + file.ensure_absence_if_deleted() + self._redirect(Path(postvars.first_for('redir_target'))) + + def _receive_files_command(self, postvars: _ReqMap) -> None: + for k in postvars.key_starting_with('play_'): + with DbConn() as conn: + file = VideoFile.get_one( + conn, Hash.from_b64(k.split('_', 1)[1])) + self.server.player.inject_and_play(file) + self._redirect(Path(postvars.first_for('redir_target'))) def _receive_player_command(self, postvars: _ReqMap) -> None: command = postvars.first_for('command') @@ -178,30 +196,12 @@ class _TaskHandler(BaseHTTPRequestHandler): postvars.first_for('needed_tags')) self._send_http('OK') - def _receive_files_command(self, postvars: _ReqMap) -> None: - for k in postvars.key_starting_with('play_'): - with DbConn() as conn: - file = VideoFile.get_one( - conn, Hash.from_b64(k.split('_', 1)[1])) - self.server.player.inject_and_play(file) - self._redirect(Path(postvars.first_for('redir_target'))) - - def _receive_file_data(self, digest: Hash, postvars: _ReqMap) -> None: - if not (self.server.config.allow_file_edit # also if whitelist, … - and self.server.config.whitelist_tags_display.empty): - self._send_http('no way', code=403) # … cuz input form under … - return # … this display filter might have suppressed set tags + def _purge_deleted_files(self) -> None: with DbConn() as conn: - file = VideoFile.get_one(conn, digest) - if postvars.has_key('unlink'): - file.unlink_locally() - file.set_flags({FILE_FLAGS[FlagName(name)] - for name in postvars.all_for('flags')}) - file.tags = TagSet.from_str_list(postvars.all_for('tags')) - file.save(conn) + VideoFile.purge_deleteds(conn) + self.server.player.load_files_and_mpv() conn.commit() - file.ensure_absence_if_deleted() - self._redirect(Path(postvars.first_for('redir_target'))) + self._send_http('OK') def _receive_yt_query(self, query_txt: QueryText) -> None: with DbConn() as conn: @@ -218,24 +218,24 @@ class _TaskHandler(BaseHTTPRequestHandler): toks_url = Path(url.path).parts page_name = Path(toks_url[1] if len(toks_url) > 1 else '') try: - if PAGE_NAMES['thumbnails'] == page_name: - self._send_thumbnail(Path(toks_url[2])) - elif PAGE_NAMES['download'] == page_name: + if PAGE_NAMES['download'] == page_name: self._send_or_download_video(YoutubeId(toks_url[2])) - elif PAGE_NAMES['files'] == page_name: - self._send_files_index(_ReqMap(url.query)) + elif PAGE_NAMES['events'] == page_name: + self._send_events(_ReqMap(url.query)) elif PAGE_NAMES['file'] == page_name: self._send_file_data(Hash.from_b64(toks_url[2])) - elif PAGE_NAMES['yt_result'] == page_name: - self._send_yt_result(YoutubeId(toks_url[2])) + elif PAGE_NAMES['files'] == page_name: + self._send_files_index(_ReqMap(url.query)) elif PAGE_NAMES['missing'] == page_name: self._send_missing_json() - elif PAGE_NAMES['yt_query'] == page_name: - self._send_yt_query_page(QueryId(toks_url[2])) + elif PAGE_NAMES['thumbnails'] == page_name: + self._send_thumbnail(Path(toks_url[2])) + elif PAGE_NAMES['yt_result'] == page_name: + self._send_yt_result(YoutubeId(toks_url[2])) elif PAGE_NAMES['yt_queries'] == page_name: self._send_yt_queries_index_and_search() - elif PAGE_NAMES['events'] == page_name: - self._send_events(_ReqMap(url.query)) + elif PAGE_NAMES['yt_query'] == page_name: + self._send_yt_query_page(QueryId(toks_url[2])) else: # e.g. for / self._send_playlist() except NotFoundException as e: @@ -252,21 +252,6 @@ class _TaskHandler(BaseHTTPRequestHandler): tmpl_ctx['page_names'] = PAGE_NAMES self._send_http(tmpl.render(**tmpl_ctx)) - def _send_thumbnail(self, filename: Path) -> None: - ensure_expected_dirs([PATH_THUMBNAILS]) - path_thumbnail = PATH_THUMBNAILS.joinpath(filename) - if not path_thumbnail.exists(): - video_id = filename.stem - url = f'{_THUMBNAIL_URL_PREFIX}{video_id}{_THUMBNAIL_URL_SUFFIX}' - try: - urlretrieve(url, PATH_THUMBNAILS.joinpath(f'{video_id}.jpg')) - except HTTPError as e: - if 404 == e.code: - raise NotFoundException from e - raise e - with path_thumbnail.open('rb') as f: - self._send_http(f.read(), [(_HEADER_CONTENT_TYPE, 'image/jpg')]) - def _send_or_download_video(self, video_id: YoutubeId) -> None: try: with DbConn() as conn: @@ -282,49 +267,55 @@ class _TaskHandler(BaseHTTPRequestHandler): .joinpath(PAGE_NAMES['yt_result']) .joinpath(video_id)) - def _send_yt_query_page(self, query_id: QueryId) -> None: - with DbConn() as conn: - query = YoutubeQuery.get_one(conn, str(query_id)) - results = YoutubeVideo.get_all_for_query(conn, query_id) - self._send_rendered_template(_NAME_TEMPLATE_RESULTS, - {'query': query.text, 'videos': results}) - - def _send_yt_queries_index_and_search(self) -> None: - with DbConn() as conn: - quota_count = QuotaLog.current(conn) - queries_data = [ - q for q in YoutubeQuery.get_all(conn) - if q.retrieved_at > self.server.config.queries_cutoff] - queries_data.sort(key=lambda q: q.retrieved_at, reverse=True) - self._send_rendered_template(_NAME_TEMPLATE_QUERIES, - {'queries': queries_data, - 'quota_count': quota_count, - 'selected': 'yt_queries'}) - - def _send_yt_result(self, video_id: YoutubeId) -> None: - conn = DbConn() - with DbConn() as conn: - linked_queries = YoutubeQuery.get_all_for_video(conn, video_id) - try: - video_data = YoutubeVideo.get_one(conn, video_id) - except NotFoundException: - video_data = YoutubeVideo(video_id) - file_digest: Optional[str] - file_path: Optional[Path] - try: - file = VideoFile.get_by_yt_id(conn, video_id) - file_digest = file.digest.b64 - file_path = file.rel_path if file.present else None - except NotFoundException: - file_path, file_digest = None, None - self._send_rendered_template( - _NAME_TEMPLATE_YT_VIDEO, - {'video_data': video_data, - 'is_temp': video_id in self.server.downloads.ids_unfinished, - 'file_digest': file_digest, - 'file_path': file_path, - 'youtube_prefix': YOUTUBE_URL_PREFIX, - 'queries': linked_queries}) + def _send_events(self, params: _ReqMap) -> None: + self._send_http(headers=[(_HEADER_CONTENT_TYPE, 'text/event-stream'), + ('Cache-Control', 'no-cache'), + ('Connection', 'keep-alive')]) + selected: Optional[VideoFile] = None + last_sent = '' + payload: dict[str, Any] = {} + time_last_write = 0.0 + while True: + if not payload and time_last_write < time() - _PING_INTERVAL_S: + payload['ping'] = True + if payload: + payload_encoded = f'data: {json_dumps(payload)}\n\n'.encode() + try: + self.wfile.write(payload_encoded) + self.wfile.flush() + except BrokenPipeError: + break + time_last_write = time() + payload.clear() + if not self.server.player.current_digest: + selected = None + elif ((not selected) + or (selected.digest != self.server.player.current_digest)): + with DbConn() as conn: + selected = VideoFile.get_one_with_whitelist_tags_display( + conn, + self.server.player.current_digest, + self.server.config.whitelist_tags_display) + if last_sent < self.server.player.last_update: + last_sent = self.server.player.last_update + title, tags, digest = '', '', '' + if selected: + tags = selected.tags_showable.joined + title = str(selected.rel_path) + digest = selected.digest.b64 + payload['is_running'] = self.server.player.is_running + payload['is_playing'] = self.server.player.is_playing + payload['can_play'] = self.server.player.can_play + payload['title_tags'] = tags + payload['title_digest'] = digest + payload['title'] = title + if params.has_key('playlist'): + payload['idx'] = self.server.player.idx + payload['playlist_files'] = [ + {'rel_path': str(f.rel_path), 'digest': f.digest.b64} + for f in self.server.player.playlist] + else: + sleep(_EVENTS_UPDATE_INTERVAL_S) def _send_file_data(self, digest: Hash) -> None: with DbConn() as conn: @@ -369,59 +360,68 @@ class _TaskHandler(BaseHTTPRequestHandler): self._send_http(json_dumps(missing), headers=[(_HEADER_CONTENT_TYPE, _HEADER_APP_JSON)]) + def _send_thumbnail(self, filename: Path) -> None: + ensure_expected_dirs([PATH_THUMBNAILS]) + path_thumbnail = PATH_THUMBNAILS.joinpath(filename) + if not path_thumbnail.exists(): + video_id = filename.stem + url = f'{_THUMBNAIL_URL_PREFIX}{video_id}{_THUMBNAIL_URL_SUFFIX}' + try: + urlretrieve(url, PATH_THUMBNAILS.joinpath(f'{video_id}.jpg')) + except HTTPError as e: + if 404 == e.code: + raise NotFoundException from e + raise e + with path_thumbnail.open('rb') as f: + self._send_http(f.read(), [(_HEADER_CONTENT_TYPE, 'image/jpg')]) + + def _send_yt_result(self, video_id: YoutubeId) -> None: + conn = DbConn() + with DbConn() as conn: + linked_queries = YoutubeQuery.get_all_for_video(conn, video_id) + try: + video_data = YoutubeVideo.get_one(conn, video_id) + except NotFoundException: + video_data = YoutubeVideo(video_id) + file_digest: Optional[str] + file_path: Optional[Path] + try: + file = VideoFile.get_by_yt_id(conn, video_id) + file_digest = file.digest.b64 + file_path = file.rel_path if file.present else None + except NotFoundException: + file_path, file_digest = None, None + self._send_rendered_template( + _NAME_TEMPLATE_YT_RESULT, + {'video_data': video_data, + 'is_temp': video_id in self.server.downloads.ids_unfinished, + 'file_digest': file_digest, + 'file_path': file_path, + 'youtube_prefix': YOUTUBE_URL_PREFIX, + 'queries': linked_queries}) + + def _send_yt_queries_index_and_search(self) -> None: + with DbConn() as conn: + quota_count = QuotaLog.current(conn) + queries_data = [ + q for q in YoutubeQuery.get_all(conn) + if q.retrieved_at > self.server.config.queries_cutoff] + queries_data.sort(key=lambda q: q.retrieved_at, reverse=True) + self._send_rendered_template(_NAME_TEMPLATE_YT_QUERIES, + {'queries': queries_data, + 'quota_count': quota_count, + 'selected': 'yt_queries'}) + + def _send_yt_query_page(self, query_id: QueryId) -> None: + with DbConn() as conn: + query = YoutubeQuery.get_one(conn, str(query_id)) + results = YoutubeVideo.get_all_for_query(conn, query_id) + self._send_rendered_template(_NAME_TEMPLATE_YT_RESULTS, + {'query': query.text, 'videos': results}) + def _send_playlist(self) -> None: self._send_rendered_template( _NAME_TEMPLATE_PLAYLIST, {'selected': 'playlist', 'filter_path': self.server.player.filter_path, 'needed_tags': self.server.player.needed_tags.joined}) - - def _send_events(self, params: _ReqMap) -> None: - self._send_http(headers=[(_HEADER_CONTENT_TYPE, 'text/event-stream'), - ('Cache-Control', 'no-cache'), - ('Connection', 'keep-alive')]) - selected: Optional[VideoFile] = None - last_sent = '' - payload: dict[str, Any] = {} - time_last_write = 0.0 - while True: - if not payload and time_last_write < time() - _PING_INTERVAL_S: - payload['ping'] = True - if payload: - payload_encoded = f'data: {json_dumps(payload)}\n\n'.encode() - try: - self.wfile.write(payload_encoded) - self.wfile.flush() - except BrokenPipeError: - break - time_last_write = time() - payload.clear() - if not self.server.player.current_digest: - selected = None - elif ((not selected) - or (selected.digest != self.server.player.current_digest)): - with DbConn() as conn: - selected = VideoFile.get_one_with_whitelist_tags_display( - conn, - self.server.player.current_digest, - self.server.config.whitelist_tags_display) - if last_sent < self.server.player.last_update: - last_sent = self.server.player.last_update - title, tags, digest = '', '', '' - if selected: - tags = selected.tags_showable.joined - title = str(selected.rel_path) - digest = selected.digest.b64 - payload['is_running'] = self.server.player.is_running - payload['is_playing'] = self.server.player.is_playing - payload['can_play'] = self.server.player.can_play - payload['title_tags'] = tags - payload['title_digest'] = digest - payload['title'] = title - if params.has_key('playlist'): - payload['idx'] = self.server.player.idx - payload['playlist_files'] = [ - {'rel_path': str(f.rel_path), 'digest': f.digest.b64} - for f in self.server.player.playlist] - else: - sleep(_EVENTS_UPDATE_INTERVAL_S)