From 45b4f6bee0e0667df01403f94def5d1df9b2a9af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilja=20Karta=C5=A1ov?= Date: Mon, 24 Jun 2019 07:42:56 +0200 Subject: [PATCH] Add minion module and todo component --- components/todo.c | 383 ++++++++++++++++++++++++++++++++++++++++++++++ components/todo.h | 79 ++++++++++ mods/minion.c | 302 ++++++++++++++++++++++++++++++++++++ mods/minion.h | 52 +++++++ 4 files changed, 816 insertions(+) create mode 100644 components/todo.c create mode 100644 components/todo.h create mode 100644 mods/minion.c create mode 100644 mods/minion.h diff --git a/components/todo.c b/components/todo.c new file mode 100644 index 0000000..eb31ecf --- /dev/null +++ b/components/todo.c @@ -0,0 +1,383 @@ +/****************************************************************************** + * + * Copyright (c) 2017-2019 by Löwenware Ltd + * Please, refer LICENSE file for legal information + * + ******************************************************************************/ + +/** + * @file todo.c + * @author Ilja Kartašov + * @brief + * + * @see https://lowenware.com/ + */ + +#include +#include +#include +#include +#include +#include +#include "todo.h" + + +AislStatus +ax_todo_init(AxTodo self, const char *file) +{ + FILE *f; + uuid_generate(self->id); + + if (!(f = fopen(file, "a+"))) { + return AISL_SYSCALL_ERROR; + } + + if (cstuff_list_init(&self->list, 16) != CSTUFF_SUCCESS) { + fclose(f); + return AISL_MALLOC_ERROR; + } + + self->fd = f; + + return AISL_SUCCESS; +} + + +void +ax_todo_release(AxTodo self) +{ + if (self->fd) + fclose(self->fd); + + cstuff_list_release(&self->list, (CStuffListFree)ax_todo_task_free); +} + + +AislStatus +ax_todo_read(AxTodo self) +{ + FILE *f = self->fd; + size_t sz = 256; + char *buf; + ssize_t rs; + AislStatus result = AISL_SUCCESS; + + if ((fseek(f, 0L, SEEK_SET) != 0)) + return AISL_SYSCALL_ERROR; + + if (!(buf = malloc(sz))) + return AISL_MALLOC_ERROR; + + while ((rs = getdelim(&buf, &sz, '\n', f)) != -1) { + AxTodoTask task; + + if (!(task = ax_todo_task_new())) + goto e_malloc; + + if (ax_todo_task_set(task, buf, rs) != 0) { + ax_todo_task_free(task); + goto e_input; + } + + if (cstuff_list_append(&self->list, task) == -1) { + ax_todo_task_free(task); + goto e_malloc; + } + } + + switch(errno) { + case 0: + goto finally; + + case ENOMEM: + goto e_malloc; + + default: + goto e_syscall; + } + +e_input: + result = AISL_INPUT_ERROR; + goto finally; + +e_malloc: + result = AISL_MALLOC_ERROR; + goto finally; + +e_syscall: + result = AISL_SYSCALL_ERROR; + +finally: + free(buf); + return result; +} + + +static void +ax_todo__write_dt(FILE *f, time_t dt) +{ + char str_dt[16]; + struct tm *p_stm = gmtime(&dt); + (void) strftime(str_dt, sizeof (str_dt), "%Y-%m-%d", p_stm); + fprintf(f, "%s ", str_dt); +} + + +AislStatus +ax_todo_write(AxTodo self) +{ + FILE *f = self->fd; + int i; + + if ((fseek(f, 0L, SEEK_SET) != 0)) + return AISL_SYSCALL_ERROR; + + f = self->fd; + + for (i = 0; i < self->list.length; i++) { + AxTodoTask task = (AxTodoTask) self->list.items[i]; + + if (task->is_done) { + fwrite("x ", 1, 2, f); + } + + if (task->priority) { + fprintf(f, "(%c) ", task->priority); + } + + if (task->is_done) { + if (task->completion) { + ax_todo__write_dt(f, task->completion); + if (task->creation) { + ax_todo__write_dt(f, task->creation); + } + } + } else if (task->creation) { + ax_todo__write_dt(f, task->creation); + } + + fprintf(f, "%s\n", task->description); + } + + return (fflush(f) == 0) ? AISL_SUCCESS : AISL_SYSCALL_ERROR; +} + + +AxTodoTask +ax_todo_task_new(void) +{ + AxTodoTask result; + + if ((result = calloc(1, sizeof (struct ax_todo_task))) != NULL) { + uuid_generate(result->task_id); + } + + return result; +} + + +void +ax_todo_task_release(AxTodoTask task) +{ + if (task->description) + free(task->description); +} + + +void +ax_todo_task_free(AxTodoTask task) +{ + ax_todo_task_release(task); + free(task); +} + + +static int +ax_todo__get_priority(char *buf, char *p_priority) +{ + int result; + char priority = 0; + const char fmt[] = {'(', 'A', ')', ' '}; + + for (result = 0; result < 4; result++) { + switch (result) { + case 0: + case 2: + case 3: + if (buf[result] == fmt[result]) + continue; + break; + case 1: + priority = buf[result]; + if (!(priority < 0x41 || priority > 0x5A)) + continue; + break; + } + return 0; + } + *p_priority = priority; + return result; +} + + +static int +ax_todo__get_dt(char *buf, time_t *p_dt) +{ + struct tm stm = {0}; + int result, *p_num = NULL, b_hour = 0, b_min = 0, usec = 0, z_bias = 0; + const char fmt[] = "Y-M-DTH:m:s.uZB:b ", *f = fmt; + char *ptr = NULL; + + for (result = 0; *f != 0; result++, f++) { + switch(*f) { + case 'Y': + p_num = &stm.tm_year; + break; + case 'M': + p_num = &stm.tm_mon; + break; + case 'D': + p_num = &stm.tm_mday; + break; + case 'H': + p_num = &stm.tm_hour; + break; + case 'm': + p_num = &stm.tm_min; + break; + case 's': + p_num = &stm.tm_sec; + break; + case 'u': + p_num = &usec; + break; + case 'B': + p_num = &b_hour; + break; + case 'b': + p_num = &b_min; + break; + + case '.': + if (buf[result] != *f) { + f++; + } + continue; + + case 'T': + if (buf[result] == *f) + continue; + + if (buf[result] == ' ') { + f = &fmt[17]; + continue; + } + goto e_input; + + case 'Z': + if (buf[result] == *f) { + f += 3; + z_bias = 'Z'; + } else if (buf[result] == '-') { + z_bias = -1; + } else if (buf[result] == '+') { + z_bias = 1; + } else { + goto e_input; + } + continue; + + case '-': + case ':': + case ' ': + if (buf[result] == *f) + continue; + /* go through */ + default: + goto e_input; + } /* switch */ + + *p_num = strtol(&buf[result], &ptr, 10); + + if (!ptr || ptr == &buf[result]) { + if (f > &fmt[6] && buf[result] == ' ') { + f = &fmt[17]; + /* skip optional part of timestamp */ + continue; + } + goto e_input; + } + + result += (ptr - &buf[result] - 1); + } /* for */ + + /* validate results */ + /* ToDo: improve (stm.tm_mday < 31) check */ + if (!(stm.tm_year < 1900) && (stm.tm_mon) && (stm.tm_mday) && + (stm.tm_mon < 13) && (stm.tm_mday < 31) && (stm.tm_hour < 24) && + (stm.tm_min < 60) && (stm.tm_sec < 60)) { + stm.tm_year -= 1900; + stm.tm_mon--; + + if (z_bias == 0) { /* local time zone */ + *p_dt = mktime(&stm); + return result; + } + + if (z_bias != 'Z') { + stm.tm_hour += (b_hour * z_bias); + stm.tm_min += (b_min * z_bias); + } + + setenv("TZ", "UTC", 1); + *p_dt = mktime(&stm); + unsetenv("TZ"); + + return result; + } + +e_input: + AX_LOG_DEBUG("Parse error: %c(%d) at %d", *f, (int) (f - fmt), result); + return 0; + (void) usec; +} + + +AislStatus +ax_todo_task_set(AxTodoTask task, char *buf, ssize_t length) +{ + bool is_done; + int l; + + if (strncmp(buf, "x ", 2) == 0) { + is_done = true; + buf += 2; + } else { + is_done = false; + } + + task->is_done = is_done; + + if ((l = ax_todo__get_priority(buf, &task->priority)) != 0) { + buf += l; + } + + l = ax_todo__get_dt(buf, (is_done) ? &task->completion : &task->creation); + + if (l != 0) { + buf += l; + if (is_done && (l = ax_todo__get_dt(buf, &task->creation)) != 0) { + buf += l; + } + } + + if ((l = cstuff_strcpy(&task->description, buf)) == -1) { + return AISL_MALLOC_ERROR; + } + + if (l > 1 && task->description[l-1] == '\n') + task->description[l-1] = 0; + + return AISL_SUCCESS; +} + diff --git a/components/todo.h b/components/todo.h new file mode 100644 index 0000000..aea58d8 --- /dev/null +++ b/components/todo.h @@ -0,0 +1,79 @@ +/****************************************************************************** + * + * Copyright (c) 2017-2019 by Löwenware Ltd + * Please, refer LICENSE file for legal information + * + ******************************************************************************/ + +/** + * @file todo.h + * @author Ilja Kartašov + * @brief ToDo.txt module + * + * @see https://lowenware.com/ + */ + +#ifndef TODO_H_FAD28DF0_DA05_4436_88E5_E3715DA254E8 +#define TODO_H_FAD28DF0_DA05_4436_88E5_E3715DA254E8 + +#include +#include +#include +#include +#include + + +struct ax_todo { + uuid_t id; + FILE *fd; + struct cstuff_list list; +}; + +typedef struct ax_todo * AxTodo; + + +struct ax_todo_task { + uuid_t task_id; + char priority; + time_t completion; + time_t creation; + char *description; + bool is_done; +}; + +typedef struct ax_todo_task * AxTodoTask; + + +AislStatus +ax_todo_init(AxTodo self, const char *file); + + +void +ax_todo_release(AxTodo self); + + +AislStatus +ax_todo_read(AxTodo self); + + +AislStatus +ax_todo_write(AxTodo self); + + +AxTodoTask +ax_todo_task_new(void); + + +void +ax_todo_task_release(AxTodoTask task); + + +void +ax_todo_task_free(AxTodoTask task); + + +AislStatus +ax_todo_task_set(AxTodoTask task, char *buf, ssize_t length); + + +#endif /* !TODO_H */ diff --git a/mods/minion.c b/mods/minion.c new file mode 100644 index 0000000..f19de6c --- /dev/null +++ b/mods/minion.c @@ -0,0 +1,302 @@ +/****************************************************************************** + * + * Copyright (c) 2017-2019 by Löwenware Ltd + * Please, refer LICENSE file for legal information + * + ******************************************************************************/ + +/** + * @file todo.c + * @author Ilja Kartašov + * @brief AISL ToDo.txt module code file + * + * @see https://lowenware.com/ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + + +typedef enum { + AX_MINION_UNDEFINED + , AX_MINION_TODAY + , AX_MINION_CREATE_TASK + , AX_MINION_READ_TASK + , AX_MINION_UPDATE_TASK + , AX_MINION_DELETE_TASK +} AxMinionMode; + + +struct context { + struct ax_context root; + struct ax_query qs; + struct ax_todo_task task; + uuid_t file_id; + + AxMinionMode mode; +}; + +typedef struct context * Context; + + +static const char modName[] = "minion"; + + +static Context +context_new(const char *path, AislHttpMethod method, AxMinion mod) +{ + Context ctx; + + if (!(ctx = (Context)ax_context_new(AX_MODULE(mod)))) + return ctx; + + if (!(*path)) + ctx->mode = AX_MINION_TODAY; + else if (strcmp(path, "create/") == 0) + ctx->mode = AX_MINION_CREATE_TASK; + else if (strcmp(path, "read/") == 0) + ctx->mode = AX_MINION_READ_TASK; + else if (strcmp(path, "update/") == 0) + ctx->mode = AX_MINION_UPDATE_TASK; + else if (strcmp(path, "delete/") == 0) + ctx->mode = AX_MINION_DELETE_TASK; + + return ctx; +} + + +static void +context_free(Context ctx) +{ + ax_query_release(&ctx->qs); + ax_todo_task_release(&ctx->task); + + ax_context_free((AxContext)ctx); +} + + +static int +on_input_var(const char *key, uint32_t k_len, const char *val, uint32_t v_len, + void *p_ctx ) +{ + Context ctx = (Context) p_ctx; + /* AxMinion mod = (AxMinion)((AxContext)ctx)->mod; */ + char **out = NULL; + + if (!ctx->task.description && k_len == 11) { + if (strncmp(key, "description", k_len) == 0) { + out = &ctx->task.description; + } + } + + if (out) { + if (cstuff_strncpy(out, val, v_len) == -1) + return -1; + ax_query__decode(*out); + } + return 0; +} + + +static AislStatus +on_stream_open(AxMinion mod, const struct aisl_evt_open *evt) +{ + Context ctx; + AislStream s = (AislStream)evt->evt.source; + + if (!(ctx = context_new(&evt->path[AX_MODULE(mod)->ep_length], + evt->http_method, mod))) { + return AISL_MALLOC_ERROR; + } + + aisl_set_context(s, ctx); + + return AISL_SUCCESS; +} + + +static AislStatus +on_stream_header(AxMinion mod, const struct aisl_evt_header *evt) +{ + AislStream s = (AislStream)evt->evt.source; + Context ctx = aisl_get_context(s); + + if (strcmp(evt->key, "content-length") == 0) { + size_t total = strtoll(evt->value, NULL, 10); + + if(ax_query_init(&ctx->qs, total, on_input_var, (void*)ctx) != 0) { + ax_quick_response(s, AISL_HTTP_INTERNAL_SERVER_ERROR); + } + } + return AISL_SUCCESS; +} + + +static AislStatus +on_stream_input(AxMinion mod, const struct aisl_evt_input *evt) +{ + Context ctx = aisl_get_context((AislStream)evt->evt.source); + + ax_query_feed(&ctx->qs, evt->data, evt->size); + + return AISL_SUCCESS; +} + + +static void +ax_minion_date2str(time_t tt, char *str_dt, size_t sz) +{ + struct tm *p_tm = gmtime(&tt); + strftime(str_dt, sz, "%Y-%m-%d", p_tm); +} + + +static void +ax_minion_stream_today(AxMinion mod, AislStream s, Context ctx) +{ + int i; + CStuffList lst = &mod->todo.list; + + if (aisl_response(s, AISL_HTTP_OK, AISL_AUTO_LENGTH) != AISL_SUCCESS) + goto e_reject; + + if (aisl_header(s, "Content-Type", "text/json") == -1) + goto e_reject; + + if (aisl_write(s, "{\"tasks\":[", 10) == -1) + goto e_reject; + + for (i = 0; i < lst->length; i++) { + AxTodoTask task = lst->items[i]; + int rc; + char dt[32]; + + if (i != 0 && aisl_write(s, ",", 1) == -1) + goto e_reject; + + if (aisl_printf(s, "{\"x\":%s,", task->is_done ? "true" : "false") == -1) + goto e_reject; + + rc = aisl_printf(s, "\"p\":\"%c\",", task->priority ? task->priority : '0'); + if (rc == -1) + goto e_reject; + + if (task->is_done && task->completion) { + ax_minion_date2str(task->completion, dt, sizeof(dt)); + if (aisl_printf(s, "\"co\":\"%s\",", dt) == -1) + goto e_reject; + } + + if (task->creation) { + ax_minion_date2str(task->creation, dt, sizeof(dt)); + if (aisl_printf(s, "\"cr\":\"%s\",", dt) == -1) + goto e_reject; + } + + rc = aisl_printf(s, "\"d\":\"%s\"}", task->description); + if (rc == -1) + goto e_reject; + } + + if (aisl_write(s, "]}", 2) == -1) + goto e_reject; + + if (aisl_flush(s) != AISL_SUCCESS) + goto e_reject; + + return; + +e_reject: + aisl_reject(s); +} + + +static AislStatus +on_stream_request(AxMinion mod, const struct aisl_evt *evt) +{ + Context ctx = aisl_get_context((AislStream)evt->source); + AislStream s = (AislStream)evt->source; + + /* verify input */ + AX_LOG_STATE("%s: request mode = %d", modName, ctx->mode); + switch(ctx->mode) { + case AX_MINION_TODAY: + ax_minion_stream_today(mod, s, ctx); + break; + + default: + goto bad_request; + } + + goto finally; + +bad_request: + ax_quick_response(s, AISL_HTTP_BAD_REQUEST); + +finally: + return AISL_SUCCESS; +} + + +static AislStatus +on_stream_close(AxMinion mod, const struct aisl_evt *evt) +{ + context_free((Context)aisl_get_context((AislStream)evt->source)); + return AISL_SUCCESS; +} + + +static AislStatus +ax_minion_on_event(AxMinion mod, const struct aisl_evt *evt) +{ + switch(evt->code) { + case AISL_EVENT_STREAM_OPEN: + return on_stream_open(mod, (const struct aisl_evt_open *)evt); + + case AISL_EVENT_STREAM_HEADER: + return on_stream_header(mod, (const struct aisl_evt_header *)evt); + + case AISL_EVENT_STREAM_INPUT: + return on_stream_input(mod, (const struct aisl_evt_input *)evt); + + case AISL_EVENT_STREAM_REQUEST: + return on_stream_request(mod, evt); + + case AISL_EVENT_STREAM_CLOSE: + return on_stream_close(mod, evt); + + default: + return AISL_IDLE; + } +} + + + +AislStatus +ax_minion_init(AxMinion mod, const struct ax_minion_cfg *cfg) +{ + AislStatus result; + + AX_MODULE_INIT(ax_minion, cfg->end_point); + + if ((result = ax_todo_init(&mod->todo, cfg->todo_file)) != AISL_SUCCESS) + return result; + + if ((result = ax_todo_read(&mod->todo)) != AISL_SUCCESS) + return result; + + return result; +} + + +void +ax_minion_release(AxMinion mod) +{ + ax_todo_release(&mod->todo); +} diff --git a/mods/minion.h b/mods/minion.h new file mode 100644 index 0000000..f5df019 --- /dev/null +++ b/mods/minion.h @@ -0,0 +1,52 @@ +/****************************************************************************** + * + * Copyright (c) 2017-2019 by Löwenware Ltd + * Please, refer LICENSE file for legal information + * + ******************************************************************************/ + +/** + * @file mod-feedback.h + * @author Ilja Kartašov + * @brief AISL ToDo.txt module header file + * + * @see https://lowenware.com/aisl/ + */ + +#ifndef MINION_H2FC5C912_0F59_43AD_B805_B0F7C82BE2EF +#define MINION_H2FC5C912_0F59_43AD_B805_B0F7C82BE2EF + +#include +#include +#include +#include + +/* ToDo: + * Start with one todo.txt file + * Write ToDo.txt parser + * Join them together + */ + +struct ax_minion_cfg { + const char *end_point; + const char *todo_file; +}; + + +struct ax_minion { + struct ax_module root; + struct ax_todo todo; +}; + + +typedef struct ax_minion *AxMinion; + + +AislStatus +ax_minion_init(AxMinion mod, const struct ax_minion_cfg *cfg); + + +void +ax_minion_release(AxMinion mod); + +#endif /* !MINION_H */