]> git.mar77i.info Git - hublib/blob - hub/app.py
add a bunch of typehints
[hublib] / hub / app.py
1 import socket
2 import sys
3 from argparse import ArgumentParser
4 from base64 import urlsafe_b64encode
5 from hashlib import pbkdf2_hmac
6 from pathlib import Path
7 from secrets import token_urlsafe
8 from subprocess import run
9 from traceback import print_exception
10 from typing import Generator
11 from urllib.parse import urlunsplit
12
13 from falcon.asgi import App as FalconApp
14 from falcon.constants import MEDIA_HTML
15 from jinja2 import Environment, FileSystemLoader, select_autoescape
16 from redis.asyncio import StrictRedis
17 from uvicorn import Config, Server
18
19 from .static import HubApp, TemplateTreeFileApp
20
21
22 class App(TemplateTreeFileApp, FalconApp):
23 @classmethod
24 def scan_files(cls, base_dir: Path) -> Generator[Path]:
25 for path in base_dir.iterdir():
26 if not path.is_dir() and not cls.is_ignored_filename(path):
27 yield path
28
29 def scramble(self, value: str | bytes) -> str:
30 if isinstance(value, str):
31 value = value.encode()
32 secret = self.secret
33 if isinstance(secret, str):
34 secret = secret.encode()
35 return urlsafe_b64encode(
36 pbkdf2_hmac("sha512", value, secret, 221100)
37 ).rstrip(b"=").decode("ascii")
38
39 def uri_tail(self, path: Path) -> str:
40 return self.scramble(super().uri_tail(path))
41
42 def __init__(self, base_dir: Path, secret: str, **kwargs):
43 from .utils import get_redis_pass
44
45 self.secret = secret or token_urlsafe(64)
46 kwargs.setdefault("media_type", MEDIA_HTML)
47 FalconApp.__init__(self, **kwargs)
48 TemplateTreeFileApp.__init__(
49 self, self, base_dir, "/derp", "root"
50 )
51 self.env = Environment(
52 loader=FileSystemLoader(self.base_dir),
53 autoescape=select_autoescape(),
54 extensions=["hub.utils.StaticTag"],
55 )
56 self.hubapps = {}
57 self.conn = StrictRedis(
58 username="default", password=get_redis_pass("/etc/redis/redis.conf")
59 )
60 for base_dir in self.base_dir.iterdir():
61 if base_dir.is_dir() and not self.is_ignored_filename(base_dir):
62 self.hubapps[base_dir.name] = HubApp(
63 self, base_dir, base_uri=f"/derp/{base_dir.name}"
64 )
65 self.add_error_handler(Exception, self.print_exception)
66
67 async def print_exception(self, req, resp, ex, params) -> None:
68 print_exception(*sys.exc_info())
69
70
71 class HubServer(Server):
72 def __init__(self, *args, browser, **kwargs):
73 self.browser = browser
74 super().__init__(*args, **kwargs)
75
76 async def startup(self, sockets: list[socket.socket] | None = None) -> None:
77 await super().startup(sockets)
78 root_app = self.config.loaded_app.app
79 print("Secret:", root_app.secret)
80 for uri, file in root_app.files_per_uris.items():
81 if file.name == "index.html":
82 break
83 else:
84 raise ValueError("Root page not found!")
85 host, port, ssl = self.config.host, self.config.port, bool(self.config.ssl)
86 if port and port != (80, 443)[ssl]:
87 host = f"{host}:{port}"
88 url = urlunsplit((f"http{'s'[:ssl]}", host, uri, "", ""))
89 print("URL:", url)
90 if self.browser:
91 run([self.browser, url])
92
93
94 async def main():
95 ap = ArgumentParser()
96 ap.add_argument("--secret")
97 ap.add_argument("--browser", default="xdg-open")
98 args = ap.parse_args()
99 app = App(Path(__file__).parents[1] / "webroot", args.secret)
100 await app.conn.set("client_id", 0)
101 config = Config(app, port=5000, log_level="info")
102 config.setup_event_loop()
103 hs = HubServer(config, browser=args.browser)
104 await hs.serve()