From 88a5cd83b47a9dfb5a832936095c7b99ce0d8179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Smodi=C5=A1?= Date: Tue, 25 Aug 2015 14:20:42 +0200 Subject: Implemented methods to fetch a list of available problems and the problem description. JavaScript no longer parses pythonic problem descriptions, instead they are loaded by server and JSONized. --- client/__init__.py | 22 +++++--- server/__init__.py | 30 +++++++++-- server/problems.py | 28 ++++++++++- server/prolog_session.py | 8 +-- server/user_session.py | 28 +++++++++++ wsgi_server.py | 128 +++++++++++++++++++++++++++++++---------------- 6 files changed, 188 insertions(+), 56 deletions(-) diff --git a/client/__init__.py b/client/__init__.py index 8781a96..1ac18cd 100644 --- a/client/__init__.py +++ b/client/__init__.py @@ -9,18 +9,28 @@ class CodeqManager(multiprocessing.managers.BaseManager): CodeqManager.register('PrologSession') CodeqManager.register('UserSession') -CodeqManager.register('get_session_by_id') -CodeqManager.register('get_or_create_session') -CodeqManager.register('authenticate_and_create_session') +#CodeqManager.register('get_session_by_id') +#CodeqManager.register('get_or_create_session') +#CodeqManager.register('authenticate_and_create_session') +#CodeqManager.register('list_problems_in_groups') +CodeqManager.register('Codeq') m = CodeqManager(address=('localhost', 16231), authkey=b'c0d3q3y') m.connect() +codeq = m.Codeq() def get_session_by_id(sid): - return m.get_session_by_id(sid) +# return m.get_session_by_id(sid) + return codeq.get_session_by_id(sid) def get_or_create_session(uid, username, sid=None): - return m.get_or_create_session(uid, username, sid) +# return m.get_or_create_session(uid, username, sid) + return codeq.get_or_create_session(uid, username, sid) def authenticate_and_create_session(username, password): - return m.authenticate_and_create_session(username, password) +# return m.authenticate_and_create_session(username, password) + return codeq.authenticate_and_create_session(username, password) + +def list_problems_in_groups(language): +# return m.list_problems_in_groups(language) + return codeq.list_problems_in_groups(language) diff --git a/server/__init__.py b/server/__init__.py index f2c73b7..9f2469d 100644 --- a/server/__init__.py +++ b/server/__init__.py @@ -3,8 +3,28 @@ import multiprocessing.managers from . import user_session from . import prolog_session +import server.problems -__all__ = ['user_session'] +__all__ = ['user_session', 'prolog_session', 'problems', 'start'] + +class Codeq(object): + _method_to_typeid_ = { + 'get_session_by_id': 'UserSession', + 'get_or_create_session': 'UserSession', + 'authenticate_and_create_session': 'UserSession' + } + + def get_session_by_id(self, sid): + return user_session.get_session_by_id(sid) + + def get_or_create_session(self, uid, username, sid=None): + return user_session.get_or_create_session(uid, username, sid) + + def authenticate_and_create_session(self, username, password): + return user_session.authenticate_and_create_session(username, password) + + def list_problems_in_groups(self, language): + return server.problems.list_problems_in_groups(language) class CodeqManager(multiprocessing.managers.BaseManager): pass @@ -14,9 +34,11 @@ class UserSessionProxy(multiprocessing.managers.BaseProxy): CodeqManager.register('PrologSession') CodeqManager.register('UserSession', proxytype=UserSessionProxy) -CodeqManager.register('get_session_by_id', callable=user_session.get_session_by_id, proxytype=UserSessionProxy) -CodeqManager.register('get_or_create_session', callable=user_session.get_or_create_session, proxytype=UserSessionProxy) -CodeqManager.register('authenticate_and_create_session', callable=user_session.authenticate_and_create_session, proxytype=UserSessionProxy) +#CodeqManager.register('get_session_by_id', callable=user_session.get_session_by_id, proxytype=UserSessionProxy) +#CodeqManager.register('get_or_create_session', callable=user_session.get_or_create_session, proxytype=UserSessionProxy) +#CodeqManager.register('authenticate_and_create_session', callable=user_session.authenticate_and_create_session, proxytype=UserSessionProxy) +#CodeqManager.register('list_problems_in_groups', callable=server.problems.list_problems_in_groups) +CodeqManager.register('Codeq', callable=Codeq, method_to_typeid=Codeq._method_to_typeid_) def start(): m = CodeqManager(address=('localhost', 16231), authkey=b'c0d3q3y') diff --git a/server/problems.py b/server/problems.py index 7fb2606..bce72fe 100644 --- a/server/problems.py +++ b/server/problems.py @@ -3,6 +3,7 @@ import sys import importlib.machinery import threading +from db import get_connection, return_connection #sys.path.append('/home/aless/job/codeq/source/codeq-problems/') _path_prefix = '/home/aless/job/codeq/source/codeq-problems/' @@ -45,7 +46,6 @@ def load_problems(language, tuples, tail_module): modules = [] for problem_group, problem in tuples: mod = '{0}.problems.{1}.{2}.{3}'.format(language, problem_group, problem, tail_module) - print('importing {}'.format(mod)) modules.append(load_module(mod)) return modules @@ -81,3 +81,29 @@ def solutions_for_problems(language, tuples): if f: facts.add(f) return '\n'.join(facts) + '\n' + '\n'.join(solutions) + +def list_problems_in_groups(language): + conn = get_connection() + try: + cur = conn.cursor() + try: + cur.arraysize = 1000 + cur.execute("select g.identifier, g.name, p.identifier, p.name from problem p inner join problem_group g on g.id = p.problem_group_id where p.language_id = (select id from language where identifier = %s) order by g.identifier, p.identifier", (language,)) + result = [] + previous_group_name = '' + current_sublist = None + row = cur.fetchone() + while row: + current_group_name = row[0] + if previous_group_name != current_group_name: + current_sublist = [] + result.append({'identifier': current_group_name, 'name': row[1], 'problems': current_sublist}) + previous_group_name = current_group_name + current_sublist.append({'identifier': row[2], 'name': row[3]}) + row = cur.fetchone() + return result + finally: + cur.close() + finally: + conn.commit() + return_connection(conn) diff --git a/server/prolog_session.py b/server/prolog_session.py index e00afd8..c21d929 100644 --- a/server/prolog_session.py +++ b/server/prolog_session.py @@ -86,7 +86,7 @@ class PrologSession(object): prolog.engine.stop(self._engine_id) self._engine_id = None - def run_for_user(self, user_id, language, problem_group, problem, program, query): + def run_for_user(self, user_id, problem_id, program, query): """A "shorthand" method to start a Prolog session, load correct solutions of all user's solved problems and the given program, and ask a query. """ @@ -94,10 +94,12 @@ class PrologSession(object): try: cur = conn.cursor() try: - cur.execute('select l.id, p.id from problem p inner join problem_group g on g.id = p.problem_group_id inner join language l on l.id = p.language_id where l.identifier = %s and g.identifier = %s and p.identifier = %s', (language, problem_group, problem)) + cur.execute('select l.id, l.identifier, g.identifier, p.identifier from problem p inner join problem_group g on g.id = p.problem_group_id inner join language l on l.id = p.language_id where p.id = %s', (problem_id,)) row = cur.fetchone() language_id = row[0] - problem_id = row[1] + language = row[1] + problem_group = row[2] + problem = row[3] cur.execute('select g.identifier, p.identifier from solution s inner join problem p on p.id = s.problem_id inner join problem_group g on g.id = p.problem_group_id where s.codeq_user_id = %s and s.done = True and s.problem_id != %s and p.language_id = %s', (user_id, problem_id, language_id)) solved_problems = cur.fetchall() finally: diff --git a/server/user_session.py b/server/user_session.py index b12d6f1..bb4179d 100644 --- a/server/user_session.py +++ b/server/user_session.py @@ -6,6 +6,7 @@ import hashlib import base64 import random from . import prolog_session +from . import problems import db from errors.session import NoSuchSession, AuthenticationFailed @@ -51,6 +52,33 @@ class UserSession(object): self.prolog_session = prolog_session.PrologSession() # lazy init return self.prolog_session + def get_problem_data(self, language, problem_group, problem): + mod = problems.load_problem(language, problem_group, problem, 'en') + conn = db.get_connection() + try: + cur = conn.cursor() + try: + cur.execute("select l.id, l.name, g.id, g.name, p.id, p.name 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 l.identifier = %s and g.identifier = %s and p.identifier = %s", (language, problem_group, problem)) + row = cur.fetchone() + problem_id = row[4] + result = { + 'language': {'id': row[0], 'identifier': language, 'name': row[1]}, + 'problem_group': {'id': row[2], 'identifier': problem_group, 'name': row[3]}, + 'problem': {'id': problem_id, 'identifier': problem, 'name': row[5], 'slug': mod.slug, 'description': mod.description, 'hint': mod.hint} + } + cur.execute("select content from solution where problem_id = %s and codeq_user_id = %s", (problem_id, self.uid)) + row = cur.fetchone() + if row: + result['solution'] = row[0] + else: + result['solution'] = '' + return result + finally: + cur.close() + finally: + conn.commit() + db.return_connection(conn) + 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): diff --git a/wsgi_server.py b/wsgi_server.py index 0f70b40..9b3a51e 100644 --- a/wsgi_server.py +++ b/wsgi_server.py @@ -12,7 +12,15 @@ api = application = falcon.API() def get_json_payload(req, session_is_optional=False): - if not ((req.content_type == 'application/json') or (req.content_type == 'text/json')): + content_type_parts = req.content_type.split(';') + content_type = content_type_parts[0].strip() + encoding = 'utf-8' + if len(content_type_parts) > 1: + for part in content_type_parts[1:]: + subparts = part.strip().split('=') + if (len(subparts) == 2) and subparts[0] == 'charset': + encoding = subparts[1] + if not ((content_type == 'application/json') or (content_type == 'text/json')): raise falcon.HTTPUnsupportedMediaType('Unsupported content-type: {0}'.format(req.content_type)) length = req.get_header('Content-Length') if length is None: @@ -28,95 +36,121 @@ def get_json_payload(req, session_is_optional=False): except Exception as e: raise falcon.HTTPBadRequest('Error reading request', 'Error while reading the request body: {0}'.format(e.message)) try: - js = json.loads(txt, encoding="utf-8") + js = json.loads(txt.decode(encoding=encoding, errors='replace')) except ValueError as e: raise falcon.HTTPBadRequest('Error parsing JSON payload', 'Error while parsing the request as JSON: {0}'.format(e.message)) sid = js.get('sid') if sid is None: if session_is_optional: - return js + return js, None raise falcon.HTTPBadRequest('No Session', 'Request is missing a session-ID') del js['sid'] try: session = client.get_session_by_id(sid) except NoSuchSession: if session_is_optional: - return js + return js, None raise falcon.HTTPBadRequest('Session Expired', 'This user session has expired. Please log-in again.') - req.context['session'] = session - return js + return js, session -class Login(object): +class CodeqService(object): + """Base class for all CodeQ services. + It only support the POST method and JSON data. + Handles JSON decoding and encoding, and session retrieval which can be optional. + """ + session_is_optional = False + def on_post(self, req, resp): + js, session = get_json_payload(req, self.session_is_optional) + resp.body = json.dumps(self.process(js, session)) resp.cache_control = 'private, no-cache, no-store' - js = get_json_payload(req, True) + resp.content_type = 'application/json' + resp.status = falcon.HTTP_200 + + def process(self, js, session): + raise falcon.HTTPNotFound() + +class ProblemList(CodeqService): + session_is_optional = True + + def process(self, js, session): + language = js.get('language') + if language is None: + return {'code': 1, 'message': 'Language was not provided'} + else: + return {'code': 0, 'message': 'ok', 'problems': client.list_problems_in_groups(language)} + +class Login(CodeqService): + session_is_optional = True + + def process(self, js, old_session): username = js.get('username') password = js.get('password') if username is None: - response = {'code': 1, 'message': 'Username was not provided'} + return {'code': 1, 'message': 'Username was not provided'} elif password is None: - response = {'code': 2, 'message': 'Password was not provided'} + return {'code': 2, 'message': 'Password was not provided'} else: try: session = client.authenticate_and_create_session(username, password) except AuthenticationFailed: - response = {'code': 3, 'message': 'Username or password do not match'} + return {'code': 3, 'message': 'Username or password do not match'} else: - response = {'code': 0, 'message': 'OK', 'sid':session.get_sid()} - old_session = req.context.get('session') if old_session: old_session.destroy() - resp.body = json.dumps(response) - resp.content_type = 'application/json' - resp.status = falcon.HTTP_200 + return {'code': 0, 'message': 'OK', 'sid':session.get_sid()} -class Activity(object): +class Activity(CodeqService): def on_get(self, req, resp): resp.body = '{}' resp.status = falcon.HTTP_200 -class Query(object): - def on_post(self, req, resp): - resp.cache_control = 'private, no-cache, no-store' - js = get_json_payload(req) - session = req.context['session'] +class Query(CodeqService): + def process(self, js, session): step = js.get('step') if step is None: - response = {'code': 1, 'message': '"step" is not set'} + return {'code': 1, 'message': '"step" is not set'} else: prolog = session.get_prolog() if step == 'run': program = js.get('program') query = js.get('query') - language = js.get('language') - problem_group = js.get('problem_group') - problem = js.get('problem') + problem_id = js.get('problem_id') if program is None: - response = {'code': 2, 'message': 'No program specified'} + return {'code': 2, 'message': 'No program specified'} elif query is None: - response = {'code': 3, 'message': 'No query specified'} - elif language is None: - response = {'code': 4, 'message': 'Language identifier not given'} - elif problem_group is None: - response = {'code': 5, 'message': 'Problem group identifier not given'} - elif problem is None: - response = {'code': 6, 'message': 'Problem identifier not given'} + return {'code': 3, 'message': 'No query specified'} + elif problem_id is None: + return {'code': 4, 'message': 'Problem ID not given'} else: - messages, status, have_more = prolog.run_for_user(session.get_uid(), language, problem_group, problem, program, query) - response = {'code': 0, 'message': 'ok', 'terminal': {'messages': messages, 'status': status, 'have_more': have_more}} + messages, status, have_more = prolog.run_for_user(session.get_uid(), problem_id, program, query) + return {'code': 0, 'message': 'ok', 'terminal': {'messages': messages, 'status': status, 'have_more': have_more}} elif step == 'next': messages, status, have_more = prolog.step() - response = {'code': 0, 'message': 'ok', 'terminal': {'messages': messages, 'status': status, 'have_more': have_more}} + return {'code': 0, 'message': 'ok', 'terminal': {'messages': messages, 'status': status, 'have_more': have_more}} elif step == 'end': messages, status, have_more = prolog.end() - response = {'code': 0, 'message': 'ok', 'terminal': {'messages': messages, 'status': status, 'have_more': have_more}} + return {'code': 0, 'message': 'ok', 'terminal': {'messages': messages, 'status': status, 'have_more': have_more}} else: - response = {'code': 7, 'message': 'Unknown prolog step: {0}'.format(step)} + return {'code': 5, 'message': 'Unknown prolog step: {0}'.format(step)} - resp.body = json.dumps(response) - resp.content_type = 'application/json' - resp.status = falcon.HTTP_200 +class GetProblem(CodeqService): + def process(self, js, session): + language = js.get('language') + problem_group = js.get('problem_group') + problem = js.get('problem') + if language is None: + return {'code': 1, 'message': 'Language identifier not given'} + elif problem_group is None: + return {'code': 2, 'message': 'Problem group identifier not given'} + elif problem is None: + return {'code': 3, 'message': 'Problem identifier not given'} + else: + return {'code': 0, 'message': 'ok', 'data': session.get_problem_data(language, problem_group, problem)} + +problem_list = ProblemList() +api.add_route('/list_problems', problem_list) login = Login() api.add_route('/login', login) @@ -126,3 +160,13 @@ api.add_route('/activity', activity) query = Query() api.add_route('/query', query) + +get_problem = GetProblem() +api.add_route('/get_problem', get_problem) + +if __name__ == '__main__': + import logging + from waitress import serve + logger = logging.getLogger('waitress') + logger.setLevel(logging.DEBUG) + serve(application, host='0.0.0.0', port=8082) -- cgit v1.2.1