]>
git.mar77i.info Git - hublib/blob - hub/static.py
1 from pathlib
import Path
3 from falcon
.constants
import MEDIA_HTML
, MEDIA_JS
, MEDIA_TEXT
4 from falcon
.request
import Request
5 from falcon
.response
import Response
6 from falcon
.status_codes
import HTTP_OK
7 from jinja2
import Template
9 from .websocket
import WebSocketApp
11 MEDIA_CSS
= "text/css"
16 Basic static file wrapper.
18 media_types_per_suffix
: dict[str, str] = {
25 def __init__(self
, path
: Path
):
28 self
.media_type
= self
.get_media_type(path
)
29 self
.mtime
: float |
None = None
30 self
.content
: bytes |
None = None
33 def get_media_type(cls
, path
: Path
):
34 return cls
.media_types_per_suffix
[path
.suffix
]
36 def get(self
) -> bytes:
37 mtime
= self
.path
.stat().st_mtime
38 if mtime
!= self
.mtime
:
39 with open(self
.path
, "rb") as fh
:
40 self
.content
= fh
.read()
45 return f
"<{type(self).__name__} {self.path}>"
50 Map a directory tree base_dir to a base_uri and serve it statically.
51 Map index.html files to their relative roots:
52 index.html to "/" (root uri) to index.html and
53 a/index.html to "/a" (directory uri)
56 def is_ignored_filename(path
):
57 return path
.name
.startswith(".")
60 def scan_files(cls
, base_dir
):
61 stack
= [base_dir
.iterdir()]
64 path
= next(stack
[-1])
69 stack
.append(path
.iterdir())
70 elif not cls
.is_ignored_filename(path
):
73 def get_file(self
, path
: Path
) -> StaticFile
:
74 return StaticFile(path
)
76 def uri_tail(self
, path
: Path
) -> str:
78 Return the "local" path, relative to self.base_dir, if applicable
80 assert isinstance(path
, Path
)
81 if path
.is_absolute():
82 path
= path
.relative_to(self
.base_dir
)
85 def uri(self
, path
: Path
) -> str:
86 uri_tail
= self
.uri_tail(path
)
87 if uri_tail
== "index.html":
88 return self
.base_uri
or "/"
89 index_suffix
= "/index.html"
90 if uri_tail
.endswith(index_suffix
):
91 uri_tail
= uri_tail
[:-len(index_suffix
)]
92 return f
"{self.base_uri}/{uri_tail.lstrip('/')}"
95 def create_name(root_base_uri
, base_uri
):
96 if base_uri
.startswith(root_base_uri
):
97 base_uri
= base_uri
[len(root_base_uri
):]
98 return base_uri
.replace("/", ".").strip(".")
100 def __init__(self
, root
, base_dir
, base_uri
="/", name
=None):
102 self
.base_dir
= base_dir
103 self
.base_uri
= base_uri
.rstrip("/")
104 self
.name
= name
or self
.create_name(self
.root
.base_uri
, self
.base_uri
)
105 assert self
.name
, self
.name
106 self
.files_per_uris
: dict[str, StaticFile
] = {}
107 for path
in self
.scan_files(base_dir
):
108 static_file
= self
.get_file(path
)
109 uri
= self
.uri(static_file
.path
)
110 self
.files_per_uris
[uri
] = static_file
111 root
.add_route(uri
, self
)
113 async def on_get(self
, req
: Request
, resp
: Response
):
114 resource
= self
.files_per_uris
[req
.path
]
115 resp
.content_type
= resource
.media_type
116 resp
.data
= resource
.get()
117 resp
.status
= HTTP_OK
120 class StaticTemplateFile(StaticFile
):
121 TEMPLATE_SUFFIX
= ".j2"
122 content
: Template |
None
124 def __init__(self
, path
: Path
, hubapp
: "TemplateTreeFileApp"):
125 super().__init
__(path
)
126 self
.name
= path
.stem
128 self
.context
= {"static_file": self}
130 def get(self
) -> bytes:
131 mtime
= self
.path
.stat().st_mtime
132 if mtime
!= self
.mtime
:
133 env
= self
.hubapp
.root
.env
134 self
.content
= env
.get_template(
135 str(self
.path
.relative_to(env
.loader
.searchpath
[0]))
138 return self
.content
.render(self
.context
).encode()
141 def get_media_type(cls
, path
: Path
) -> str:
142 assert path
.suffix
== cls
.TEMPLATE_SUFFIX
143 return cls
.media_types_per_suffix
[path
.suffixes
[-2]]
146 class TemplateTreeFileApp(TreeFileApp
):
147 def get_file(self
, path
: Path
) -> StaticFile
:
148 if path
.suffix
== StaticTemplateFile
.TEMPLATE_SUFFIX
:
149 return StaticTemplateFile(path
, self
)
150 return super().get_file(path
)
152 def uri_tail(self
, path
: Path
) -> str:
153 uri_tail
= super().uri_tail(path
)
154 if uri_tail
.endswith(StaticTemplateFile
.TEMPLATE_SUFFIX
):
155 uri_tail
= uri_tail
[:-len(StaticTemplateFile
.TEMPLATE_SUFFIX
)]
159 class HubApp(TemplateTreeFileApp
):
160 def __init__(self
, app
, base_dir
, base_uri
="/"):
161 super().__init
__(app
, base_dir
, base_uri
)
162 self
.ws_app
= WebSocketApp(self
)
163 for suffix
in ("client", "master"):
164 app
.add_route(self
.uri(Path(f
"ws_{suffix}")), self
.ws_app
, suffix
=suffix
)
167 def is_master_uri(path
: Path
) -> bool:
168 assert isinstance(path
, Path
)
170 pos
= basename
.find(".")
172 basename
= basename
[:pos
]
173 return basename
in ("master", "ws_master")
175 def uri(self
, path
: Path
) -> str:
176 uri
= super().uri(path
)
177 if not self
.is_master_uri(path
):
179 pos
= uri
.rstrip("/").rfind("/")
181 scrambled_uri
= self
.root
.scramble(uri
)
182 return f
"{uri[:pos]}/{scrambled_uri}"