Commit c1e08d68 authored by platypro's avatar platypro

Made better command-line interface; Added JSON export

parent 9dab4727
......@@ -20,13 +20,15 @@ configure_file("src/paths.h.in" "${CMAKE_CURRENT_BINARY_DIR}/paths.h")
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
${DEPS_INCLUDE}
src
)
set(SRCS
src/quizgrinder.c
src/exercise.c
src/script.c
src/session.c
src/daemon/daemon.c
src/daemon/http.c
src/pcg_basic.c
${PLATYPRO_BASE_LIST_SRCS}
)
......
#include "common.h"
#include "daemon/daemon.h"
#include <termios.h>
#include <pcg_basic.h>
#include <unistd.h>
#include "daemon/http.h"
#include "quizgrinder.h"
bool daemon_stop(PG_STATE* state)
{
if(state->daemon.type & DAEMON_HTTP)
daemon_http_stop(state);
tcsetattr(STDIN_FILENO, TCSANOW, &state->daemon.oldt);
return true;
}
bool daemon_run(PG_STATE* state)
{
if(state->daemon.type & DAEMON_HTTP)
daemon_http_start(state);
else return false;
printf("Daemon mode enabled\n");
struct termios term;
char c;
exercise_loadAll(state);
pcg32_srandom(time(NULL), (intptr_t)&printf);
//Change term settings
if (tcgetattr( STDIN_FILENO, &term) == 0)
{
state->daemon.oldt = term;
term.c_lflag &= ~(ICANON | ECHO | ISIG);
tcsetattr( STDIN_FILENO, TCSANOW, &term);
}
while((c = getchar()) != 'q')
{
switch(c)
{
case 'r':
// This can probably be optimized greatly. For now this is fine
printf("Reloading...\n");
exercise_cleanup(state);
script_cleanup(state);
script_init(state);
exercise_loadAll(state);
printf("Reloaded!\n");
break;
default: continue;
}
}
daemon_stop(state);
return true;
}
#ifndef INCLUDE_DAEMON_H
#define INCLUDE_DAEMON_H
#include <termios.h>
struct PG_State;
#define DAEMON_HTTP (1<<0)
typedef struct PG_Daemon {
struct termios oldt;
uint8_t type;
} PG_DAEMON;
extern bool daemon_run(struct PG_State* state);
extern bool daemon_stop(struct PG_State* state);
#endif /* INCLUDE_DAEMON_H */
......@@ -7,7 +7,7 @@
*/
#include "common.h"
#include "session.h"
#include "daemon/http.h"
#include <unistd.h>
#include <sys/un.h>
......@@ -24,10 +24,10 @@
USER** getUser(PG_STATE* state, UID_T uid)
{
pthread_mutex_lock(&state->session.userMutex);
pthread_mutex_lock(&state->http.userMutex);
//Search for user
uint8_t basebucket = (uid & 0xFF);
USER** bucket = &state->session.users[basebucket];
USER** bucket = &state->http.users[basebucket];
USER** result = NULL;
while(!result)//bucket->uid != uid || result->next)
......@@ -62,7 +62,7 @@ USER** getUser(PG_STATE* state, UID_T uid)
else
result = bucket;
}
pthread_mutex_unlock(&state->session.userMutex);
pthread_mutex_unlock(&state->http.userMutex);
return result;
}
......@@ -119,7 +119,7 @@ bool user_writeQuestion(USER* u, WJWriter w)
void session_common_init(PG_STATE* state)
{
pthread_mutex_init(&state->session.userMutex, NULL);
pthread_mutex_init(&state->http.userMutex, NULL);
}
#define SCP_ASSERT(val, msg) \
......@@ -171,7 +171,7 @@ bool session_common_process(PG_STATE* state, WJElement request, WJWriter respons
if(u->questionTop)
exercise_destroyQuiz(u->questionTop);
int numQuestions = 5;
uint32_t numQuestions = 5;
QUESTION* elem = exercise_genQuiz(state, pset, &numQuestions);
SCP_ASSERT(elem, "Invalid problem set!");
......@@ -281,7 +281,7 @@ close_session_common_process:
void session_common_cleanup(PG_STATE* state)
{
int i = MAXUSERS;
USER** user = state->session.users;
USER** user = state->http.users;
while(i)
{
if(*user)
......@@ -297,7 +297,7 @@ void session_common_cleanup(PG_STATE* state)
user++;
i--;
}
pthread_mutex_destroy(&state->session.userMutex);
pthread_mutex_destroy(&state->http.userMutex);
}
size_t WJWOnionCallback(char *data, size_t size, void *_res)
......@@ -355,10 +355,10 @@ int session_http_proc(void *p, onion_request * req, onion_response * res)
void session_http_do_cleanup(void* state)
{
PG_STATE* s = state;
if(s->session.onion)
if(s->http.onion)
{
onion_listen_stop(s->session.onion);
onion_free(s->session.onion);
onion_listen_stop(s->http.onion);
onion_free(s->http.onion);
session_common_cleanup(s);
}
}
......@@ -368,7 +368,7 @@ void* session_http_do(void* state)
pthread_cleanup_push(session_http_do_cleanup, state);
PG_STATE* s = state;
onion* o = onion_new(O_NO_SIGTERM);
s->session.onion = o;
s->http.onion = o;
onion_set_root_handler(o, onion_handler_new(session_http_proc,state,NULL));
onion_set_port(o, DEF_PORT);
session_common_init(s);
......@@ -378,16 +378,16 @@ void* session_http_do(void* state)
return NULL;
}
bool session_http(PG_STATE* state)
bool daemon_http_start(PG_STATE* state)
{
pthread_create(&state->session.thread, NULL, session_http_do, state);
pthread_create(&state->http.thread, NULL, session_http_do, state);
return true;
}
bool session_http_stop(PG_STATE* state)
bool daemon_http_stop(PG_STATE* state)
{
pthread_cancel(state->session.thread);
pthread_join(state->session.thread, NULL);
pthread_cancel(state->http.thread);
pthread_join(state->http.thread, NULL);
return true;
}
......
......@@ -49,7 +49,7 @@ typedef struct User
} USER;
typedef struct PG_Session
typedef struct PG_Http
{
pthread_mutex_t userMutex;
USER* users[MAXUSERS];
......@@ -58,16 +58,7 @@ typedef struct PG_Session
onion* onion;
pthread_t thread;
} PG_SESSION;
typedef struct ThreadInfo
{
struct PG_State* state;
struct sockaddr conn;
socklen_t conn_len;
int conn_fd;
} THREADINFO;
} PG_HTTP;
typedef enum RequestType
{
......@@ -80,8 +71,8 @@ typedef enum RequestType
REQUEST_ERROR
} REQUESTTYPE;
extern bool session_http(struct PG_State* state);
extern bool session_http_stop(struct PG_State* state);
extern bool daemon_http_start(struct PG_State* state);
extern bool daemon_http_stop(struct PG_State* state);
#endif /*INCLUDE_SESSION_H*/
......
......@@ -29,18 +29,20 @@ char* genPath(char* path, char* dir)
return result;
}
char* loadId(char* path, char* id)
char* loadId(char* path, char* id, uint8_t chop)
{
size_t len;
size_t len;
char* result = NULL;
DIR *d;
struct dirent *dir;
len = strlen(id);
d = opendir(path);
while((dir = readdir(d)))
{
if(dir->d_type == DT_REG && !memcmp(basename(dir->d_name), id, len))
if((strlen(dir->d_name) - chop) != len) continue;
if(dir->d_type == DT_REG && !memcmp(dir->d_name, id, len))
{
result = dir->d_name;
break;
......@@ -57,106 +59,120 @@ char* loadId(char* path, char* id)
return result;
}
bool exercise_init(PG_STATE* state, char* search)
//TODO: Allow updating
char* exercise_load(PG_STATE* state, char* filename)
{
//TODO: Test against schemas
DIR *d;
struct dirent *dir;
d = opendir(search);
while ((dir = readdir(d)))
PROBLEMSET* pset = calloc(1, sizeof(PROBLEMSET));
pset->next = state->exercise.problemSets;
state->exercise.problemSets = pset;
FILE* f = fopen(filename, "r");
WJElement question = NULL;
WJReader pset_rdr =
WJROpenFILEDocument(f, NULL, 0);
WJElement pset_element =
WJEOpenDocument(pset_rdr, NULL, NULL, NULL);
WJRCloseDocument(pset_rdr);
fclose(f);
WJElement questions = WJEArray(pset_element, KEYLIST(KEY_QUESTION), WJE_GET);
char* nameObj = WJEString(pset_element, KEY_NAME, WJE_GET, NULL);
pset->name = malloc(strlen(nameObj) + 1);
strcpy(pset->name, nameObj);
pset->numExercises = questions->count;
pset->exercises = calloc(pset->numExercises, sizeof(EXERCISEREF));
EXERCISEREF* exref = pset->exercises;
while((question = WJEGet(questions, "[]", question)))
{
if(dir->d_type == DT_REG)
char* exerId = WJEString(question, KEY_ID, WJE_GET, NULL);
EXERCISE* exer = NULL;
//First see if exercise is already loaded
EXERCISE* exerFind = state->exercise.stash;
while(exerFind)
{
if(!strcmp(exerFind->id, exerId))
{
exer=exerFind;
break;
}
}
if(!exer)
{
PROBLEMSET* pset = calloc(1, sizeof(PROBLEMSET));
pset->next = state->exercise.problemSets;
state->exercise.problemSets = pset;
//Load the exercise
exer = calloc(1, sizeof(EXERCISE));
exer->next = state->exercise.stash;
state->exercise.stash = exer;
char* fpath = genPath(search, dir->d_name);
if(!fpath) continue;
char* exerPath = loadId(state->path_exercise, exerId, 5);
if(!exerPath) return NULL;
FILE* f = fopen(exerPath, "r");
free(exerPath);
FILE* f = fopen(fpath, "r");
free(fpath);
WJElement question = NULL;
WJReader pset_rdr =
WJReader exer_rdr =
WJROpenFILEDocument(f, NULL, 0);
WJElement pset_element =
WJEOpenDocument(pset_rdr, NULL, NULL, NULL);
WJRCloseDocument(pset_rdr);
WJElement exer_element =
WJEOpenDocument(exer_rdr, NULL, NULL, NULL);
WJRCloseDocument(exer_rdr);
fclose(f);
exer->doc = exer_element;
exer->id = malloc(strlen(exerId) + 1);
strcpy(exer->id, exerId);
WJElement questions = WJEArray(pset_element, KEYLIST(KEY_QUESTION), WJE_GET);
char* nameObj = WJEString(pset_element, KEY_NAME, WJE_GET, NULL);
pset->name = malloc(strlen(nameObj) + 1);
strcpy(pset->name, nameObj);
pset->numExercises = questions->count;
pset->exercises = calloc(pset->numExercises, sizeof(EXERCISEREF));
int i = 0;
while((question = WJEGet(questions, "[]", question)))
{
char* exerId = WJEString(question, KEY_ID, WJE_GET, NULL);
EXERCISEREF* exref = pset->exercises + i;
EXERCISE* exer = NULL;
//First see if exercise is already loaded
EXERCISE* exerFind = state->exercise.stash;
while(exerFind)
{
if(!strcmp(exerFind->id, exerId))
{
exer=exerFind;
break;
}
}
if(!exer)
{
//Load the exercise
exer = calloc(1, sizeof(EXERCISE));
exer->next = state->exercise.stash;
state->exercise.stash = exer;
char* scriptPath = loadId(state->path_script, exerId, 4);
// script_load frees scriptPath
if(!scriptPath || !script_load(state, exerId, scriptPath)) return NULL;
}
exref->exercise = exer;
exref->options = WJEObject(NULL, NULL, WJE_NEW);
WJEMergeObjects(exref->options, WJEObject(exer->doc, KEYLIST(KEY_OPTION), WJE_GET), false);
WJEMergeObjects(exref->options, WJEObject(question, KEYLIST(KEY_OPTION), WJE_GET), true);
exref++;
}
WJECloseDocument(pset_element);
return pset->name;
}
char* exerPath = loadId(APP_EXERCISE_PATH, exerId);
if(!exerPath) continue;
FILE* f = fopen(exerPath, "r");
free(exerPath);
WJReader exer_rdr =
WJROpenFILEDocument(f, NULL, 0);
WJElement exer_element =
WJEOpenDocument(exer_rdr, NULL, NULL, NULL);
WJRCloseDocument(exer_rdr);
fclose(f);
//TODO: Clean-up deleted exercises
bool exercise_loadAll(PG_STATE* state)
{
DIR *d;
struct dirent *dir;
d = opendir(state->path_pset);
exer->doc = exer_element;
exer->id = malloc(strlen(exerId) + 1);
strcpy(exer->id, exerId);
char* scriptPath = loadId(APP_SCRIPT_PATH, exerId);
// script_load frees scriptPath
if(!scriptPath || !script_load(state, exerId, scriptPath)) continue;
}
exref->exercise = exer;
exref->options = WJEObject(NULL, NULL, WJE_NEW);
WJEMergeObjects(exref->options, WJEObject(exer->doc, KEYLIST(KEY_OPTION), WJE_GET), false);
WJEMergeObjects(exref->options, WJEObject(question, KEYLIST(KEY_OPTION), WJE_GET), true);
i++;
}
WJECloseDocument(pset_element);
while ((dir = readdir(d)))
{
if(dir->d_type == DT_REG)
{
char* fpath = genPath(state->path_pset, dir->d_name);
if(!fpath) return false;
exercise_load(state, fpath);
free(fpath);
}
}
closedir(d);
return true;
}
QUESTION* exercise_genQuiz(PG_STATE* state, char* name, int* numQuestions)
char* exercise_loadOne(PG_STATE* state, char* set)
{
char* path = loadId(state->path_pset, set, 5);
if(!path) return false;
set = exercise_load(state, path);
free(path);
return set;
}
QUESTION* exercise_genQuiz(PG_STATE* state, char* name, uint32_t* numQuestions)
{
QUESTION* result = NULL;
QUESTION* at = NULL;
......@@ -177,7 +193,7 @@ QUESTION* exercise_genQuiz(PG_STATE* state, char* name, int* numQuestions)
while(*numQuestions)
{
WJElement doc = WJEObject(NULL, NULL, WJE_NEW);
int exnum = rand() % pset->numExercises;
uint32_t exnum = rand() % pset->numExercises;
EXERCISEREF* exercise = &pset->exercises[exnum];
script_genQuiz(state, doc, exercise);
......@@ -211,6 +227,31 @@ QUESTION* exercise_genQuiz(PG_STATE* state, char* name, int* numQuestions)
return result;
}
bool exercise_writeQuiz(QUESTION* q, char* file)
{
FILE* f = fopen(file, "w");
if(!f) return false;
WJWriter w = WJWOpenFILEDocument(true, f);
WJWOpenArray(NULL, w);
while(q)
{
WJWOpenObject(NULL, w);
WJWString(KEY_CALCULATOR, q->calculator, true, w);
WJWString(KEY_NAME, q->name, true, w);
WJElement d = q->doc->child;
while(d)
{
WJEWriteDocument(d, w, d->name);
d = d->next;
}
WJWCloseObject(w);
q = q->next;
}
WJWCloseArray(w);
return true;
}
void exercise_destroyQuiz(QUESTION* quiz)
{
QUESTION* q = quiz;
......@@ -230,7 +271,7 @@ void exercise_cleanup(PG_STATE* state)
{
PROBLEMSET* set_next = set->next;
EXERCISEREF* ref = set->exercises;
int i = set->numExercises;
uint32_t i = set->numExercises;
while(i)
{
WJECloseDocument(ref->options);
......
......@@ -67,8 +67,10 @@ typedef struct PG_Exercise
} PG_EXERCISE;
extern bool exercise_init(struct PG_State* state, char* search);
extern QUESTION* exercise_genQuiz(struct PG_State* state, char* name, int* numQuestions);
extern bool exercise_loadAll(struct PG_State* state);
extern char* exercise_loadOne(struct PG_State* state, char* set);
extern QUESTION* exercise_genQuiz(struct PG_State* state, char* name, uint32_t* numQuestions);
extern bool exercise_writeQuiz(QUESTION* quiz, char* path);
extern void exercise_destroyQuiz(QUESTION* quiz);
extern void exercise_cleanup(struct PG_State* state);
......
......@@ -4,4 +4,3 @@
#define APP_PROBSET_PATH APP_DATA_PATH "/pset"
#define APP_SCRIPT_PATH APP_DATA_PATH "/script"
#define APP_EXERCISE_PATH APP_DATA_PATH "/exercise"
#define APP_SCHEMA_PATH APP_DATA_PATH "/schema"
......@@ -10,87 +10,130 @@
#include "quizgrinder.h"
#include <unistd.h>
#include <getopt.h>
#include <argp.h>
#include <signal.h>
#include <termios.h>
#include <fcntl.h>
#include <string.h>
#include "pcg_basic.h"
PG_STATE* gstate = NULL;
static struct termios oldt;
void init(PG_STATE* state)
{
struct termios term;
script_init(state);
exercise_init(state, APP_PROBSET_PATH);
pcg32_srandom(time(NULL), (intptr_t)&printf);
#define OPT_PATH_EX 128
#define OPT_PATH_PS 129
#define OPT_PATH_SC 130
#define OPT_DAEMON_HTTP 'd'
#define OPT_OUTPUT_FILE 'f'
#define OPT_OUTPUT_TYPE 't'
#define OPT_INPUT_PSET 's'
#define OPT_NUMQUESTIONS 'n'
//Change term settings
if (tcgetattr( STDIN_FILENO, &oldt) == 0)
{
term = oldt;
term.c_lflag &= ~(ICANON | ECHO | ISIG);
tcsetattr( STDIN_FILENO, TCSANOW, &term);
}
}
PG_STATE* gstate = NULL;
void cleanup(PG_STATE* state)
{
session_http_stop(state);
daemon_stop(state);
exercise_cleanup(state);
script_cleanup(state);
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
}
void interrupt(int signal)
{
printf("Stop\n");
if(gstate)
cleanup(gstate);
cleanup(gstate);
exit(0);
}
int main(int argc, char* argv[])
bool handle_file(PG_STATE* state)
{
PG_STATE state = {0};
gstate = &state;
char c;
signal(SIGINT, interrupt);
init(&state);
QUESTION* q = exercise_genQuiz(state, exercise_loadOne(state, state->gen_pset), &state->gen_num);
exercise_writeQuiz(q, state->gen_file);
exercise_destroyQuiz(q);
return true;
}
while((c = getopt(argc, argv, "d")) != -1)
error_t arg_parser (int key, char *arg, struct argp_state *s)
{
PG_STATE* state = s->input;
switch(key)
{
switch(c)
{
case 'd':
printf("Daemon mode enabled\n");
session_http(&state);
while((c = getchar()) != 'q')
{
switch(c)
{
case 'r':
// This can probably be optimized greatly. For now this is fine
printf("Reloading...\n");
exercise_cleanup(&state);
script_cleanup(&state);
script_init(&state);
exercise_init(&state, APP_EXERCISE_PATH);
printf("Reloaded!\n");
break;
default: continue;
}
}
cleanup(&state);
case OPT_PATH_EX:
state->path_exercise = arg;
break;
case OPT_PATH_PS:
state->path_pset = arg;
break;
case OPT_PATH_SC:
state->path_script = arg;
break;
case OPT_DAEMON_HTTP:
state->daemon.type |= DAEMON_HTTP;
break;
case OPT_OUTPUT_FILE:
state->gen_file = arg;
break;
case OPT_INPUT_PSET:
state->gen_pset = arg;
break;
case OPT_OUTPUT_TYPE:
if(!strcmp(arg, "json"))
state->gen_type = GTYPE_JSON;
break;
case OPT_NUMQUESTIONS:
state->gen_num = strtoul(arg, 0, 0);
default:
break;
}
}
return 0;
}
struct argp_option args[] = {
{"exercise-path", OPT_PATH_EX, "PATH", 0, "Path to find exercises", 0},
{"pset-path" , OPT_PATH_PS, "PATH", 0, "Path to find problem sets", 0},
{"script-path" , OPT_PATH_SC, "PATH", 0, "Path to find scripts", 0},
{NULL, 0, NULL , OPTION_DOC, "Daemons: These override any file operations", 0},
{"http-daemon" , OPT_DAEMON_HTTP, NULL, 0, "Run HTTP daemon. Overrides any file options.", 0},
{NULL, 0, NULL , OPTION_DOC, "File operations", 0},
{NULL , OPT_OUTPUT_FILE, "FILE", 0, "Output file", 0},
{NULL , OPT_OUTPUT_TYPE, "TYPE", 0, "Output type", 0},
{NULL , OPT_INPUT_PSET, "FILE", 0, "Problem set to generate", 0},
{NULL , OPT_NUMQUESTIONS, "NUM", 0, "Number of questions"},
{0,0,0,0,0}
};
struct argp argsettings = {
.options = args,
.parser = arg_parser,
.doc = "A quiz generator",
.children = NULL,
.help_filter = NULL,
.argp_domain = NULL
};
int main(int argc, char* argv[])
{
PG_STATE state = {0};
signal(SIGINT, interrupt);
gstate = &state;
state.path_exercise = APP_EXERCISE_PATH;
state.path_script = APP_SCRIPT_PATH;
state.path_pset = APP_PROBSET_PATH;
state.gen_type = GTYPE_JSON;
state.gen_num = 10;
argp_parse (&argsettings, argc, argv, 0, NULL, &state);
script_init(&state);
if(state.daemon.type)
daemon_run(&state);
else if(state.gen_file && state.gen_pset)
handle_file(&state);