Commit 4c5146e66cc5c1a6f3584e5a60f98c6aa043ff84

Authored by Miguel Barão
1 parent 7ce13d39
Exists in master and in 1 other branch dev

serving questions now working (except review if not in cache)

perguntations/app.py
@@ -71,12 +71,15 @@ class App(): @@ -71,12 +71,15 @@ class App():
71 71
72 # ------------------------------------------------------------------------ 72 # ------------------------------------------------------------------------
73 def _db_setup(self) -> None: 73 def _db_setup(self) -> None:
74 - logger.debug('Checking database...')  
75 -  
76 - # connect to database and check registered students 74 + '''
  75 + Create database engine and checks for admin and students
  76 + '''
77 dbfile = os.path.expanduser(self._testfactory['database']) 77 dbfile = os.path.expanduser(self._testfactory['database'])
  78 + logger.info('Checking database "%s"...', dbfile)
78 if not os.path.exists(dbfile): 79 if not os.path.exists(dbfile):
79 raise AppException('No database. Use "initdb" to create.') 80 raise AppException('No database. Use "initdb" to create.')
  81 +
  82 + # connect to database and check for admin & registered students
80 self._engine = create_engine(f'sqlite:///{dbfile}', future=True) 83 self._engine = create_engine(f'sqlite:///{dbfile}', future=True)
81 try: 84 try:
82 with Session(self._engine, future=True) as session: 85 with Session(self._engine, future=True) as session:
@@ -92,20 +95,11 @@ class App(): @@ -92,20 +95,11 @@ class App():
92 msg = f'Database "{dbfile}" unusable.' 95 msg = f'Database "{dbfile}" unusable.'
93 logger.error(msg) 96 logger.error(msg)
94 raise AppException(msg) from None 97 raise AppException(msg) from None
95 -  
96 - logger.info('Database "%s" has %d students.', dbfile, len(dbstudents)) 98 + logger.info('Database has %d students.', len(dbstudents))
97 99
98 self._students = {uid: {'name': name, 'state': 'offline', 'test': None} 100 self._students = {uid: {'name': name, 'state': 'offline', 'test': None}
99 for uid, name in dbstudents} 101 for uid, name in dbstudents}
100 102
101 - # self._students = {}  
102 - # for uid, name in dbstudents:  
103 - # self._students[uid] = {  
104 - # 'name': name,  
105 - # 'state': 'offline', # offline, allowed, waiting, online  
106 - # 'test': None  
107 - # }  
108 -  
109 # ------------------------------------------------------------------------ 103 # ------------------------------------------------------------------------
110 async def login(self, uid: str, password: str, headers: dict) -> Optional[str]: 104 async def login(self, uid: str, password: str, headers: dict) -> Optional[str]:
111 ''' 105 '''
@@ -172,7 +166,7 @@ class App(): @@ -172,7 +166,7 @@ class App():
172 try: 166 try:
173 testconf = load_yaml(filename) 167 testconf = load_yaml(filename)
174 testconf['testfile'] = filename 168 testconf['testfile'] = filename
175 - except (IOError, yaml.YAMLError) as exc: 169 + except (OSError, yaml.YAMLError) as exc:
176 msg = f'Cannot read test configuration "{filename}"' 170 msg = f'Cannot read test configuration "{filename}"'
177 logger.error(msg) 171 logger.error(msg)
178 raise AppException(msg) from exc 172 raise AppException(msg) from exc
@@ -534,7 +528,7 @@ class App(): @@ -534,7 +528,7 @@ class App():
534 with open(filename, 'r', encoding='utf-8') as file: 528 with open(filename, 'r', encoding='utf-8') as file:
535 allowed = {line.strip() for line in file} 529 allowed = {line.strip() for line in file}
536 allowed.discard('') 530 allowed.discard('')
537 - except IOError as exc: 531 + except OSError as exc:
538 error_msg = f'Cannot read file {filename}' 532 error_msg = f'Cannot read file {filename}'
539 logger.critical(error_msg) 533 logger.critical(error_msg)
540 raise AppException(error_msg) from exc 534 raise AppException(error_msg) from exc
perguntations/main.py
@@ -71,7 +71,7 @@ def get_logger_config(debug=False) -> dict: @@ -71,7 +71,7 @@ def get_logger_config(debug=False) -> dict:
71 path = os.path.expanduser(os.environ.get('XDG_CONFIG_HOME', '~/.config/')) 71 path = os.path.expanduser(os.environ.get('XDG_CONFIG_HOME', '~/.config/'))
72 try: 72 try:
73 return load_yaml(os.path.join(path, APP_NAME, file)) 73 return load_yaml(os.path.join(path, APP_NAME, file))
74 - except IOError: 74 + except OSError:
75 print('Using default logger configuration...') 75 print('Using default logger configuration...')
76 76
77 if debug: 77 if debug:
perguntations/serve.py
@@ -360,6 +360,7 @@ class FileHandler(BaseHandler): @@ -360,6 +360,7 @@ class FileHandler(BaseHandler):
360 Handles static files from questions like images, etc. 360 Handles static files from questions like images, etc.
361 ''' 361 '''
362 362
  363 + _filecache = {}
363 364
364 @tornado.web.authenticated 365 @tornado.web.authenticated
365 async def get(self): 366 async def get(self):
@@ -371,35 +372,42 @@ class FileHandler(BaseHandler): @@ -371,35 +372,42 @@ class FileHandler(BaseHandler):
371 ref = self.get_query_argument('ref', None) 372 ref = self.get_query_argument('ref', None)
372 image = self.get_query_argument('image', None) 373 image = self.get_query_argument('image', None)
373 logger.debug('GET /file (ref=%s, image=%s)', ref, image) 374 logger.debug('GET /file (ref=%s, image=%s)', ref, image)
  375 +
  376 + if ref is None or image is None:
  377 + return
  378 +
374 content_type = mimetypes.guess_type(image)[0] 379 content_type = mimetypes.guess_type(image)[0]
375 380
376 - if uid != '0':  
377 - test = self.testapp.get_student_test(uid)  
378 - else:  
379 - logger.error('FIXME Cannot serve images for review.')  
380 - raise tornado.web.HTTPError(404) # FIXME admin 381 + if (ref, image) in self._filecache:
  382 + logger.debug('using cached file')
  383 + self.write(self._filecache[(ref, image)])
  384 + if content_type is not None:
  385 + self.set_header("Content-Type", content_type)
  386 + await self.flush()
  387 + return
381 388
382 - if test is None: 389 + try:
  390 + test = self.testapp.get_test(uid)
  391 + except KeyError:
  392 + logger.warning('Could not get test to serve image file')
383 raise tornado.web.HTTPError(404) # Not Found 393 raise tornado.web.HTTPError(404) # Not Found
384 394
385 for question in test['questions']: 395 for question in test['questions']:
386 # search for the question that contains the image 396 # search for the question that contains the image
387 if question['ref'] == ref: 397 if question['ref'] == ref:
388 - filepath = path.join(question['path'], b'public', image) 398 + filepath = path.join(question['path'], 'public', image)
  399 +
389 try: 400 try:
390 - file = open(filepath, 'rb')  
391 - except FileNotFoundError:  
392 - logger.error('File not found: %s', filepath)  
393 - except PermissionError:  
394 - logger.error('No permission: %s', filepath) 401 + with open(filepath, 'rb') as file:
  402 + data = file.read()
395 except OSError: 403 except OSError:
396 - logger.error('Error opening file: %s', filepath)  
397 - else:  
398 - data = file.read()  
399 - file.close() 404 + logger.error('Error reading file "%s"', filepath)
  405 + break
  406 + self._filecache[(ref, image)] = data
  407 + self.write(data)
  408 + if content_type is not None:
400 self.set_header("Content-Type", content_type) 409 self.set_header("Content-Type", content_type)
401 - self.write(data)  
402 - await self.flush() 410 + await self.flush()
403 break 411 break
404 412
405 413
perguntations/testfactory.py
@@ -99,13 +99,6 @@ class TestFactory(dict): @@ -99,13 +99,6 @@ class TestFactory(dict):
99 if question['ref'] in qrefs: 99 if question['ref'] in qrefs:
100 question.update(zip(('path', 'filename', 'index'), 100 question.update(zip(('path', 'filename', 'index'),
101 path.split(fullpath) + (i,))) 101 path.split(fullpath) + (i,)))
102 - # if question['type'] == 'code' and 'server' not in question:  
103 - # try:  
104 - # question['server'] = self['jobe_server']  
105 - # except KeyError as exc:  
106 - # msg = f'Missing JOBE server in "{question["ref"]}"'  
107 - # raise TestFactoryException(msg) from exc  
108 -  
109 self['question_factory'][question['ref']] = QFactory(QDict(question)) 102 self['question_factory'][question['ref']] = QFactory(QDict(question))
110 103
111 qmissing = qrefs.difference(set(self['question_factory'].keys())) 104 qmissing = qrefs.difference(set(self['question_factory'].keys()))
@@ -338,4 +331,4 @@ class TestFactory(dict): @@ -338,4 +331,4 @@ class TestFactory(dict):
338 # ------------------------------------------------------------------------ 331 # ------------------------------------------------------------------------
339 def __repr__(self): 332 def __repr__(self):
340 testsettings = '\n'.join(f' {k:14s}: {v}' for k, v in self.items()) 333 testsettings = '\n'.join(f' {k:14s}: {v}' for k, v in self.items())
341 - return '{\n' + testsettings + '\n}' 334 + return 'TestFactory({\n' + testsettings + '\n})'