diff options
-rw-r--r-- | daemon.py | 3 | ||||
-rw-r--r-- | server/__init__.py | 32 | ||||
-rw-r--r-- | server/handlers.py | 73 | ||||
-rw-r--r-- | server/prolog_session.py | 8 | ||||
-rw-r--r-- | server/python_session.py | 16 | ||||
-rw-r--r-- | server/robot_session.py | 15 | ||||
-rw-r--r-- | server/user_session.py | 85 | ||||
-rw-r--r-- | web/main.js | 20 |
8 files changed, 149 insertions, 103 deletions
@@ -4,5 +4,6 @@ if __name__ == '__main__': # import server # server.start() - import server.socket + import server +# import server.socket server.socket.serve_forever() diff --git a/server/__init__.py b/server/__init__.py index 526c6f9..e174626 100644 --- a/server/__init__.py +++ b/server/__init__.py @@ -1,10 +1,30 @@ # coding=utf-8 -from . import user_session -from . import prolog_session -from . import python_session -from . import robot_session -from . import socket +__all__ = ['socket', 'handlers', 'user_session', 'prolog_session', 'python_session', 'robot_session', 'problems', 'LanguageSession'] + +# language session handlers are added as their modules are loaded and processed +language_session_handlers = {} + +# the base class for all language session handlers +class LanguageSession(object): + + def destroy(self): + pass + + def hint(self, sid, problem_id, program): + pass + + def test(self, sid, problem_id, program): + pass + +# these imports must be made after LanguageSession is defined, otherwise they won't succeed +# the order of imports is important! first language sessions, then user_session + +import server.prolog_session +import server.python_session +import server.robot_session +import server.user_session +import server.socket import server.problems +import server.handlers -__all__ = ['socket', 'handlers', 'user_session', 'prolog_session', 'python_session', 'robot_session', 'problems', 'start'] diff --git a/server/handlers.py b/server/handlers.py index 6720cae..99b021f 100644 --- a/server/handlers.py +++ b/server/handlers.py @@ -99,9 +99,13 @@ class Query(CodeqService): else: session = request.session trace = js.get('trace') - prolog = session.get_prolog() program = None - if step == 'run': + prolog = session.current_language_session() + if prolog is None: + result = {'code': 6, 'message': 'No language session is active'} + elif not isinstance(prolog, server.prolog_session.PrologSession): + result = {'code': 7, 'message': 'The currently active session is not Prolog'} + elif step == 'run': program = js.get('program') query = js.get('query') if program is None: @@ -115,7 +119,7 @@ class Query(CodeqService): messages, status, have_more = prolog.step() result = {'code': 0, 'message': 'ok', 'terminal': {'messages': messages, 'status': status, 'have_more': have_more}} elif step == 'end': - messages, status, have_more = prolog.end() + messages, status, have_more = prolog.destroy() result = {'code': 0, 'message': 'ok', 'terminal': {'messages': messages, 'status': status, 'have_more': have_more}} else: result = {'code': 5, 'message': 'Unknown prolog step: {0}'.format(step)} @@ -128,10 +132,12 @@ class Query(CodeqService): class PythonExec(CodeqService): def process(self, request): program = request.data.get('program') + python = request.session.current_language_session() if program is None: request.reply({'code': 1, 'message': 'No program specified'}) + elif not isinstance(python, server.python_session.PythonSession): + request.reply({'code': 2, 'message': 'The currently active session is not Python'}) else: - python = request.session.get_python() python.exec(program) request.reply({'code': 0, 'message': 'ok'}) @@ -139,19 +145,24 @@ class PythonExec(CodeqService): # Send an interrupt to the Python interpreter. class PythonStop(CodeqService): def process(self, request): - python = request.session.get_python() - python.stop() - request.reply({'code': 0, 'message': 'ok'}) + python = request.session.current_language_session() + if not isinstance(python, server.python_session.PythonSession): + request.reply({'code': 2, 'message': 'The currently active session is not Python'}) + else: + python.stop() + request.reply({'code': 0, 'message': 'ok'}) # Push stdin to the Python interpreter. class PythonPush(CodeqService): def process(self, request): text = request.data.get('text') + python = request.session.current_language_session() if text is None: request.reply({'code': 1, 'message': 'No input specified'}) + elif not isinstance(python, server.python_session.PythonSession): + request.reply({'code': 2, 'message': 'The currently active session is not Python'}) else: - python = request.session.get_python() python.push(text) request.reply({'code': 0, 'message': 'ok'}) @@ -159,7 +170,7 @@ class PythonPush(CodeqService): class Hint(CodeqService): def process(self, request): js = request.data - language = js.get('language') + language = js.get('language') # TODO: remove problem_id = js.get('problem_id') program = js.get('program') @@ -169,16 +180,9 @@ class Hint(CodeqService): request.reply({'code': 2, 'message': 'No program specified'}) else: session = request.session - lang_session = None - if language == 'prolog': - lang_session = session.get_prolog() - elif language == 'python': - lang_session = session.get_python() - elif language == 'robot': - lang_session = session.get_robot() - + lang_session = session.current_language_session() if lang_session is None: - request.reply({'code': 3, 'message': 'Unknown language specified'}) + request.reply({'code': 3, 'message': 'No active session exists'}) else: hints = lang_session.hint(session.get_sid(), problem_id, program) request.reply({'code': 0, 'message': 'ok', 'hints': hints}) @@ -187,7 +191,7 @@ class Hint(CodeqService): class Test(CodeqService): def process(self, request): js = request.data - language = js.get('language') + language = js.get('language') # TODO: remove problem_id = js.get('problem_id') program = js.get('program') @@ -197,16 +201,9 @@ class Test(CodeqService): request.reply({'code': 2, 'message': 'No program specified'}) else: session = request.session - lang_session = None - if language == 'prolog': - lang_session = session.get_prolog() - elif language == 'python': - lang_session = session.get_python() - elif language == 'robot': - lang_session = session.get_robot() - + lang_session = session.current_language_session() if lang_session is None: - request.reply({'code': 3, 'message': 'Unknown language specified'}) + request.reply({'code': 3, 'message': 'No active session exists'}) else: hints = lang_session.test(session.get_sid(), problem_id, program) request.reply({'code': 0, 'message': 'ok', 'hints': hints}) @@ -227,6 +224,22 @@ class GetProblem(CodeqService): else: request.reply({'code': 0, 'message': 'ok', 'data': request.session.get_problem_data(language, problem_group, problem)}) +class LoadProblem(CodeqService): + def process(self, request): + problem_id = request.data.get('problem_id') + if problem_id is None: + request.reply({'code': 1, 'message': 'There is no active session'}) + else: + session = request.session.load_language_session(problem_id) + if session is None: + request.reply({'code': 2, 'message': 'The session failed to load'}) + else: + request.reply({'code': 0, 'message': 'OK'}) + +class EndProblem(CodeqService): + def process(self, request): + request.session.end_language_session() + request.end() # maps actions to their handlers incoming_handlers = { @@ -241,7 +254,9 @@ incoming_handlers = { 'python_stop': PythonStop(), 'hint': Hint(), 'settings': Settings(), - 'test': Test() + 'test': Test(), + 'load_problem': LoadProblem(), + 'end_problem': EndProblem() } diff --git a/server/prolog_session.py b/server/prolog_session.py index ebd53fb..3de861c 100644 --- a/server/prolog_session.py +++ b/server/prolog_session.py @@ -3,6 +3,7 @@ import operator import threading import prolog.engine +import server import server.user_session from db.models import CodeqUser, Problem from . import problems @@ -31,7 +32,7 @@ def format_prolog_output(reply, output): return messages, 'ok', False # TODO: is it possible to reach this return statement? -class PrologSession(object): +class PrologSession(server.LanguageSession): """Abstracts a Prolog session. Only public methods are available to the outside world due to the use of multiprocessing managers. Therefore prefix any private methods with an underscore (_). @@ -75,7 +76,7 @@ class PrologSession(object): except Exception as e: return [str(e)], 'error', False - def end(self): + def destroy(self): # this method was previously named: end() """Stops the Prolog engine.""" with self._access_lock: if self._engine_id is not None: @@ -158,3 +159,6 @@ class PrologSession(object): def get_problem_id(self): return self._problem_id + +server.language_session_handlers['prolog'] = lambda user_session, problem_id, language_identifier, group_identifier, problem_identifier: PrologSession() + diff --git a/server/python_session.py b/server/python_session.py index 55a4812..91ce1e8 100644 --- a/server/python_session.py +++ b/server/python_session.py @@ -12,13 +12,12 @@ import sys import threading import time -import server.user_session +import server from db.models import Problem -from . import problems __all__ = ['PythonSession'] -class PythonSession(object): +class PythonSession(server.LanguageSession): """Abstracts a Python session. Only public methods are available to the outside world due to the use of multiprocessing managers. Therefore prefix any private methods with an underscore (_). @@ -73,8 +72,8 @@ class PythonSession(object): def hint(self, sid, problem_id, program): language, problem_group, problem = Problem.get_identifier(problem_id) - language_module = problems.load_language(language, 'common') - problem_module = problems.load_problem(language, problem_group, problem, 'common') + language_module = server.problems.load_language(language, 'common') + problem_module = server.problems.load_problem(language, problem_group, problem, 'common') hints = [] if hasattr(language_module, 'hint'): @@ -89,8 +88,8 @@ class PythonSession(object): def test(self, sid, problem_id, program): language, problem_group, problem = Problem.get_identifier(problem_id) - language_module = problems.load_language(language, 'common') - problem_module = problems.load_problem(language, problem_group, problem, 'common') + language_module = server.problems.load_language(language, 'common') + problem_module = server.problems.load_problem(language, problem_group, problem, 'common') try: passed, hints = problem_module.test(self.run, program) @@ -209,3 +208,6 @@ def _run_exec(conn, code, expr=None, stdin=''): sys.stdout.close() sys.stderr.close() conn.send((result, out, err, exc)) + +server.language_session_handlers['python'] = lambda user_session, problem_id, language_identifier, group_identifier, problem_identifier: PythonSession(lambda text: user_session.send({'event': 'terminal_output', 'text': text})) + diff --git a/server/robot_session.py b/server/robot_session.py index e941641..b41e233 100644 --- a/server/robot_session.py +++ b/server/robot_session.py @@ -3,11 +3,11 @@ import threading from db.models import Problem -from . import problems +import server __all__ = ['RobotSession'] -class RobotSession(object): +class RobotSession(server.LanguageSession): """Abstracts a Robot session. Only public methods are available to the outside world due to the use of multiprocessing managers. Therefore prefix any private methods with an underscore (_). @@ -26,8 +26,8 @@ class RobotSession(object): def hint(self, sid, problem_id, program): language, problem_group, problem = Problem.get_identifier(problem_id) - language_module = problems.load_language(language, 'common') - problem_module = problems.load_problem(language, problem_group, problem, 'common') + language_module = server.problems.load_language(language, 'common') + problem_module = server.problems.load_problem(language, problem_group, problem, 'common') hints = [] if hasattr(language_module, 'hint'): @@ -42,8 +42,8 @@ class RobotSession(object): def test(self, sid, problem_id, program): language, problem_group, problem = Problem.get_identifier(problem_id) - language_module = problems.load_language(language, 'common') - problem_module = problems.load_problem(language, problem_group, problem, 'common') + language_module = server.problems.load_language(language, 'common') + problem_module = server.problems.load_problem(language, problem_group, problem, 'common') try: passed, hints = problem_module.test(program) @@ -63,3 +63,6 @@ class RobotSession(object): hint_type = mod.hint_type[hint['id']] hint_type.instantiate(hint, self._sent_hints) self._sent_hints.extend(hints) + +server.language_session_handlers['robot'] = lambda user_session, problem_id, language_identifier, group_identifier, problem_identifier: RobotSession() + diff --git a/server/user_session.py b/server/user_session.py index f53ae57..d80cedd 100644 --- a/server/user_session.py +++ b/server/user_session.py @@ -5,12 +5,8 @@ import threading # multiprocessing.managers.BaseManager uses threading to serve import hashlib import base64 import random -from . import prolog_session -from . import python_session -from . import robot_session -from . import problems -from . import handlers import db +import server from errors.session import NoSuchSession, AuthenticationFailed import psycopg2.extras @@ -32,9 +28,7 @@ class UserSession(object): self.sid = uuid.uuid4().hex self.uid = uid self.username = username - self.prolog_session = None - self.python_session = None - self.robot_session = None + self._lang_session = None self.settings = settings def destroy(self): @@ -42,15 +36,9 @@ class UserSession(object): with self._access_lock: with module_access_lock: del sessions[self.sid] - if self.prolog_session is not None: - self.prolog_session.end() - self.prolog_session = None - if self.python_session is not None: - self.python_session.destroy() - self.python_session = None - if self.robot_session is not None: - self.robot_session.destroy() - self.robot_session = None + if self._lang_session is not None: + self._lang_session.destroy() + self._lang_session = None # TODO: add any cleanups as features are added! def get_sid(self): @@ -77,29 +65,46 @@ class UserSession(object): conn.commit() db.return_connection(conn) - - def get_prolog(self): + def load_language_session(self, problem_id): with self._access_lock: - if self.prolog_session is None: - self.prolog_session = prolog_session.PrologSession() # lazy init - return self.prolog_session + if self._lang_session is not None: + self._lang_session.destroy() + self._lang_session = None + conn = db.get_connection() + try: + cur = conn.cursor() + try: + cur.execute("select l.identifier, g.identifier, p.identifier from problem p inner join language l on l.id = p.language_id inner join problem_group g on g.id = p.problem_group_id where p.id = %s", (problem_id,)) + row = cur.fetchone() + if not row: + return None + language_identifier = row[0] + group_identifier = row[1] + problem_identifier = row[2] + handler = server.language_session_handlers.get(language_identifier) + if not handler: + return None + self._lang_session = handler(self, problem_id, language_identifier, group_identifier, problem_identifier) + return self._lang_session + finally: + cur.close() + finally: + conn.commit() + db.return_connection(conn) - def get_python(self): + def end_language_session(self): with self._access_lock: - if self.python_session is None: - self.python_session = python_session.PythonSession( - output_cb=lambda text: self.send({'event': 'terminal_output', 'text': text})) - return self.python_session + if self._lang_session is not None: + self._lang_session.destroy() + self._lang_session = None - def get_robot(self): + def current_language_session(self): with self._access_lock: - if self.robot_session is None: - self.robot_session = robot_session.RobotSession() - return self.robot_session + return self._lang_session def get_problem_data(self, language, problem_group, problem): - mod = problems.load_problem(language, problem_group, problem, 'sl') - mod_language = problems.load_language(language, 'sl') + mod = server.problems.load_problem(language, problem_group, problem, 'sl') + mod_language = server.problems.load_language(language, 'sl') # Get generic and problem-specific hints. hint = dict(mod_language.hint) @@ -171,19 +176,13 @@ class UserSession(object): :return: None """ json_obj['sid'] = self.sid - handlers.send(None, self.sid, json_obj) + server.handlers.send(None, self.sid, json_obj) def __del__(self): # no locking needed if GC is removing us, as there cannot be any concurrent access by definition - if hasattr(self, 'prolog_session') and (self.prolog_session is not None): - self.prolog_session.end() - self.prolog_session = None - if hasattr(self, 'python_session') and (self.python_session is not None): - self.python_session.destroy() - self.python_session = None - if hasattr(self, 'robot_session') and (self.python_session is not None): - self.robot_session.destroy() - self.robot_session = None + if hasattr(self, '_lang_session') and (self._lang_session is not None): + self._lang_session.destroy() + self._lang_session = None # TODO: add any cleanups as features are added! def get_session_by_id(sid): diff --git a/web/main.js b/web/main.js index 931a550..8eac85c 100644 --- a/web/main.js +++ b/web/main.js @@ -108,15 +108,17 @@ var guiHandlers = { // actions to use default handling should define truthy values that are not functions // (this is to filter out unnecessary traffic before it hits Python) - 'activity': true, - 'query': true, - 'python_exec': true, - 'python_push': true, - 'python_stop': true, - 'hint': true, - 'test': true, - 'get_problem': true, - 'settings': true + 'activity': true, + 'query': true, + 'python_exec': true, + 'python_push': true, + 'python_stop': true, + 'hint': true, + 'test': true, + 'get_problem': true, + 'settings': true, + 'load_problem': true, + 'end_problem': true }; server.on('connection', function (socket) { |