import glob import logging import os import pathlib import sys import flask from flask_cors import CORS import lxml.etree as xmltree log = logging.getLogger() pipe_stderr = logging.StreamHandler(sys.stderr) pipe_stderr.setLevel(logging.DEBUG) log.addHandler(pipe_stderr) if getattr(sys, "frozen", False): path_appdir = pathlib.Path(sys._MEIPASS) else: path_appdir = pathlib.Path.cwd() log.critical(path_appdir) ## \internal # \brief Signals whether we are a desktop application (as opposed to a Docker container). desktop_mode = False from . import gamedata ## \internal # \brief The Flask instance. See Flask documentation. app = flask.Flask("seagull-game", root_path=path_appdir, template_folder="templates", static_folder="static") CORS(app) orig_url_for = app.url_for xml_namespaces = { "rule": "seagull:rules", "items": "seagull:rules/items", "upgrades": "seagull:rules/upgrades" } #REDIS_HOST="stub-implementation.example.net" #REDIS_PORT=6379 #REDIS_USER="seagull" #REDIS_PASS="i am not a real password" #state_cache = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, username=REDIS_USER, password=REDIS_PASS) # ham5 standing by... ## \internal # \brief Returns CDN URLs for files not present within the container itself, or copies VFS data out so Flask etc can use it. def url_for_override(endpoint, *posargs, _anchor=None, _method=None, _scheme=None, _external=None, self=app, **values): if endpoint == "static": if not gamedata.vfs.exists(f"static/{values["filename"]}"): log.warning("requested {0} from local file, but it doesn't exist in this container. Redirecting to CDN...\n".format(values["filename"])) return "https://cdn.otl-hga.net/seagull/" + values["filename"] else: gamedata.vfs.copy_out(f"static/{values["filename"]}", dest=path_appdir.as_posix()) return orig_url_for(endpoint, *posargs, _anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external, **values) else: print(endpoint) return orig_url_for(endpoint, *posargs, _anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external, **values) app.url_for = url_for_override schemas = [] pth_common_rule_schema = path_appdir / "basepak/rules/schemas/rules.xsd" doc_common_rule_schema = xmltree.parse(pth_common_rule_schema.as_posix()) common_rule_schema = xmltree.XMLSchema(doc_common_rule_schema) ## \brief Validates all XML files in gamedata. # \internal # # This is typically run on program startup. def validate_xml_files(): for schema in schemas: for file in gamedata.vfs.vfs.glob(schema[1]): pth_xml = file.path[1:] fd_xml = gamedata.vfs.open(pth_xml) doc = xmltree.parse(fd_xml) if not schema[0].validate(doc): log.error(f"Bogus XML document: {pth_xml}") ## \internal # \brief Base Flask rendering context. Generated with render_base_context(). base_context = {} base_context_live = False ## \brief Renders a dialog template and sends it to the client. # \param dialog The dialog to render. # \api{GET} /dialog/`` @app.route("/dialog/") def render_dialog(dialog): if gamedata.vfs.exists(f"templates/{dialog}.j2"): gamedata.vfs.copy_out(f"templates/{dialog}.j2", dest=path_appdir.as_posix()) if gamedata.vfs.exists(f"static/js/dlg-{dialog}.js"): gamedata.vfs.copy_out(f"static/js/dlg-{dialog}.js", dest=path_appdir.as_posix()) return flask.render_template(f"{dialog}.j2") else: return "", 404 ## \brief Prepares the base rendering context for Flask to serve our content. def render_base_context(): global base_context global base_context_live base_context["svchost"] = flask.request.host domain_components = flask.request.host.split(".") base_domain = ".".join(domain_components[-2:]) gamedata.vfs.copy_dir("static", dest=path_appdir.as_posix()) # all this wind up for... if base_domain == "otl-hga.net": # production, use assets from S3 base_context["styles"] = ["https://cdn.otl-hga.net/seagull/css/seagull.css"] base_context["scripts"] = ["https://cdn.otl-hga.net/seagull/js/seagull.js", "https://cdn.otl-hga.net/seagull/js/konami.js"] base_context["seagull_pic"] = "https://cdn.otl-hga.net/seagull/image/seagull.jpg" else: # dev, serve files from here #print(base_domain) base_context["styles"] = [app.url_for("static", filename="css/seagull.css")] base_context["scripts"] = [(app.url_for("static", filename="js/konami.js"), True), (app.url_for("static", filename="js/seagull.js"), True)] base_context["seagull_pic"] = app.url_for("static", filename="image/seagull.jpg") base_context_live = True ## \brief Returns OK. Useful for health checks. # \api{GET} /core/ping @app.route("/core/ping") def healthcheck_ping(): return flask.Response("OK", content_type="text/plain") ## \brief Informs the game we're about to request a file from JavaScript. # \internal # \api{POST} /core/ready_file # \apidata Plaintext path to the intended file. @app.route("/core/ready_file", methods=["POST"]) def ready_file(): gamedata.vfs.copy_out(flask.request.data) return flask.Response("OK", content_type="text/plain")