summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleš Smodiš <aless@guru.si>2015-10-04 19:43:09 +0200
committerAleš Smodiš <aless@guru.si>2015-10-04 19:43:09 +0200
commit361f8245079b625560449324faf111ed6fcf3b1b (patch)
treea484991b6e3456545d6d5437a17201ab0a8e7b8a
parent8c61a1980c973c649a2d9b3b9da67f2a680f4139 (diff)
Unification of language session implementations. Added load_problem and end_problem actions so handlers get appropriately created and destroyed upon loading and unloading the problem solving screen.
-rw-r--r--daemon.py3
-rw-r--r--server/__init__.py32
-rw-r--r--server/handlers.py73
-rw-r--r--server/prolog_session.py8
-rw-r--r--server/python_session.py16
-rw-r--r--server/robot_session.py15
-rw-r--r--server/user_session.py85
-rw-r--r--web/main.js20
8 files changed, 149 insertions, 103 deletions
diff --git a/daemon.py b/daemon.py
index bc8df3e..8a03d4c 100644
--- a/daemon.py
+++ b/daemon.py
@@ -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) {