diff --git a/qt/aqt/data/web/imgs/BUILD.bazel b/qt/aqt/data/web/imgs/BUILD.bazel index 58b338198..26b6d5efd 100644 --- a/qt/aqt/data/web/imgs/BUILD.bazel +++ b/qt/aqt/data/web/imgs/BUILD.bazel @@ -1,6 +1,7 @@ filegroup( name = "imgs", srcs = glob([ + "**/*.ico", "**/*.png", "**/*.svg", ]), diff --git a/qt/aqt/data/web/imgs/favicon.ico b/qt/aqt/data/web/imgs/favicon.ico new file mode 100644 index 000000000..257849bdc Binary files /dev/null and b/qt/aqt/data/web/imgs/favicon.ico differ diff --git a/qt/aqt/main.py b/qt/aqt/main.py index d92db613b..f4c45f484 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -1680,6 +1680,7 @@ title="{}" {}>{}""".format( def setupMediaServer(self) -> None: self.mediaServer = aqt.mediasrv.MediaServer(self) self.mediaServer.start() + self.mediaServer.wait_start_up() def baseHTML(self) -> str: return f'' diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index f67df5f44..2e6e3f0c2 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -3,16 +3,20 @@ from __future__ import annotations +import datetime import logging import mimetypes import os import re +import socket import sys import threading import time import traceback +from contextlib import contextmanager from dataclasses import dataclass -from http import HTTPStatus +from http import HTTPStatus, client +from typing import Any, Generator import flask import flask_cors # type: ignore @@ -32,6 +36,18 @@ from aqt.deckoptions import DeckOptionsDialog from aqt.operations.deck import update_deck_configs from aqt.qt import * + +@contextmanager +def http_connection( + *args: Any, **kwds: Any +) -> Generator[client.HTTPConnection, None, None]: + resource = client.HTTPConnection(*args, **kwds) + try: + yield resource + finally: + resource.close() + + app = flask.Flask(__name__, root_path="/fake") flask_cors.CORS(app) @@ -121,6 +137,46 @@ class MediaServer(threading.Thread): except KeyError: pass + def getHost(self) -> str: + self._ready.wait() + return str(self.server.effective_host) # type: ignore + + def wait_start_up(self) -> None: + self.check_server(self.getHost(), self.getPort(), "/favicon.ico") + + @classmethod + def check_server( + cls, + host: str, + port: int, + path_info: str = "/", + timeout: int = 1, + retries: int = 30, + ) -> int: + """Perform a request until the server reply""" + if retries < 0: + return 0 + # https://github.com/Pylons/webtest/blob/4b8a3ebf984185ff4fefb31b4d0cf82682e1fcf7/webtest/http.py#L123-L132 + for index in range(retries): + if devMode or index > 0: + print( + f"{datetime.datetime.now()} waiting media server on {host}:{port}..." + ) + try: + with http_connection(host, port, timeout=timeout) as conn: + conn.request("GET", path_info) + res = conn.getresponse() + return res.status + except (socket.error, client.HTTPException): + time.sleep(0.3) + return 0 + + +@app.route("/favicon.ico") +def favicon() -> Response: + request = BundledFileRequest(os.path.join("imgs", "favicon.ico")) + return _handle_builtin_file_request(request) + def _mime_for_path(path: str) -> str: "Mime type for provided path/filename."