import socket
import sys
from argparse import ArgumentParser
+from base64 import urlsafe_b64encode
+from hashlib import pbkdf2_hmac
from itertools import chain
from pathlib import Path
from secrets import token_urlsafe
self.base_dir = Path(__file__).parents[1] / "webroot"
self.env = Environment(
loader=FileSystemLoader(self.base_dir),
- autoescape=select_autoescape,
+ autoescape=select_autoescape(),
extensions=["hub.utils.StaticTag"],
)
self.secret = secret or token_urlsafe(64)
HubApp(self, base_dir)
self.add_error_handler(Exception, self.print_exception)
- def get_hubapp_by_name(self, name):
- return self.hubapps[name]
+ def scramble(self, value):
+ if isinstance(value, str):
+ value = value.encode()
+ secret = self.secret
+ if isinstance(secret, str):
+ secret = secret.encode()
+ return urlsafe_b64encode(
+ pbkdf2_hmac("sha512", value, secret, 221100)
+ ).rstrip(b"=").decode("ascii")
async def print_exception(self, req, resp, ex, params):
print_exception(*sys.exc_info())
from falcon.constants import MEDIA_HTML, MEDIA_JS, MEDIA_TEXT
from falcon.status_codes import HTTP_OK
+from jinja2 import Template
from .websocket import WebSocketHub
-from .utils import scramble
MEDIA_CSS = "text/css"
-
class StaticFile:
media_types_per_suffix = {
".html": MEDIA_HTML,
".css": MEDIA_CSS,
".txt": MEDIA_TEXT,
}
+ content: str | None
def __init__(self, path):
self.path = path
class StaticTemplateFile(StaticFile):
TEMPLATE_SUFFIX = ".j2"
+ content: Template | None
def __init__(self, path, hubapp):
super().__init__(path)
self.hubapp = hubapp
self.context = {"hubapp": self.hubapp}
- self.template = hubapp.app.env.get_template(
- str(path.relative_to(hubapp.app.env.loader.searchpath[0]))
- )
- def get(self):
- return self.template.render(self.context)
+ def get(self) -> str:
+ mtime = self.path.stat().st_mtime
+ if mtime != self.mtime:
+ env = self.hubapp.app.env
+ self.content = env.get_template(
+ str(self.path.relative_to(env.loader.searchpath[0]))
+ )
+ self.mtime = mtime
+ return self.content.render(self.context)
@classmethod
def get_media_type(cls, path):
or "master" in uri_tail[:start].split(slash)
)
- def uri_from(self, path):
- if isinstance(path, Path) and path.is_absolute():
- uri_tail = str(path.relative_to(self.base_dir))
+ def _uri_tail(self, path, suffix):
+ if isinstance(path, Path):
+ if path.is_absolute():
+ path = path.relative_to(self.base_dir)
+ uri_tail = str(path)
else:
uri_tail = str(path)
- suffix = StaticTemplateFile.TEMPLATE_SUFFIX
if uri_tail.endswith(suffix):
uri_tail = uri_tail[:-len(suffix)]
if self.is_master_uri(uri_tail):
- uri_tail = scramble(self.app.secret, uri_tail)
+ return self.app.scramble(uri_tail)
elif uri_tail == "index.html":
- uri_tail = ""
+ return ""
+ return uri_tail
+
+ def uri_from(self, path) -> str | Path:
+ uri_tail = self._uri_tail(path, StaticTemplateFile.TEMPLATE_SUFFIX)
name = self.name
if name == "root":
name = ""
- if name and uri_tail:
- name = f"{name}/"
- return f"/{name}{uri_tail}"
+ return f"/{name}{'/' if name and uri_tail else ''}{uri_tail}"
def scan_files(self, base_dir):
stack = [base_dir.iterdir()]
-from base64 import urlsafe_b64encode
-from hashlib import pbkdf2_hmac
from pathlib import Path
from jinja2_simple_tags import StandaloneTag
if not hubapp:
hubapp = self.context["hubapp"]
elif isinstance(hubapp, str):
- hubapp = self.context["hubapp"].app.get_hubapp_by_name(hubapp)
+ hubapp = self.context["hubapp"].app.hubapps[hubapp]
return hubapp.uri_from(Path(filename))
-def scramble(secret, value):
- if isinstance(value, str):
- value = value.encode()
- if isinstance(secret, str):
- secret = secret.encode()
- return urlsafe_b64encode(
- pbkdf2_hmac("sha512", value, secret, 221100)
- ).rstrip(b"=").decode("ascii")
-
-
def get_redis_pass(redis_conf):
prefix = "requirepass "
with open(redis_conf, "rt") as fh:
class BaseWebSocketHub:
- first = True
client_ids_sem = asyncio.Semaphore(0)
@classmethod
- def __class_init(cls, redis):
- if not cls.first:
+ def _class_init(cls, redis):
+ if not hasattr(cls, "_class_init"):
return
- cls.first = False
+ delattr(cls, "_class_init")
asyncio.create_task(cls.initialize_client_ids(redis))
@classmethod
def __init__(self):
self.redis = StrictRedis(password=get_redis_pass("/etc/redis/redis.conf"))
- self.__class_init(self.redis)
+ if hasattr(BaseWebSocketHub, "_class_init"):
+ BaseWebSocketHub._class_init(self.redis)
def task_done(self):
self.task = None
anyio==4.2.0
-black==23.12.1
+async-timeout==4.0.3
+black==24.2.0
click==8.1.7
falcon==3.1.3
h11==0.14.0
httptools==0.6.1
idna==3.6
isort==5.13.2
-Jinja2==3.1.2
+Jinja2==3.1.3
jinja2-simple-tags==0.5.0
MarkupSafe==2.1.3
mypy-extensions==1.0.0
packaging==23.2
pathspec==0.12.1
platformdirs==4.1.0
-python-dotenv==1.0.0
+python-dotenv==1.0.1
PyYAML==6.0.1
-redis==5.0.1
+redis==5.0.2
sniffio==1.3.0
-uvicorn==0.25.0
+uvicorn==0.27.1
uvloop==0.19.0
watchfiles==0.21.0
websockets==12.0