home · contact · privacy
Re-organize http module code, mostly alphabetically.
authorChristian Heller <c.heller@plomlompom.de>
Thu, 26 Dec 2024 09:11:12 +0000 (10:11 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Thu, 26 Dec 2024 09:11:12 +0000 (10:11 +0100)
src/ytplom/http.py

index f1a919c33949bcc6eb821ce819f83fd21cc10ad5..0a6fa85e824a317c7ea3f8c11d5abc9410c773e5 100644 (file)
@@ -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)