diff --git a/src/bot/context.c b/src/bot/context.c index 220f5c7..087a1ce 100644 --- a/src/bot/context.c +++ b/src/bot/context.c @@ -3,6 +3,22 @@ #include "context.h" +extern struct BotXmppCtx *g_bctx = NULL; + +struct BotXmppCtx *bot_get_ctx() +{ + return g_bctx; +} + +void bot_set_ctx(struct BotXmppCtx *ctx) +{ + if(g_bctx) { + fprintf(stderr, "Dev error: cannot set XMPP ctx twice in the same program run.\n"); + } else { + g_bctx = ctx; + } +} + void bot_free_user_data(struct BotUserInfo user, GString *filename, bool freeSegment) { g_string_free(filename, freeSegment); @@ -63,11 +79,11 @@ /* implement https://xmpp.org/extensions/xep-0153.html */ -GString *bot_xmpp_set_avatar(struct BotXmppCtx * const bctx, const GString *avatarFile) +int bot_xmpp_set_avatar(struct BotXmppCtx * const bctx, const GString *avatarFile) { gchar *avatar; gsize avatarLenght; - GString *avatarID; + /* GString *avatarID; */ // TODO unsigned char hash[SHA_DIGEST_LENGTH]; /* == 20 */ xmpp_stanza_t *iq, *vcard, *photo, *type, *binval; @@ -99,7 +115,7 @@ xmpp_stanza_set_name(binval, "BINVAL"); if(g_file_get_contents(avatarFile->str, &avatar, &avatarLenght, NULL)) { - SHA1(avatarFile->str, sizeof(avatarFile->str)-1, hash); + SHA1((const unsigned char*)avatarFile->str, sizeof(avatarFile->str)-1, hash); xmpp_stanza_set_text(binval, avatar); } else { fprintf(stderr, "Unable to read BASE64-encoded avatar.\n"); @@ -110,16 +126,17 @@ xmpp_send(bctx->conn, iq); xmpp_stanza_release(iq); - return g_string_new(hash); + return 0; } -int bot_xmpp_startup(struct BotUserInfo user, GString *avatarFile, struct BotXmppCtx * const bctx) +int bot_xmpp_startup(struct BotUserInfo user, GString *avatarFile) { xmpp_log_t* newLog; xmpp_ctx_t* newCtx; xmpp_conn_t* newCon; - GString *avatarID; + /* GString *avatarID; */ // TODO int connectStatus; + struct BotXmppCtx *bctx = malloc(sizeof *bctx); /* init library */ xmpp_initialize(); @@ -145,8 +162,12 @@ xmpp_conn_set_jid(newCon, user.jid->str); xmpp_conn_set_pass(newCon, user.pass->str); + bctx->log = newLog; + bctx->ctx = newCtx; + bctx->conn = newCon; + /* TODO properly set avatarID in presence stanza */ - avatarID = bot_xmpp_set_avatar(bctx, avatarFile); + bot_xmpp_set_avatar(bctx, avatarFile); /* initiate connection */ connectStatus = xmpp_connect_client(newCon, NULL, 0, conn_handler, newCtx); @@ -155,23 +176,47 @@ return connectStatus; } - (*bctx).log = newLog; - (*bctx).ctx = newCtx; - (*bctx).conn = newCon; + bot_set_ctx(bctx); + /* install signal handler */ + signal(SIGALRM, reminder_alarm_func); return 0; } -void bot_xmpp_shutdown(struct BotXmppCtx * const bctx) +void bot_xmpp_shutdown() { + struct BotXmppCtx *bctx = bot_get_ctx(); + /* enter the event loop - our connect handler will trigger an exit */ - xmpp_run((*bctx).ctx); + xmpp_run(bctx->ctx); /* release our connection and context */ - xmpp_conn_release((*bctx).conn); - xmpp_ctx_free((*bctx).ctx); + xmpp_conn_release(bctx->conn); + xmpp_ctx_free(bctx->ctx); /* final shutdown of the library */ xmpp_shutdown(); } + +/* send a message when SIGALRM is triggered */ +void 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/bot/context.h b/src/bot/context.h index 8a49dc2..2ad25fd 100644 --- a/src/bot/context.h +++ b/src/bot/context.h @@ -13,6 +13,8 @@ #include #include #include /* For SHA1 */ +#include +#include #include "handlers.h" struct BotUserInfo { @@ -29,10 +31,18 @@ void bot_free_user_data(struct BotUserInfo user, GString *filename, bool freeSegment); /* sets the given avatar file (base64 encoded) */ -GString *bot_xmpp_set_avatar(struct BotXmppCtx * const bctx, const GString *avatarFile); +int bot_xmpp_set_avatar(struct BotXmppCtx * const bctx, const GString *avatarFile); /* perform xmpp startup, store in bctx if completed successfully */ -int bot_xmpp_startup(struct BotUserInfo user, GString *avatarFile, struct BotXmppCtx * const bctx); +int bot_xmpp_startup(struct BotUserInfo user, GString *avatarFile); /* teardown and free ctx and connection in bctx */ -void bot_xmpp_shutdown(struct BotXmppCtx * const bctx); +void bot_xmpp_shutdown(); + +/* send a reminder when SIGALARM is triggered */ +void reminder_alarm_func(int s); + +/* global context for signal handling */ +extern struct BotXmppCtx *g_bctx; +struct BotXmppCtx *bot_get_ctx(); +void bot_set_ctx(struct BotXmppCtx *bctx); diff --git a/src/bot/handlers.c b/src/bot/handlers.c index 14e2a1e..7fd2040 100644 --- a/src/bot/handlers.c +++ b/src/bot/handlers.c @@ -54,6 +54,7 @@ xmpp_ctx_t *ctx = (xmpp_ctx_t*)userdata; GString *intext; GString *replytext; + BotReminder *reminder; if(!xmpp_stanza_get_child_by_name(stanza, "body")) return 1; if(!strcmp(xmpp_stanza_get_attribute(stanza, "type"), "error")) return 1; @@ -76,8 +77,24 @@ /* parse and store the message */ if(grammar_parse_string(intext) == 0) { - lastId = (bot_get_last_expense())->id; - g_string_printf(replytext, "OK! Ho salvato l'ultima spesa, id: %u\n", lastId); + switch(bot_get_last_command()) { + case BOT_CMD_IN: + case BOT_CMD_OUT: + lastId = (bot_get_last_expense())->id; + 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, "Appena capisco come ti mando il reminder in %d minuti.\n", reminder->minutes); + break; + default: + g_string_printf(replytext, "Non ho riconosciuto il comando: %s", intext->str); + break; + } } else { g_string_printf(replytext, "Stavo rosicchiando una ghianda e non ho capito. Che vuol dire \"%s\"?\n%s\n", intext->str, HELPTEXT); } @@ -119,3 +136,24 @@ xmpp_stop(ctx); } } + +void send_message(xmpp_conn_t * const conn, GString * const from, xmpp_ctx_t * const ctx, const GString *msg) +{ + xmpp_stanza_t *reply, *body, *text; + + reply = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(reply, "message"); + xmpp_stanza_set_type(reply, "chat"); + xmpp_stanza_set_attribute(reply, "to", from->str); + + body = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(body, "body"); + + text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(text, msg->str); + xmpp_stanza_add_child(body, text); + xmpp_stanza_add_child(reply, body); + + xmpp_send(conn, reply); + xmpp_stanza_release(reply); +} diff --git a/src/bot/handlers.h b/src/bot/handlers.h index b19438d..0ab7373 100644 --- a/src/bot/handlers.h +++ b/src/bot/handlers.h @@ -8,6 +8,7 @@ #include #include #include "../../build/expenses/expenses.tab.h" +#include "../grammar/types.h" #define HELPTEXT "---\n\ Comandi:\n\ @@ -26,3 +27,6 @@ /* define a handler for connection events */ void conn_handler(xmpp_conn_t * const conn, const xmpp_conn_event_t status, const int error, xmpp_stream_error_t * const stream_error, void * const userdata); + +/* sends a text message */ +void send_message(xmpp_conn_t * const conn, GString * const from, xmpp_ctx_t * const ctx, const GString *msg); diff --git a/src/grammar/expenses.l b/src/grammar/expenses.l index a01ec48..5ee74f0 100644 --- a/src/grammar/expenses.l +++ b/src/grammar/expenses.l @@ -18,6 +18,7 @@ "-"|out|spesa yylval.cmd=BOT_CMD_OUT; return TOK_CMD_OUT; "+"|in|ricavo yylval.cmd=BOT_CMD_IN; return TOK_CMD_IN; del|storno yylval.cmd=BOT_CMD_DELETE; return TOK_CMD_DEL; +reminder|ricordami yylval.cmd=BOT_CMD_REMINDER; return TOK_CMD_REMINDER; report yylval.cmd=BOT_CMD_REPORT; return TOK_CMD_REPORT; daily|giorno yylval.tsp=BOT_TS_DAILY; return TOK_TS_DAILY; weekly|sett|settimana yylval.tsp=BOT_TS_WEEKLY; return TOK_TS_WEEKLY; diff --git a/src/grammar/expenses.y b/src/grammar/expenses.y index 499cf15..2c0123d 100644 --- a/src/grammar/expenses.y +++ b/src/grammar/expenses.y @@ -39,7 +39,7 @@ %token TOK_INT %token TOK_DOUBLE -%token TOK_CMD_IN TOK_CMD_OUT TOK_CMD_DEL TOK_CMD_REPORT +%token TOK_CMD_IN TOK_CMD_OUT TOK_CMD_DEL TOK_CMD_REPORT TOK_CMD_REMINDER %token TOK_TS_DAILY TOK_TS_WEEKLY TOK_TS_MONTHLY TOK_TS_YEARLY %token TOK_USER /* string for user */ %token TOK_STR /* string for notes */ @@ -63,6 +63,11 @@ 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; + } | TOK_CMD_DEL TOK_INT { printf("delete: %d\n", $2); $$ = 0; } | TOK_CMD_REPORT ts { printf("report: %d\n", $2); $$ = 0; } diff --git a/src/grammar/types.c b/src/grammar/types.c index f33afb1..3c24b08 100644 --- a/src/grammar/types.c +++ b/src/grammar/types.c @@ -2,9 +2,20 @@ */ #include "types.h" +extern BotCommand g_LastCommand = BOT_CMD_NONE; extern BotExpenseEntry *g_LastExpenseEntry = NULL; +extern BotReminder *g_LastReminder = NULL; extern 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; @@ -17,6 +28,18 @@ 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; +} + GString *bot_get_filename() { return g_filename; @@ -193,11 +216,27 @@ 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; } +/* 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; +} /* To be called after it has been serialized */ void free_bot_expense_entry @@ -215,3 +254,12 @@ g_string_free(entry->notes, TRUE); free(entry); } + +void free_bot_reminder +( + BotReminder *reminder +) +{ + g_string_free(reminder->from, true); + free(reminder); +} diff --git a/src/grammar/types.h b/src/grammar/types.h index 63562d8..fd43932 100644 --- a/src/grammar/types.h +++ b/src/grammar/types.h @@ -3,14 +3,19 @@ #pragma once #include #include +#include +#include #include #include +#include typedef enum BotCommand { + BOT_CMD_NONE, BOT_CMD_IN, BOT_CMD_OUT, BOT_CMD_DELETE, - BOT_CMD_REPORT + BOT_CMD_REPORT, + BOT_CMD_REMINDER } BotCommand; typedef enum BotReportTimespan { @@ -29,6 +34,11 @@ GString *notes; } BotExpenseEntry; +typedef struct BotReminder { + GString *from; + unsigned int minutes; +} BotReminder; + /* Parser definitions */ int yylex(); int yyparse(); @@ -80,18 +90,35 @@ bool needsHeaders ); +/* set a SIGALARM to be raised in 'minutes'; also sets the last reminder */ +int start_alarm_reminder(unsigned int minutes); + /* To be called after it has been serialized */ void free_bot_expense_entry ( BotExpenseEntry *entry ); +void free_bot_reminder +( + BotReminder *reminder + ); + +/* use this to check the last received command when responding */ +extern BotCommand g_LastCommand; +BotCommand bot_get_last_command(); +void bot_set_last_command(BotCommand cmd); /* store the last expense in memory */ extern BotExpenseEntry *g_LastExpenseEntry; BotExpenseEntry *bot_get_last_expense(); void bot_set_last_expense(BotExpenseEntry *entry); +/* store the last reminder in memory */ +extern BotReminder *g_LastReminder; +BotReminder *bot_get_last_reminder(); +void bot_set_last_reminder(BotReminder *reminder); + /* store the filename in memory */ extern GString *g_filename; GString *bot_get_filename(); diff --git a/src/main.c b/src/main.c index 49d04a4..4da4ae7 100644 --- a/src/main.c +++ b/src/main.c @@ -11,7 +11,7 @@ GString *filename; GString *avatarFile; struct BotUserInfo user; - struct BotXmppCtx bctx; + struct BotXmppCtx *bctx; int startup_code; /* take a jid and password on the command line */ @@ -35,19 +35,22 @@ bot_set_filename(filename); /* perform the startup and connect routine */ - startup_code = bot_xmpp_startup(user, avatarFile, &bctx); + startup_code = bot_xmpp_startup(user, avatarFile); + if(startup_code != 0) { /* something went wrong at startup */ return startup_code; } + bctx = bot_get_ctx(); + /* enter the event loop - our connect handler will trigger an exit */ - xmpp_run(bctx.ctx); + xmpp_run(bctx->ctx); /* release our connection and context */ /* final shutdown of the library */ - bot_xmpp_shutdown(&bctx); + bot_xmpp_shutdown(); /* Free user data, leave underlying strings as they are part of argv*/ bot_free_user_data(user, filename, false);