mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -04:00
use ResourceReader for serving bundled web files
This commit is contained in:
parent
3f321f08f1
commit
b5e9eba26f
1 changed files with 74 additions and 31 deletions
|
@ -4,6 +4,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
@ -30,27 +31,7 @@ from aqt.changenotetype import ChangeNotetypeDialog
|
||||||
from aqt.deckoptions import DeckOptionsDialog
|
from aqt.deckoptions import DeckOptionsDialog
|
||||||
from aqt.operations.deck import update_deck_configs
|
from aqt.operations.deck import update_deck_configs
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import aqt_data_folder
|
|
||||||
|
|
||||||
|
|
||||||
def _getExportFolder() -> str:
|
|
||||||
if not (data_folder := os.getenv("ANKI_DATA_FOLDER")):
|
|
||||||
data_folder = aqt_data_folder()
|
|
||||||
webInSrcFolder = os.path.abspath(os.path.join(data_folder, "web"))
|
|
||||||
if os.path.exists(webInSrcFolder):
|
|
||||||
return webInSrcFolder
|
|
||||||
elif isMac:
|
|
||||||
dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
return os.path.abspath(f"{dir}/../../Resources/web")
|
|
||||||
else:
|
|
||||||
if os.environ.get("TEST_TARGET"):
|
|
||||||
# running tests in bazel; we have no data
|
|
||||||
return "."
|
|
||||||
else:
|
|
||||||
raise Exception("couldn't find web folder")
|
|
||||||
|
|
||||||
|
|
||||||
_exportFolder = _getExportFolder()
|
|
||||||
app = flask.Flask(__name__, root_path="/fake")
|
app = flask.Flask(__name__, root_path="/fake")
|
||||||
flask_cors.CORS(app)
|
flask_cors.CORS(app)
|
||||||
|
|
||||||
|
@ -63,6 +44,12 @@ class LocalFileRequest:
|
||||||
path: str
|
path: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BundledFileRequest:
|
||||||
|
# path relative to aqt data folder
|
||||||
|
path: str
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class NotFound:
|
class NotFound:
|
||||||
message: str
|
message: str
|
||||||
|
@ -135,6 +122,19 @@ class MediaServer(threading.Thread):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _mime_for_path(path: str) -> str:
|
||||||
|
"Mime type for provided path/filename."
|
||||||
|
if path.endswith(".css"):
|
||||||
|
# some users may have invalid mime type in the Windows registry
|
||||||
|
return "text/css"
|
||||||
|
elif path.endswith(".js"):
|
||||||
|
return "application/javascript"
|
||||||
|
else:
|
||||||
|
# autodetect
|
||||||
|
mime, _encoding = mimetypes.guess_type(path)
|
||||||
|
return mime or "application/octet-stream"
|
||||||
|
|
||||||
|
|
||||||
def _handle_local_file_request(request: LocalFileRequest) -> Response:
|
def _handle_local_file_request(request: LocalFileRequest) -> Response:
|
||||||
directory = request.root
|
directory = request.root
|
||||||
path = request.path
|
path = request.path
|
||||||
|
@ -164,14 +164,7 @@ def _handle_local_file_request(request: LocalFileRequest) -> Response:
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if fullpath.endswith(".css"):
|
mimetype = _mime_for_path(fullpath)
|
||||||
# some users may have invalid mime type in the Windows registry
|
|
||||||
mimetype = "text/css"
|
|
||||||
elif fullpath.endswith(".js"):
|
|
||||||
mimetype = "application/javascript"
|
|
||||||
else:
|
|
||||||
# autodetect
|
|
||||||
mimetype = None
|
|
||||||
if os.path.exists(fullpath):
|
if os.path.exists(fullpath):
|
||||||
return flask.send_file(fullpath, mimetype=mimetype, conditional=True)
|
return flask.send_file(fullpath, mimetype=mimetype, conditional=True)
|
||||||
else:
|
else:
|
||||||
|
@ -197,6 +190,51 @@ def _handle_local_file_request(request: LocalFileRequest) -> Response:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _builtin_data(path: str) -> bytes:
|
||||||
|
"""Return data from file in aqt/data folder.
|
||||||
|
Path must use forward slash separators."""
|
||||||
|
# overriden location?
|
||||||
|
if data_folder := os.getenv("ANKI_DATA_FOLDER"):
|
||||||
|
full_path = os.path.join(data_folder, path)
|
||||||
|
with open(full_path, "rb") as f:
|
||||||
|
return f.read()
|
||||||
|
else:
|
||||||
|
if isWin and not getattr(sys, "frozen", False) :
|
||||||
|
# default Python resource loader expects backslashes on Windows
|
||||||
|
path = path.replace("/", "\\")
|
||||||
|
reader = aqt.__loader__.get_resource_reader("aqt") # type: ignore
|
||||||
|
with reader.open_resource(path) as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_builtin_file_request(request: BundledFileRequest) -> Response:
|
||||||
|
path = request.path
|
||||||
|
mimetype = _mime_for_path(path)
|
||||||
|
data_path = f"data/web/{path}"
|
||||||
|
try:
|
||||||
|
data = _builtin_data(data_path)
|
||||||
|
return Response(data, mimetype=mimetype)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return flask.make_response(
|
||||||
|
f"Invalid path: {path}",
|
||||||
|
HTTPStatus.NOT_FOUND,
|
||||||
|
)
|
||||||
|
except Exception as error:
|
||||||
|
if devMode:
|
||||||
|
print(
|
||||||
|
"Caught HTTP server exception,\n%s"
|
||||||
|
% "".join(traceback.format_exception(*sys.exc_info())),
|
||||||
|
)
|
||||||
|
|
||||||
|
# swallow it - user likely surfed away from
|
||||||
|
# review screen before an image had finished
|
||||||
|
# downloading
|
||||||
|
return flask.make_response(
|
||||||
|
str(error),
|
||||||
|
HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/<path:pathin>", methods=["GET", "POST"])
|
@app.route("/<path:pathin>", methods=["GET", "POST"])
|
||||||
def handle_request(pathin: str) -> Response:
|
def handle_request(pathin: str) -> Response:
|
||||||
request = _extract_request(pathin)
|
request = _extract_request(pathin)
|
||||||
|
@ -211,6 +249,8 @@ def handle_request(pathin: str) -> Response:
|
||||||
)
|
)
|
||||||
elif callable(request):
|
elif callable(request):
|
||||||
return _handle_dynamic_request(request)
|
return _handle_dynamic_request(request)
|
||||||
|
elif isinstance(request, BundledFileRequest):
|
||||||
|
return _handle_builtin_file_request(request)
|
||||||
elif isinstance(request, LocalFileRequest):
|
elif isinstance(request, LocalFileRequest):
|
||||||
return _handle_local_file_request(request)
|
return _handle_local_file_request(request)
|
||||||
else:
|
else:
|
||||||
|
@ -222,7 +262,7 @@ def handle_request(pathin: str) -> Response:
|
||||||
|
|
||||||
def _extract_internal_request(
|
def _extract_internal_request(
|
||||||
path: str,
|
path: str,
|
||||||
) -> LocalFileRequest | DynamicRequest | NotFound | None:
|
) -> BundledFileRequest | DynamicRequest | NotFound | None:
|
||||||
"Catch /_anki references and rewrite them to web export folder."
|
"Catch /_anki references and rewrite them to web export folder."
|
||||||
prefix = "_anki/"
|
prefix = "_anki/"
|
||||||
if not path.startswith(prefix):
|
if not path.startswith(prefix):
|
||||||
|
@ -237,6 +277,7 @@ def _extract_internal_request(
|
||||||
return _extract_collection_post_request(filename)
|
return _extract_collection_post_request(filename)
|
||||||
elif get_handler := _extract_dynamic_get_request(filename):
|
elif get_handler := _extract_dynamic_get_request(filename):
|
||||||
return get_handler
|
return get_handler
|
||||||
|
|
||||||
# remap legacy top-level references
|
# remap legacy top-level references
|
||||||
base, ext = os.path.splitext(filename)
|
base, ext = os.path.splitext(filename)
|
||||||
if ext == ".css":
|
if ext == ".css":
|
||||||
|
@ -267,7 +308,7 @@ def _extract_internal_request(
|
||||||
path = f"{prefix}{additional_prefix}{base}{ext}"
|
path = f"{prefix}{additional_prefix}{base}{ext}"
|
||||||
print(f"legacy {oldpath} remapped to {path}")
|
print(f"legacy {oldpath} remapped to {path}")
|
||||||
|
|
||||||
return LocalFileRequest(root=_exportFolder, path=path[len(prefix) :])
|
return BundledFileRequest(path=path[len(prefix) :])
|
||||||
|
|
||||||
|
|
||||||
def _extract_addon_request(path: str) -> LocalFileRequest | NotFound | None:
|
def _extract_addon_request(path: str) -> LocalFileRequest | NotFound | None:
|
||||||
|
@ -302,7 +343,9 @@ def _extract_addon_request(path: str) -> LocalFileRequest | NotFound | None:
|
||||||
return NotFound(message=f"couldn't locate item in add-on folder {path}")
|
return NotFound(message=f"couldn't locate item in add-on folder {path}")
|
||||||
|
|
||||||
|
|
||||||
def _extract_request(path: str) -> LocalFileRequest | DynamicRequest | NotFound:
|
def _extract_request(
|
||||||
|
path: str,
|
||||||
|
) -> LocalFileRequest | BundledFileRequest | DynamicRequest | NotFound:
|
||||||
if internal := _extract_internal_request(path):
|
if internal := _extract_internal_request(path):
|
||||||
return internal
|
return internal
|
||||||
elif addon := _extract_addon_request(path):
|
elif addon := _extract_addon_request(path):
|
||||||
|
|
Loading…
Reference in a new issue