Commit 6deb4a4fe31ff9b6b386f6524142926730265bd8
1 parent
ea4ff4d2
Exists in
master
and in
1 other branch
- file support added. Every file in public subdir of some topic is accessible th…
…rough /file/filename. In the question text images can be referred in markdown as `` - files are served asynchronously in chunks of 1MiB.
Showing
4 changed files
with
46 additions
and
36 deletions
Show diff stats
BUGS.md
| ... | ... | @@ -11,7 +11,6 @@ |
| 11 | 11 | |
| 12 | 12 | # TODO |
| 13 | 13 | |
| 14 | -- servir imagens/ficheiros. | |
| 15 | 14 | - session management. close after inactive time. |
| 16 | 15 | - each topic only loads a sample of K questions (max) in random order. |
| 17 | 16 | - radio e checkboxes, aceitar numeros como seleccao das opcoes. |
| ... | ... | @@ -30,6 +29,7 @@ |
| 30 | 29 | |
| 31 | 30 | # FIXED |
| 32 | 31 | |
| 32 | +- servir imagens/ficheiros. | |
| 33 | 33 | - radio: suporte para multiplas opcoes correctas e erradas, escolhendo-se uma selecção aleatoria destas (so com 1 certa). |
| 34 | 34 | - checkbox: cada opção pode ser uma dupla (certo, errado) sendo escolhida uma aleatória. |
| 35 | 35 | - async/threadpool no bcrypt do initdb. | ... | ... |
demo/solar_system/questions.yaml
| 1 | 1 | --- |
| 2 | 2 | |
| 3 | -# # --------------------------------------------------------------------------- | |
| 4 | -# - | |
| 5 | -# ref: solar-system | |
| 6 | -# type: radio | |
| 7 | -# title: Sistema solar | |
| 8 | -# text: Qual é o maior planeta do Sistema Solar? | |
| 9 | -# options: | |
| 10 | -# - Mercúrio | |
| 11 | -# - Marte | |
| 12 | -# - Júpiter | |
| 13 | -# - Têm todos o mesmo tamanho | |
| 14 | -# # opcional | |
| 15 | -# correct: 2 | |
| 16 | -# shuffle: False | |
| 17 | -# discount: True | |
| 3 | +# --------------------------------------------------------------------------- | |
| 4 | +- | |
| 5 | + ref: solar-system | |
| 6 | + type: radio | |
| 7 | + title: Sistema solar | |
| 8 | + text: | | |
| 9 | +  | |
| 10 | + | |
| 11 | + Qual é o maior planeta do Sistema Solar? | |
| 12 | + options: | |
| 13 | + - Mercúrio | |
| 14 | + - Marte | |
| 15 | + - Júpiter | |
| 16 | + - Têm todos o mesmo tamanho | |
| 17 | + # opcional | |
| 18 | + correct: 2 | |
| 19 | + shuffle: False | |
| 20 | + discount: True | |
| 18 | 21 | |
| 19 | 22 | # # --------------------------------------------------------------------------- |
| 20 | 23 | # - | ... | ... |
serve.py
| ... | ... | @@ -30,8 +30,8 @@ class WebApplication(tornado.web.Application): |
| 30 | 30 | (r'/change_password', ChangePasswordHandler), |
| 31 | 31 | (r'/question', QuestionHandler), # each question |
| 32 | 32 | (r'/topic/(.+)', TopicHandler), # page for doing a topic |
| 33 | - # (r'/file/(.+)', FileHandler), # FIXME | |
| 34 | - (r'/.*', RootHandler), # show list of topics | |
| 33 | + (r'/file/(.+)', FileHandler), # FIXME | |
| 34 | + (r'/', RootHandler), # show list of topics | |
| 35 | 35 | ] |
| 36 | 36 | settings = { |
| 37 | 37 | 'template_path': path.join(path.dirname(__file__), 'templates'), |
| ... | ... | @@ -143,20 +143,36 @@ class TopicHandler(BaseHandler): |
| 143 | 143 | self.redirect('/') |
| 144 | 144 | |
| 145 | 145 | # ---------------------------------------------------------------------------- |
| 146 | -# FIXME | |
| 146 | +# Based on https://bhch.github.io/posts/2017/12/serving-large-files-with-tornado-safely-without-blocking/ | |
| 147 | 147 | class FileHandler(BaseHandler): |
| 148 | 148 | @tornado.web.authenticated |
| 149 | - def get(self, filename): | |
| 149 | + async def get(self, filename): | |
| 150 | 150 | uid = self.current_user |
| 151 | 151 | public_dir = self.learn.get_current_public_dir(uid) |
| 152 | 152 | filepath = path.expanduser(path.join(public_dir, filename)) |
| 153 | + chunk_size = 1024 * 1024 # serve 1MiB multiple times | |
| 153 | 154 | try: |
| 154 | 155 | f = open(filepath, 'rb') |
| 155 | 156 | except FileNotFoundError: |
| 156 | - raise tornado.web.HTTPError(404) | |
| 157 | + logging.error(f'File not found: {filepath}') | |
| 158 | + except PermissionError: | |
| 159 | + logging.error(f'No permission: {filepath}') | |
| 157 | 160 | else: |
| 158 | - self.write(f.read()) | |
| 159 | - f.close() | |
| 161 | + with f: | |
| 162 | + while True: | |
| 163 | + chunk = f.read(chunk_size) | |
| 164 | + if not chunk: break | |
| 165 | + try: | |
| 166 | + self.write(chunk) # write the cunk to response | |
| 167 | + await self.flush() # flush the current chunk to socket | |
| 168 | + except iostream.StreamClosedError: | |
| 169 | + # client closed the connection | |
| 170 | + break | |
| 171 | + finally: | |
| 172 | + del chunk | |
| 173 | + await gen.sleep(0.000000001) # 1 nanosecond (hack) | |
| 174 | + # in tornnado 5.0 use `await asyncio.sleep(0)` instead | |
| 175 | + | |
| 160 | 176 | |
| 161 | 177 | # ---------------------------------------------------------------------------- |
| 162 | 178 | # respond to AJAX to get a JSON question | ... | ... |
tools.py
| ... | ... | @@ -99,19 +99,10 @@ class HighlightRenderer(mistune.Renderer): |
| 99 | 99 | def table(self, header, body): |
| 100 | 100 | return '<table class="table table-sm"><thead class="thead-light">' + header + '</thead><tbody>' + body + "</tbody></table>" |
| 101 | 101 | |
| 102 | - # def image(self, src, title, text): | |
| 103 | - # if src.startswith('javascript:'): | |
| 104 | - # src = '' | |
| 105 | - # text = mistune.escape(text, quote=True) | |
| 106 | - # if title: | |
| 107 | - # title = mistune.escape(title, quote=True) | |
| 108 | - # html = '<img class="img-responsive center-block" src="%s" alt="%s" title="%s"' % (src, text, title) | |
| 109 | - # else: | |
| 110 | - # html = '<img class="img-responsive center-block" src="%s" alt="%s"' % (src, text) | |
| 111 | - # if self.options.get('use_xhtml'): | |
| 112 | - # return '%s />' % html | |
| 113 | - # return '%s>' % html | |
| 114 | - | |
| 102 | + def image(self, src, title, alt): | |
| 103 | + alt = mistune.escape(alt, quote=True) | |
| 104 | + title = mistune.escape(title or '', quote=True) | |
| 105 | + return f'<img src="/file/{src}" class="img-fluid mx-auto d-block" alt="{alt}" title="{title}">' | |
| 115 | 106 | |
| 116 | 107 | # Pass math through unaltered - mathjax does the rendering in the browser |
| 117 | 108 | def block_math(self, text): | ... | ... |