diff --git a/makefile b/makefile index 929f467..720943c 100644 --- a/makefile +++ b/makefile @@ -5,10 +5,10 @@ LOCPATH = ./bin OLEX = $(BUILDPATH)/$(BOT)/lex.yy.c OYACC = $(BUILDPATH)/$(BOT)/grammar.tab.c -SRC = src/$(BOT)/types.c src/$(BOT)/csv.c $(OLEX) $(OYACC) src/core/handlers.c src/core/context.c src/main.c +SRC = src/$(BOT)/types.c src/$(BOT)/task.c src/$(BOT)/csv.c $(OLEX) $(OYACC) src/core/handlers.c src/core/context.c src/main.c INSTALLPATH = /usr/local CFLAGS = `pkg-config --cflags glib-2.0` -LFLAGS = -lstrophe -lssl -lcrypto `pkg-config --libs glib-2.0` +LFLAGS = -lstrophe -lpthread -lssl -lcrypto `pkg-config --libs glib-2.0` DEBFLAGS = -g -Wall OFLAGS = -O2 NAME = xmpp-bot diff --git a/src/core/context.c b/src/core/context.c index c7dd6e2..5cb96e9 100644 --- a/src/core/context.c +++ b/src/core/context.c @@ -8,24 +8,27 @@ #include #include /* For SHA1 */ #include -#include +#include #include "context.h" /* static lifetime but only accessible here and through get and set functions below */ static struct BotXmppCtx *g_bctx = NULL; +pthread_mutex_t g_ctxLock = PTHREAD_MUTEX_INITIALIZER; -struct BotXmppCtx *bot_get_ctx() +BotXmppCtx * const bot_get_ctx() { return g_bctx; } -void bot_set_ctx(struct BotXmppCtx * const ctx) +void bot_set_ctx(BotXmppCtx * const ctx) { + pthread_mutex_lock(&g_ctxLock); if(g_bctx) { fprintf(stderr, "Dev error: cannot set XMPP ctx twice in the same program run.\n"); } else { g_bctx = ctx; } + pthread_mutex_unlock(&g_ctxLock); } void bot_free_user_data(struct BotUserInfo user, GString *filename, bool freeSegment) @@ -187,9 +190,7 @@ /* store ctx as global */ bot_set_ctx(bctx); - - /* install signal handler */ - signal(SIGALRM, bot_reminder_alarm_func); + return 0; } @@ -209,24 +210,3 @@ xmpp_shutdown(); } -/* send a message when SIGALRM is triggered */ -void bot_reminder_alarm_func(int s) -{ - unsigned int minutes; - BotReminder *reminder = bot_get_last_reminder(); - struct BotXmppCtx *bctx = bot_get_ctx(); - GString *msg = g_string_new(NULL); - - if(!reminder) { - fprintf(stderr, "SIGALRM was raised but no reminder was set.\n"); - return; - } - - minutes = reminder->minutes; - g_string_printf(msg, "OHI! Segna le spese! Te lo ricordo di nuovo tra %d minuti.\n", minutes); - send_message(bctx->conn, reminder->from, bctx->ctx, msg); - g_string_free(msg, true); - - /* reinstall alarm */ - alarm(minutes * 60); -} diff --git a/src/core/handlers.c b/src/core/handlers.c index 24e3198..a34c430 100644 --- a/src/core/handlers.c +++ b/src/core/handlers.c @@ -5,6 +5,7 @@ #include #include #include "handlers.h" +#include "../expenses/task.h" int version_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata) { @@ -58,20 +59,19 @@ GString *intext; GString *replytext; BotReminder *reminder; + const gchar * from = xmpp_stanza_get_attribute(stanza, "from"); if(!xmpp_stanza_get_child_by_name(stanza, "body")) return 1; if(!strcmp(xmpp_stanza_get_attribute(stanza, "type"), "error")) return 1; intext = g_string_new(xmpp_stanza_get_text(xmpp_stanza_get_child_by_name(stanza, "body"))); - printf("Incoming message from %s: %s\n", xmpp_stanza_get_attribute(stanza, "from"), intext->str); - - /* parse with yyparse() */ + printf("Incoming message from %s: %s\n", from, intext->str); reply = xmpp_stanza_new(ctx); xmpp_stanza_set_name(reply, "message"); xmpp_stanza_set_type(reply, xmpp_stanza_get_type(stanza)?xmpp_stanza_get_type(stanza):"chat"); - xmpp_stanza_set_attribute(reply, "to", xmpp_stanza_get_attribute(stanza, "from")); + xmpp_stanza_set_attribute(reply, "to", from); body = xmpp_stanza_new(ctx); xmpp_stanza_set_name(body, "body"); @@ -87,12 +87,19 @@ g_string_printf(replytext, "OK! Ho salvato l'ultima spesa, id: %u\n", lastId); break; case BOT_CMD_REMINDER: - // TODO this allocates twice, fix - reminder = malloc(sizeof *reminder); - reminder->minutes = (bot_get_last_reminder())->minutes; - reminder->from = g_string_new(strdup(xmpp_stanza_get_attribute(stanza, "from"))); - bot_set_last_reminder(reminder); - g_string_printf(replytext, "Va bene, ti mando un messaggio tra %d minuti.\n", reminder->minutes); + reminder = bot_get_last_reminder(); + g_string_append(reminder->from, from); + if(reminder->minutes > 0) { + if(bot_start_task(reminder) != 0) { + g_string_printf(replytext, "Mi dispiace %s ma non posso mandarti un messaggio tra %d minuti, qualcosa non va.\n", reminder->from->str, reminder->minutes); + } else { + g_string_printf(replytext, "Va bene %s, ti mando un messaggio tra %d minuti.\n", reminder->from->str, reminder->minutes); + } + } else { + /* cancel reminder */ + bot_cancel_task(from); + g_string_printf(replytext, "Ho cancellato il reminder, %s.\n", reminder->from->str); + } break; default: g_string_printf(replytext, "Non ho riconosciuto il comando: %s", intext->str); diff --git a/src/expenses/csv.c b/src/expenses/csv.c index fd23a1d..ea2932f 100644 --- a/src/expenses/csv.c +++ b/src/expenses/csv.c @@ -1,6 +1,7 @@ #include #include #include +#include "types.h" #include "csv.h" int bot_get_current_entry_id_csv @@ -86,7 +87,6 @@ result = write_bot_expense_entry(filename, entry, (id == 0)); if(result > 0) { /* write successful */ - bot_set_last_command(cmd); bot_set_last_expense(entry); } return result; diff --git a/src/expenses/grammar.y b/src/expenses/grammar.y index 7670265..7784022 100644 --- a/src/expenses/grammar.y +++ b/src/expenses/grammar.y @@ -1,8 +1,6 @@ %{ #include #include /* For GString */ -#include "../../src/expenses/types.h" -#include "../../src/expenses/csv.h" /* suppress warning about missing declarations */ int yylex(); @@ -22,13 +20,11 @@ %code requires { #include "../../src/expenses/types.h" +#include "../../src/expenses/task.h" #include "../../src/expenses/csv.h" /* Parse a string with Flex+Bison instead of a file / stdout */ int grammar_parse_string(const GString *buf); - -/* start a SIGALARM routine */ -int start_alarm_reminder(unsigned int minutes); } %define parse.lac full @@ -65,13 +61,13 @@ msg: update value TOK_USER TOK_STR { + bot_set_last_command($1); int res = write_update_to_csv(bot_get_filename(), $1, $2, $3, $4); - printf("update [%d]: %d %f %s %s\n", res, $1, $2, $3->str, $4->str); $$ = 0; } | TOK_CMD_REMINDER TOK_INT { - int res = start_alarm_reminder($2); - printf("reminder in %d minutes\n", $2); $$ = 0; + bot_set_last_command($1); + bot_set_last_reminder($2); } | TOK_CMD_DEL TOK_INT { printf("delete: %d\n", $2); $$ = 0; } | TOK_CMD_REPORT ts { printf("report: %d\n", $2); $$ = 0; } @@ -99,19 +95,3 @@ yylex_destroy(); return res; } - -/* start a SIGALARM routine */ -int start_alarm_reminder(unsigned int minutes) -{ - /* in seconds */ - alarm(minutes * 60); - - BotReminder *reminder = malloc(sizeof *reminder); - reminder->minutes = minutes; - reminder->from = g_string_new(NULL); /* to be set by the message handler of the bot */ - - bot_set_last_command(BOT_CMD_REMINDER); - bot_set_last_reminder(reminder); - - return 0; -} diff --git a/src/expenses/task.c b/src/expenses/task.c new file mode 100644 index 0000000..dbcafbc --- /dev/null +++ b/src/expenses/task.c @@ -0,0 +1,144 @@ +/* Use pthread.h to run background tasks such as reminder + * aim is to avoid signals (per-process) +*/ +#include +#include +#include +#include +#include "types.h" +#include "../core/context.h" + +typedef struct BotTask { + pthread_t tid; + BotReminder *reminder; +} BotTask; + +/* protected globals */ +static BotCommand g_lastCommand = BOT_CMD_NONE; +static BotReminder *g_tmp_lastReminder = NULL; + +/* store a thread per user */ +static GHashTable* g_taskTable = NULL; + +BotCommand bot_get_last_command() +{ + return g_lastCommand; +} + +void bot_set_last_command(const BotCommand cmd) +{ + g_lastCommand = cmd; +} + +BotReminder *bot_get_last_reminder() +{ + BotReminder *reminder = malloc(sizeof *reminder); + + if(g_tmp_lastReminder) { + /* deep copy, but we don't care about from */ + reminder->minutes = g_tmp_lastReminder->minutes; + reminder->from = g_string_new(""); + /* free_bot_reminder(g_tmp_lastReminder); /\* can only be used once *\/ */ + } + + return reminder; +} + +void bot_set_last_reminder(const unsigned int minutes) +{ + BotReminder *reminder; + if(g_tmp_lastReminder) { + fprintf(stderr, "WARNING: overwriting reminder. This should not happen.\n"); + free_bot_reminder(g_tmp_lastReminder); /* can only be used once */ + } + reminder = malloc(sizeof *reminder); + reminder->minutes = minutes; + reminder->from = g_string_new(""); /* will be set afterwards */ + g_tmp_lastReminder = reminder; +} + +void free_bot_task (BotTask * data) +{ + /* cancel and wait for the thread to finish */ + pthread_cancel(data->tid); + pthread_join(data->tid, NULL); + + if(data->reminder) { + free_bot_reminder(data->reminder); + } + free(data); +} + +void bot_task_table_init() +{ + if (g_taskTable) { + fprintf(stderr, "Programmer error: User table has already been initialized. Won't do it again."); + return; + } + + g_taskTable = g_hash_table_new(g_str_hash, g_str_equal); +} + +void bot_task_table_insert(gchar * const key, BotTask * const value) +{ + BotTask * v; + + if (!g_taskTable) { + bot_task_table_init(); + } + + if ((v = g_hash_table_lookup(g_taskTable, key))) { /* value already present */ + free_bot_task(v); + } + + g_hash_table_insert(g_taskTable, key, value); +} + +void *bot_reminder_task(void *arg) +{ + const BotReminder * const reminder = (const BotReminder * const)arg; + BotXmppCtx * bctx = bot_get_ctx(); + GString *msg = g_string_new(NULL); + g_string_printf(msg, "OHI! Segna le spese! Te lo ricordo di nuovo tra %d minuti.\n", reminder->minutes); + + while (1) { + sleep(reminder->minutes * 60); // only this thread waits + + /* send a message */ + bctx = bot_get_ctx(); // TODO needs a lock + send_message(bctx->conn, reminder->from, bctx->ctx, msg); + } +} + +/* start a thread and update task table */ +int bot_start_task (BotReminder *reminder) +{ + pthread_t tid; + BotTask *task; + + if ((task = g_hash_table_lookup(g_taskTable, reminder->from->str))) { /* value already present, stop reminder */ + free_bot_task(task); + } + + task = malloc(sizeof *task); + + if(pthread_create(&tid, NULL, bot_reminder_task, reminder) != 0) { + fprintf(stderr, "Unable to start thread. No reminder will be set."); + return -1; + } + + task->tid = tid; + task->reminder = reminder; + + bot_task_table_insert(reminder->from->str, task); + return 0; +} + +void bot_cancel_task(const gchar * const user) +{ + BotTask *task; + if ((task = g_hash_table_lookup(g_taskTable, user))) { /* value already present, stop reminder */ + free_bot_task(task); + } + g_hash_table_remove(g_taskTable, user); +} diff --git a/src/expenses/task.h b/src/expenses/task.h new file mode 100644 index 0000000..4380e16 --- /dev/null +++ b/src/expenses/task.h @@ -0,0 +1,24 @@ +/* Use pthread.h to run background tasks such as reminder + * aim is to avoid signals (per-process) +*/ +#include +#include "types.h" + +typedef struct BotTask { + pthread_t tid; + BotReminder *reminder; +} BotTask; + +BotReminder *bot_get_last_reminder(); +void bot_set_last_reminder(const unsigned int minutes); + +BotCommand bot_get_last_command(); +void bot_set_last_command(BotCommand cmd); + +void bot_task_table_insert(gchar * const key, BotTask * const value); + +/* start a thread and update task table */ +int bot_start_task (BotReminder *reminder); + +/* cancel a thread and remove entry from task table */ +void bot_cancel_task(const gchar * const user); diff --git a/src/expenses/types.c b/src/expenses/types.c index e99c163..2fd7386 100644 --- a/src/expenses/types.c +++ b/src/expenses/types.c @@ -8,42 +8,19 @@ #include "types.h" /* static lifetime but only accessible here and through get and set functions below */ -static BotCommand g_LastCommand = BOT_CMD_NONE; -static BotExpenseEntry *g_LastExpenseEntry = NULL; -static BotReminder *g_LastReminder = NULL; +static BotExpenseEntry * g_lastExpenseEntry = NULL; static GString *g_filename = NULL; -BotCommand bot_get_last_command() -{ - return g_LastCommand; -} -void bot_set_last_command(BotCommand cmd) -{ - g_LastCommand = cmd; -} - BotExpenseEntry *bot_get_last_expense() { - return g_LastExpenseEntry; + return g_lastExpenseEntry; } void bot_set_last_expense(BotExpenseEntry *entry) { - if(g_LastExpenseEntry) { - free_bot_expense_entry(g_LastExpenseEntry); + if(g_lastExpenseEntry) { + free_bot_expense_entry(g_lastExpenseEntry); } - g_LastExpenseEntry = entry; -} - -BotReminder *bot_get_last_reminder() -{ - return g_LastReminder; -} -void bot_set_last_reminder(BotReminder *reminder) -{ - if(g_LastReminder) { - free_bot_reminder(g_LastReminder); - } - g_LastReminder = reminder; + g_lastExpenseEntry = entry; } GString *bot_get_filename() diff --git a/src/expenses/types.h b/src/expenses/types.h index 7731f0b..ef1a773 100644 --- a/src/expenses/types.h +++ b/src/expenses/types.h @@ -72,18 +72,9 @@ BotReminder *reminder ); -/* use this to check the last received command when responding */ -BotCommand bot_get_last_command(); -void bot_set_last_command(BotCommand cmd); - -/* store the last expense in memory */ -BotExpenseEntry *bot_get_last_expense(); -void bot_set_last_expense(BotExpenseEntry *entry); - -/* store the last reminder in memory */ -BotReminder *bot_get_last_reminder(); -void bot_set_last_reminder(BotReminder *reminder); - -/* store the filename in memory */ +/* getters & setters. Pointers return NULL if not present */ GString *bot_get_filename(); void bot_set_filename(GString *fn); + +BotExpenseEntry *bot_get_last_expense(); +void bot_set_last_expense(BotExpenseEntry *entry);