Add all core functions for minion module

This commit is contained in:
Ilja Kartašov 2019-06-28 15:59:02 +02:00
parent 45b4f6bee0
commit b9e247b88f
5 changed files with 693 additions and 71 deletions

View File

@ -18,6 +18,7 @@
#include <uuid.h> #include <uuid.h>
#include <errno.h> #include <errno.h>
#include <cStuff/string.h> #include <cStuff/string.h>
#include <cStuff/file.h>
#include <components/log.h> #include <components/log.h>
#include "todo.h" #include "todo.h"
@ -25,19 +26,12 @@
AislStatus AislStatus
ax_todo_init(AxTodo self, const char *file) ax_todo_init(AxTodo self, const char *file)
{ {
FILE *f; uuid_generate(self->list_id);
uuid_generate(self->id);
if (!(f = fopen(file, "a+"))) { self->file = file;
return AISL_SYSCALL_ERROR;
}
if (cstuff_list_init(&self->list, 16) != CSTUFF_SUCCESS) { if (cstuff_sprintf(&self->swap, "%s.swap", file) == -1)
fclose(f);
return AISL_MALLOC_ERROR; return AISL_MALLOC_ERROR;
}
self->fd = f;
return AISL_SUCCESS; return AISL_SUCCESS;
} }
@ -46,32 +40,126 @@ ax_todo_init(AxTodo self, const char *file)
void void
ax_todo_release(AxTodo self) ax_todo_release(AxTodo self)
{ {
if (self->fd) if (self->swap)
fclose(self->fd); free(self->swap);
}
cstuff_list_release(&self->list, (CStuffListFree)ax_todo_task_free);
static AislStatus
ax_todo_add_project(CStuffList list, time_t completed, char *key, size_t k_len)
{
int i;
AxTodoProject proj;
for (i = 0; i < list->length; i++) {
proj = list->items[i];
if (strncmp(proj->name, key, k_len) == 0 && !proj->name[k_len]) {
proj->ref_count++;
if (proj->last_completed < completed) {
proj->last_completed = completed;
}
return true;
}
}
if (!(proj = calloc(1, sizeof (*proj))))
return false;
if (cstuff_strncpy(&proj->name, key, k_len) != -1) {
proj->ref_count = 1;
proj->last_completed = completed;
uuid_generate(proj->project_id);
if (cstuff_list_append(list, proj) != -1)
return true;
}
ax_todo_project_free(proj);
return false;
}
static AislStatus
ax_todo_add_tag(CStuffList list, char *key, size_t k_len)
{
int i;
AxTodoTag tag;
for (i = 0; i < list->length; i++) {
tag = list->items[i];
if (strncmp(tag->name, key, k_len) == 0 && !tag->name[k_len]) {
tag->ref_count++;
return true;
}
}
if (!(tag = calloc(1, sizeof (*tag))))
return false;
if (cstuff_strncpy(&tag->name, key, k_len) != -1) {
tag->ref_count = 1;
uuid_generate(tag->tag_id);
if (cstuff_list_append(list, tag) != -1)
return true;
}
ax_todo_tag_free(tag);
return false;
}
static AislStatus
ax_todo_read_keys(AxTodoTask task, CStuffList projs, CStuffList tags)
{
const char tokens[] = "+@";
char *s = task->description;
size_t offset = 0;
for (;;) {
char t;
offset = strcspn(s, tokens);
s += offset;
if ((t = *(s++)) == 0)
break;
offset = strcspn(s, " ");
if (offset) {
if ((t == '+' && !ax_todo_add_project(projs, task->completed, s, offset))
|| (t == '@' && !ax_todo_add_tag(tags, s, offset))) {
return AISL_MALLOC_ERROR;
}
s += offset;
}
}
return AISL_SUCCESS;
} }
AislStatus AislStatus
ax_todo_read(AxTodo self) ax_todo_read(AxTodo self, CStuffList tasks, CStuffList projs, CStuffList tags)
{ {
FILE *f = self->fd; FILE *f = NULL;
size_t sz = 256; size_t sz = 256;
char *buf; char *buf;
ssize_t rs; ssize_t rs;
AislStatus result = AISL_SUCCESS; AislStatus result = AISL_SUCCESS;
if ((fseek(f, 0L, SEEK_SET) != 0))
return AISL_SYSCALL_ERROR;
if (!(buf = malloc(sz))) if (!(buf = malloc(sz)))
return AISL_MALLOC_ERROR; return AISL_MALLOC_ERROR;
if (!(f = fopen(self->file, "r")))
goto e_syscall;
while ((rs = getdelim(&buf, &sz, '\n', f)) != -1) { while ((rs = getdelim(&buf, &sz, '\n', f)) != -1) {
AxTodoTask task; AxTodoTask task;
if (!(task = ax_todo_task_new())) if (!(task = ax_todo_task_new(self->list_id)))
goto e_malloc; goto e_malloc;
if (ax_todo_task_set(task, buf, rs) != 0) { if (ax_todo_task_set(task, buf, rs) != 0) {
@ -79,10 +167,14 @@ ax_todo_read(AxTodo self)
goto e_input; goto e_input;
} }
if (cstuff_list_append(&self->list, task) == -1) { if (cstuff_list_append(tasks, task) == -1) {
ax_todo_task_free(task); ax_todo_task_free(task);
goto e_malloc; goto e_malloc;
} }
if ((result = ax_todo_read_keys(task, projs, tags)) != AISL_SUCCESS) {
goto e_malloc;
}
} }
switch(errno) { switch(errno) {
@ -109,6 +201,8 @@ e_syscall:
finally: finally:
free(buf); free(buf);
if (f)
fclose(f);
return result; return result;
} }
@ -124,52 +218,60 @@ ax_todo__write_dt(FILE *f, time_t dt)
AislStatus AislStatus
ax_todo_write(AxTodo self) ax_todo_write(AxTodo self, CStuffList list)
{ {
FILE *f = self->fd; FILE *f;
int i; int i;
if ((fseek(f, 0L, SEEK_SET) != 0)) if (!(f = fopen(self->swap, "w")))
return AISL_SYSCALL_ERROR; return AISL_SYSCALL_ERROR;
f = self->fd; for (i = 0; i < list->length; i++) {
AxTodoTask task = (AxTodoTask) list->items[i];
for (i = 0; i < self->list.length; i++) { if (uuid_compare(task->list_id, self->list_id) != 0)
AxTodoTask task = (AxTodoTask) self->list.items[i]; continue;
if (task->is_done) { if (task->is_done) {
fwrite("x ", 1, 2, f); fwrite("x ", 1, 2, f);
} }
if (task->priority) { if (task->priority && task->priority != AX_TODO_ZERO_PRIORITY) {
fprintf(f, "(%c) ", task->priority); fprintf(f, "(%c) ", task->priority);
} }
if (task->is_done) { if (task->is_done) {
if (task->completion) { if (task->completed) {
ax_todo__write_dt(f, task->completion); ax_todo__write_dt(f, task->completed);
if (task->creation) { if (task->created) {
ax_todo__write_dt(f, task->creation); ax_todo__write_dt(f, task->created);
} }
} }
} else if (task->creation) { } else if (task->created) {
ax_todo__write_dt(f, task->creation); ax_todo__write_dt(f, task->created);
} }
fprintf(f, "%s\n", task->description); fprintf(f, "%s\n", task->description);
} }
return (fflush(f) == 0) ? AISL_SUCCESS : AISL_SYSCALL_ERROR; fclose(f);
if (cstuff_file_move(self->swap, self->file) != 0)
return AISL_SYSCALL_ERROR;
return AISL_SUCCESS;
} }
AxTodoTask AxTodoTask
ax_todo_task_new(void) ax_todo_task_new(uuid_t list_id)
{ {
AxTodoTask result; AxTodoTask result;
if ((result = calloc(1, sizeof (struct ax_todo_task))) != NULL) { if ((result = calloc(1, sizeof (*result))) != NULL) {
uuid_copy(result->list_id, list_id);
uuid_generate(result->task_id); uuid_generate(result->task_id);
result->priority = AX_TODO_ZERO_PRIORITY;
} }
return result; return result;
@ -220,7 +322,7 @@ ax_todo__get_priority(char *buf, char *p_priority)
} }
static int int
ax_todo__get_dt(char *buf, time_t *p_dt) ax_todo__get_dt(char *buf, time_t *p_dt)
{ {
struct tm stm = {0}; struct tm stm = {0};
@ -228,6 +330,8 @@ ax_todo__get_dt(char *buf, time_t *p_dt)
const char fmt[] = "Y-M-DTH:m:s.uZB:b ", *f = fmt; const char fmt[] = "Y-M-DTH:m:s.uZB:b ", *f = fmt;
char *ptr = NULL; char *ptr = NULL;
setenv("TZ", "UTC", 1);
for (result = 0; *f != 0; result++, f++) { for (result = 0; *f != 0; result++, f++) {
switch(*f) { switch(*f) {
case 'Y': case 'Y':
@ -329,7 +433,6 @@ ax_todo__get_dt(char *buf, time_t *p_dt)
stm.tm_min += (b_min * z_bias); stm.tm_min += (b_min * z_bias);
} }
setenv("TZ", "UTC", 1);
*p_dt = mktime(&stm); *p_dt = mktime(&stm);
unsetenv("TZ"); unsetenv("TZ");
@ -337,12 +440,33 @@ ax_todo__get_dt(char *buf, time_t *p_dt)
} }
e_input: e_input:
AX_LOG_DEBUG("Parse error: %c(%d) at %d", *f, (int) (f - fmt), result); /* AX_LOG_DEBUG("Parse error: %c(%d) at %d", *f, (int) (f - fmt), result); */
return 0; return 0;
(void) usec; (void) usec;
} }
void
ax_todo__remove_line_break(char *description)
{
int move = 0;
char c;
do {
c = *description;
if (c == '\r' || c == '\n') {
move++;
continue;
}
*(description - move) = c;
} while (c != '\0');
}
AislStatus AislStatus
ax_todo_task_set(AxTodoTask task, char *buf, ssize_t length) ax_todo_task_set(AxTodoTask task, char *buf, ssize_t length)
{ {
@ -362,11 +486,11 @@ ax_todo_task_set(AxTodoTask task, char *buf, ssize_t length)
buf += l; buf += l;
} }
l = ax_todo__get_dt(buf, (is_done) ? &task->completion : &task->creation); l = ax_todo__get_dt(buf, (is_done) ? &task->completed : &task->created);
if (l != 0) { if (l != 0) {
buf += l; buf += l;
if (is_done && (l = ax_todo__get_dt(buf, &task->creation)) != 0) { if (is_done && (l = ax_todo__get_dt(buf, &task->created)) != 0) {
buf += l; buf += l;
} }
} }
@ -381,3 +505,20 @@ ax_todo_task_set(AxTodoTask task, char *buf, ssize_t length)
return AISL_SUCCESS; return AISL_SUCCESS;
} }
void
ax_todo_project_free(AxTodoProject project)
{
if (project->name)
free(project->name);
free(project);
}
void
ax_todo_tag_free(AxTodoTag tag)
{
if (tag->name)
free(tag->name);
free(tag);
}

View File

@ -22,28 +22,49 @@
#include <aisl/aisl.h> #include <aisl/aisl.h>
#include <cStuff/list.h> #include <cStuff/list.h>
#define AX_TODO_ZERO_PRIORITY '_'
struct ax_todo { struct ax_todo {
uuid_t id; uuid_t list_id;
FILE *fd; const char *file;
struct cstuff_list list; char *swap;
/* todo: add file timestamp */
}; };
typedef struct ax_todo * AxTodo; typedef struct ax_todo * AxTodo;
struct ax_todo_task { struct ax_todo_task {
uuid_t list_id;
uuid_t task_id; uuid_t task_id;
char priority; time_t completed;
time_t completion; time_t created;
time_t creation;
char *description; char *description;
bool is_done; bool is_done;
char priority;
}; };
typedef struct ax_todo_task * AxTodoTask; typedef struct ax_todo_task * AxTodoTask;
struct ax_todo_project {
uuid_t project_id;
time_t last_completed;
char *name;
uint32_t ref_count;
};
typedef struct ax_todo_project * AxTodoProject;
struct ax_todo_tag {
uuid_t tag_id;
char *name;
uint32_t ref_count;
};
typedef struct ax_todo_tag * AxTodoTag;
AislStatus AislStatus
ax_todo_init(AxTodo self, const char *file); ax_todo_init(AxTodo self, const char *file);
@ -53,15 +74,15 @@ ax_todo_release(AxTodo self);
AislStatus AislStatus
ax_todo_read(AxTodo self); ax_todo_read(AxTodo self, CStuffList tasks, CStuffList projs, CStuffList tags);
AislStatus AislStatus
ax_todo_write(AxTodo self); ax_todo_write(AxTodo self, CStuffList list);
AxTodoTask AxTodoTask
ax_todo_task_new(void); ax_todo_task_new(uuid_t list_id);
void void
@ -72,8 +93,24 @@ void
ax_todo_task_free(AxTodoTask task); ax_todo_task_free(AxTodoTask task);
int
ax_todo__get_dt(char *buf, time_t *p_dt);
void
ax_todo__remove_line_break(char *description);
AislStatus AislStatus
ax_todo_task_set(AxTodoTask task, char *buf, ssize_t length); ax_todo_task_set(AxTodoTask task, char *buf, ssize_t length);
void
ax_todo_project_free(AxTodoProject project);
void
ax_todo_tag_free(AxTodoTag tag);
#endif /* !TODO_H */ #endif /* !TODO_H */

View File

@ -14,6 +14,7 @@
*/ */
#include <stdlib.h> #include <stdlib.h>
#include <inttypes.h>
#include <uuid.h> #include <uuid.h>
#include <string.h> #include <string.h>
#include <components/query.h> #include <components/query.h>
@ -37,7 +38,6 @@ struct context {
struct ax_context root; struct ax_context root;
struct ax_query qs; struct ax_query qs;
struct ax_todo_task task; struct ax_todo_task task;
uuid_t file_id;
AxMinionMode mode; AxMinionMode mode;
}; };
@ -45,6 +45,9 @@ struct context {
typedef struct context * Context; typedef struct context * Context;
typedef int
(*AxMinionCompare)(void *object1, void *object2);
static const char modName[] = "minion"; static const char modName[] = "minion";
@ -56,7 +59,7 @@ context_new(const char *path, AislHttpMethod method, AxMinion mod)
if (!(ctx = (Context)ax_context_new(AX_MODULE(mod)))) if (!(ctx = (Context)ax_context_new(AX_MODULE(mod))))
return ctx; return ctx;
if (!(*path)) if (strcmp(path, "today/") == 0)
ctx->mode = AX_MINION_TODAY; ctx->mode = AX_MINION_TODAY;
else if (strcmp(path, "create/") == 0) else if (strcmp(path, "create/") == 0)
ctx->mode = AX_MINION_CREATE_TASK; ctx->mode = AX_MINION_CREATE_TASK;
@ -67,6 +70,8 @@ context_new(const char *path, AislHttpMethod method, AxMinion mod)
else if (strcmp(path, "delete/") == 0) else if (strcmp(path, "delete/") == 0)
ctx->mode = AX_MINION_DELETE_TASK; ctx->mode = AX_MINION_DELETE_TASK;
ctx->task.priority = AX_TODO_ZERO_PRIORITY;
return ctx; return ctx;
} }
@ -81,6 +86,116 @@ context_free(Context ctx)
} }
static int
ax_minion_compare_tasks(AxTodoTask task1, AxTodoTask task2)
{
if (task1->is_done) {
if (task2->is_done) {
if (task1->completed > task2->completed) {
return -1;
} else if (task1->completed > task2->completed) {
return 1;
}
} else {
return 1;
}
} else {
if (task2->is_done) {
return -1;
} else {
if (task1->priority < task2->priority) {
return -1;
} else if (task1->priority == task2->priority) {
if (task1->created > task2->created) {
return -1;
} else if (task1->created < task2->created) {
return 1;
}
} else {
return 1;
}
}
}
return 0;
}
static int
ax_minion_compare_projects(AxTodoProject proj1, AxTodoProject proj2)
{
if (proj1->last_completed < proj2->last_completed) {
return -1;
} else if (proj1->last_completed > proj2->last_completed) {
return 1;
}
if (proj1->ref_count > proj2->ref_count) {
return -1;
} else if (proj1->ref_count < proj2->ref_count) {
return 1;
}
return 0;
}
static int
ax_minion_compare_tags(AxTodoTag tag1, AxTodoTag tag2)
{
if (tag1->ref_count > tag2->ref_count) {
return -1;
} else if (tag1->ref_count < tag2->ref_count) {
return 1;
}
return 0;
}
static void
ax_minion_sort(CStuffList list, AxMinionCompare compare_objects)
{
int l = list->length, ld = l - 1, i, j;
void **data = list->items, *swap;
for (i = 0; i < l; i++) {
int c = i + 1;
for (j = ld; j >= c; j--) {
if (compare_objects((AxTodoTask)data[j], (AxTodoTask)data[j - 1]) == -1) {
swap = data[j];
data[j] = data[j - 1];
data[j - 1] = swap;
}
}
}
}
static void
on_input_dt(const char *val, uint32_t v_len, time_t *p_out)
{
char str_dt[36 + 1];
if (!(*p_out) && !(v_len < 10 || v_len > sizeof (str_dt) - 1)) {
strncpy(str_dt, val, v_len);
str_dt[v_len] = 0;
(void) ax_todo__get_dt(str_dt, p_out);
}
}
static void
on_input_uuid(const char *val, uint32_t v_len, uuid_t uuid_out)
{
char str_uuid[36 + 1];
if (v_len == 36) {
strncpy(str_uuid, val, v_len);
str_uuid[v_len] = 0;
uuid_parse(str_uuid, uuid_out);
}
}
static int static int
on_input_var(const char *key, uint32_t k_len, const char *val, uint32_t v_len, on_input_var(const char *key, uint32_t k_len, const char *val, uint32_t v_len,
void *p_ctx) void *p_ctx)
@ -89,16 +204,33 @@ on_input_var(const char *key, uint32_t k_len, const char *val, uint32_t v_len,
/* AxMinion mod = (AxMinion)((AxContext)ctx)->mod; */ /* AxMinion mod = (AxMinion)((AxContext)ctx)->mod; */
char **out = NULL; char **out = NULL;
if (!ctx->task.description && k_len == 11) { if (k_len == 1) {
if (strncmp(key, "description", k_len) == 0) { if (*key == 't') {
out = &ctx->task.description; out = &ctx->task.description;
} else if (*key == 'x') {
ctx->task.is_done = (v_len == 4 && strncmp(val, "true", 4) == 0);
} else if (*key == 'p') {
if (v_len == 1 && !((*val < 0x41 || *val > 0x5A))) {
ctx->task.priority = *val;
}
}
} else if (k_len == 2) {
if (strncmp(key, "id", 2) == 0) {
on_input_uuid(val, v_len, ctx->task.task_id);
} else if (strncmp(key, "li", 2) == 0) {
on_input_uuid(val, v_len, ctx->task.list_id);
} else if (strncmp(key, "co", 2) == 0) {
on_input_dt(val, v_len, &ctx->task.completed);
} else if (strncmp(key, "cr", 2) == 0) {
on_input_dt(val, v_len, &ctx->task.created);
} }
} }
if (out) { if (out && !(*out)) {
if (cstuff_strncpy(out, val, v_len) == -1) if (cstuff_strncpy(out, val, v_len) == -1)
return -1; return -1;
ax_query__decode(*out); ax_query__decode(*out);
ax_todo__remove_line_break(*out);
} }
return 0; return 0;
} }
@ -127,6 +259,8 @@ on_stream_header(AxMinion mod, const struct aisl_evt_header *evt)
AislStream s = (AislStream)evt->evt.source; AislStream s = (AislStream)evt->evt.source;
Context ctx = aisl_get_context(s); Context ctx = aisl_get_context(s);
AX_LOG_DEBUG("header: %s = %s", evt->key, evt->value);
if (strcmp(evt->key, "content-length") == 0) { if (strcmp(evt->key, "content-length") == 0) {
size_t total = strtoll(evt->value, NULL, 10); size_t total = strtoll(evt->value, NULL, 10);
@ -143,6 +277,7 @@ on_stream_input(AxMinion mod, const struct aisl_evt_input *evt)
{ {
Context ctx = aisl_get_context((AislStream)evt->evt.source); Context ctx = aisl_get_context((AislStream)evt->evt.source);
AX_LOG_DEBUG("input[%d]: %s", evt->size, evt->data);
ax_query_feed(&ctx->qs, evt->data, evt->size); ax_query_feed(&ctx->qs, evt->data, evt->size);
return AISL_SUCCESS; return AISL_SUCCESS;
@ -157,11 +292,83 @@ ax_minion_date2str(time_t tt, char *str_dt, size_t sz)
} }
static bool
ax_minion_today(AxMinion mod, AislStream s, Context ctx)
{
/* sprint: [{task1}, {task2}]
*/
return true;
}
static bool
ax_minion_create_task(AxMinion mod, AislStream s, Context ctx)
{
AxTodoTask task;
char str_uuid[36 + 1];
if (!(ctx->task.description)) {
AX_LOG_STATE("%s: !description", modName);
return false;
}
if (uuid_compare(ctx->task.list_id, mod->todo.list_id) != 0) {
AX_LOG_STATE("%s: !list_id", modName);
return false;
}
if (!(task = ax_todo_task_new(ctx->task.list_id)))
goto e_system;
task->is_done = ctx->task.is_done;
task->priority = ctx->task.priority;
if (!(task->created = ctx->task.created))
time(&task->created);
task->completed = ctx->task.completed;
task->description = ctx->task.description;
uuid_copy(task->list_id, ctx->task.list_id);
ctx->task.description = NULL;
if (cstuff_list_append(&mod->tasks, task) == -1) {
ax_todo_task_free(task);
goto e_system;
}
if (ax_todo_write(&mod->todo, &mod->tasks) != AISL_SUCCESS)
goto e_system;
if (aisl_header(s, "Content-Type", "text/json") == -1)
goto e_reject;
uuid_unparse_lower(task->list_id, str_uuid);
if (aisl_printf(s, "{\"id\": \"%s\"}", str_uuid) == -1)
goto e_reject;
if (aisl_flush(s) != AISL_SUCCESS)
goto e_reject;
goto finally;
e_reject:
aisl_reject(s);
goto finally;
e_system:
ax_quick_response(s, AISL_HTTP_INTERNAL_SERVER_ERROR);
goto finally;
finally:
return true;
}
static void static void
ax_minion_stream_today(AxMinion mod, AislStream s, Context ctx) ax_minion_read_task(AxMinion mod, AislStream s, Context ctx)
{ {
int i; int i;
CStuffList lst = &mod->todo.list; char str_buf[36 + 1];
CStuffList lst;
if (aisl_response(s, AISL_HTTP_OK, AISL_AUTO_LENGTH) != AISL_SUCCESS) if (aisl_response(s, AISL_HTTP_OK, AISL_AUTO_LENGTH) != AISL_SUCCESS)
goto e_reject; goto e_reject;
@ -169,41 +376,110 @@ ax_minion_stream_today(AxMinion mod, AislStream s, Context ctx)
if (aisl_header(s, "Content-Type", "text/json") == -1) if (aisl_header(s, "Content-Type", "text/json") == -1)
goto e_reject; goto e_reject;
/* tasks */
if (aisl_write(s, "{\"tasks\":[", 10) == -1) if (aisl_write(s, "{\"tasks\":[", 10) == -1)
goto e_reject; goto e_reject;
lst = &mod->tasks;
for (i = 0; i < lst->length; i++) { for (i = 0; i < lst->length; i++) {
AxTodoTask task = lst->items[i]; AxTodoTask task = lst->items[i];
int rc; int rc;
char dt[32];
if (i != 0 && aisl_write(s, ",", 1) == -1) if (i != 0 && aisl_write(s, ",", 1) == -1)
goto e_reject; goto e_reject;
if (aisl_printf(s, "{\"x\":%s,", task->is_done ? "true" : "false") == -1) uuid_unparse_lower(task->task_id, str_buf);
if (aisl_printf(s, "\n{\"id\":\"%s\",", str_buf) == -1)
goto e_reject;
uuid_unparse_lower(task->list_id, str_buf);
if (aisl_printf(s, "\"li\":\"%s\",", str_buf) == -1)
goto e_reject;
if (aisl_printf(s, "\"x\":%s,", task->is_done ? "true" : "false") == -1)
goto e_reject; goto e_reject;
rc = aisl_printf(s, "\"p\":\"%c\",", task->priority ? task->priority : '0'); rc = aisl_printf(s, "\"p\":\"%c\",", task->priority ? task->priority : '0');
if (rc == -1) if (rc == -1)
goto e_reject; goto e_reject;
if (task->is_done && task->completion) { if (task->is_done && task->completed) {
ax_minion_date2str(task->completion, dt, sizeof(dt)); ax_minion_date2str(task->completed, str_buf, sizeof(str_buf));
if (aisl_printf(s, "\"co\":\"%s\",", dt) == -1) if (aisl_printf(s, "\"co\":\"%s\",", str_buf) == -1)
goto e_reject; goto e_reject;
} }
if (task->creation) { if (task->created) {
ax_minion_date2str(task->creation, dt, sizeof(dt)); ax_minion_date2str(task->created, str_buf, sizeof(str_buf));
if (aisl_printf(s, "\"cr\":\"%s\",", dt) == -1) if (aisl_printf(s, "\"cr\":\"%s\",", str_buf) == -1)
goto e_reject; goto e_reject;
} }
rc = aisl_printf(s, "\"d\":\"%s\"}", task->description); rc = aisl_printf(s, "\"t\":\"%s\"}", task->description);
if (rc == -1) if (rc == -1)
goto e_reject; goto e_reject;
} }
/* projects */
if (aisl_write(s, "\n], \"projects\":[", 16) == -1)
goto e_reject;
lst = &mod->projects;
for (i = 0; i < lst->length; i++) {
AxTodoProject proj = lst->items[i];
int rc;
if (i != 0 && aisl_write(s, ",", 1) == -1)
goto e_reject;
uuid_unparse_lower(proj->project_id, str_buf);
if (aisl_printf(s, "\n{\"id\":\"%s\",", str_buf) == -1)
goto e_reject;
if (aisl_printf(s, "\"n\":\"%s\",", proj->name) == -1)
goto e_reject;
rc = aisl_printf(s, "\"r\":%"PRIu32"", proj->ref_count);
if (rc == -1)
goto e_reject;
if (proj->last_completed) {
ax_minion_date2str(proj->last_completed, str_buf, sizeof(str_buf));
if (aisl_printf(s, ",\"lc\":\"%s\"", str_buf) == -1)
goto e_reject;
}
if (aisl_write(s, "}", 1) == -1)
goto e_reject;
}
/* tags */
if (aisl_write(s, "\n], \"tags\":[", 12) == -1)
goto e_reject;
lst = &mod->tags;
for (i = 0; i < lst->length; i++) {
AxTodoTag tag = lst->items[i];
int rc;
if (i != 0 && aisl_write(s, ",", 1) == -1)
goto e_reject;
uuid_unparse_lower(tag->tag_id, str_buf);
if (aisl_printf(s, "\n{\"id\":\"%s\",", str_buf) == -1)
goto e_reject;
if (aisl_printf(s, "\"n\":\"%s\",", tag->name) == -1)
goto e_reject;
rc = aisl_printf(s, "\"r\":%"PRIu32"}", tag->ref_count);
if (rc == -1)
goto e_reject;
}
if (aisl_write(s, "]}", 2) == -1) if (aisl_write(s, "]}", 2) == -1)
goto e_reject; goto e_reject;
@ -213,10 +489,135 @@ ax_minion_stream_today(AxMinion mod, AislStream s, Context ctx)
return; return;
e_reject: e_reject:
AX_LOG_DEBUG("%s: reject", modName);
aisl_reject(s); aisl_reject(s);
} }
static bool
ax_minion_update_task(AxMinion mod, AislStream s, Context ctx)
{
AxTodoTask task = NULL;
char str_uuid[36 + 1];
int i;
if (!(ctx->task.description)) {
AX_LOG_STATE("%s: !description", modName);
return false;
}
if (uuid_compare(ctx->task.list_id, mod->todo.list_id) != 0) {
AX_LOG_STATE("%s: !list_id", modName);
return false;
}
for (i = 0; i < mod->tasks.length; i++) {
task = (AxTodoTask) mod->tasks.items[i];
if (uuid_compare(task->task_id, ctx->task.task_id) == 0)
break;
task = NULL;
}
if (!task) {
AX_LOG_STATE("%s: !task_id", modName);
return false;
}
if (!(task->priority = ctx->task.priority))
task->priority = AX_TODO_ZERO_PRIORITY;
task->created = ctx->task.created;
task->completed = ctx->task.completed;
if ((task->is_done = ctx->task.is_done) && !task->completed) {
time(&task->completed);
}
if (task->description)
free(task->description);
task->description = ctx->task.description;
ctx->task.description = NULL;
uuid_copy(task->list_id, ctx->task.list_id);
if (ax_todo_write(&mod->todo, &mod->tasks) != AISL_SUCCESS)
goto e_system;
if (aisl_header(s, "Content-Type", "text/json") == -1)
goto e_reject;
uuid_unparse_lower(task->list_id, str_uuid);
if (aisl_printf(s, "{\"id\": \"%s\"}", str_uuid) == -1)
goto e_reject;
if (aisl_flush(s) != AISL_SUCCESS)
goto e_reject;
goto finally;
e_reject:
aisl_reject(s);
goto finally;
e_system:
ax_quick_response(s, AISL_HTTP_INTERNAL_SERVER_ERROR);
goto finally;
finally:
return true;
}
static bool
ax_minion_delete_task(AxMinion mod, AislStream s, Context ctx)
{
AxTodoTask task = NULL;
char str_uuid[36 + 1];
int i;
for (i = 0; i < mod->tasks.length; i++) {
task = (AxTodoTask) mod->tasks.items[i];
if (uuid_compare(task->task_id, ctx->task.task_id) == 0) {
cstuff_list_remove(&mod->tasks, i);
break;
}
task = NULL;
}
if (!task) {
AX_LOG_STATE("%s: !task_id", modName);
return false;
}
uuid_unparse_lower(task->list_id, str_uuid);
ax_todo_task_free(task);
if (ax_todo_write(&mod->todo, &mod->tasks) != AISL_SUCCESS)
goto e_system;
if (aisl_header(s, "Content-Type", "text/json") == -1)
goto e_reject;
if (aisl_printf(s, "{\"id\": \"%s\"}", str_uuid) == -1)
goto e_reject;
if (aisl_flush(s) != AISL_SUCCESS)
goto e_reject;
goto finally;
e_reject:
aisl_reject(s);
goto finally;
e_system:
ax_quick_response(s, AISL_HTTP_INTERNAL_SERVER_ERROR);
goto finally;
finally:
return true;
}
static AislStatus static AislStatus
on_stream_request(AxMinion mod, const struct aisl_evt *evt) on_stream_request(AxMinion mod, const struct aisl_evt *evt)
{ {
@ -227,7 +628,27 @@ on_stream_request(AxMinion mod, const struct aisl_evt *evt)
AX_LOG_STATE("%s: request mode = %d", modName, ctx->mode); AX_LOG_STATE("%s: request mode = %d", modName, ctx->mode);
switch(ctx->mode) { switch(ctx->mode) {
case AX_MINION_TODAY: case AX_MINION_TODAY:
ax_minion_stream_today(mod, s, ctx); if (!ax_minion_today(mod, s, ctx))
goto bad_request;
break;
case AX_MINION_CREATE_TASK:
if (!ax_minion_create_task(mod, s, ctx))
goto bad_request;
break;
case AX_MINION_READ_TASK:
ax_minion_read_task(mod, s, ctx);
break;
case AX_MINION_UPDATE_TASK:
if (!ax_minion_update_task(mod, s, ctx))
goto bad_request;
break;
case AX_MINION_DELETE_TASK:
if (!ax_minion_delete_task(mod, s, ctx))
goto bad_request;
break; break;
default: default:
@ -288,9 +709,26 @@ ax_minion_init(AxMinion mod, const struct ax_minion_cfg *cfg)
if ((result = ax_todo_init(&mod->todo, cfg->todo_file)) != AISL_SUCCESS) if ((result = ax_todo_init(&mod->todo, cfg->todo_file)) != AISL_SUCCESS)
return result; return result;
if ((result = ax_todo_read(&mod->todo)) != AISL_SUCCESS) if ((cstuff_list_init(&mod->tasks, 32)) != CSTUFF_SUCCESS)
return AISL_MALLOC_ERROR;
if ((cstuff_list_init(&mod->projects, 8)) != CSTUFF_SUCCESS)
return AISL_MALLOC_ERROR;
if ((cstuff_list_init(&mod->tags, 32)) != CSTUFF_SUCCESS)
return AISL_MALLOC_ERROR;
if ((result = ax_todo_read(&mod->todo, &mod->tasks, &mod->projects,
&mod->tags)) != AISL_SUCCESS)
return result; return result;
ax_minion_sort(&mod->tasks, (AxMinionCompare)ax_minion_compare_tasks);
ax_minion_sort(&mod->projects, (AxMinionCompare)ax_minion_compare_projects);
ax_minion_sort(&mod->tags, (AxMinionCompare)ax_minion_compare_tags);
if (ax_todo_write(&mod->todo, &mod->tasks) != AISL_SUCCESS) {
AX_LOG_DEBUG("Write failed ;(");
}
return result; return result;
} }
@ -299,4 +737,7 @@ void
ax_minion_release(AxMinion mod) ax_minion_release(AxMinion mod)
{ {
ax_todo_release(&mod->todo); ax_todo_release(&mod->todo);
cstuff_list_release(&mod->tasks, (CStuffListFree) ax_todo_task_free);
cstuff_list_release(&mod->projects, (CStuffListFree) ax_todo_project_free);
cstuff_list_release(&mod->tags, (CStuffListFree) ax_todo_tag_free);
} }

View File

@ -36,6 +36,9 @@ struct ax_minion_cfg {
struct ax_minion { struct ax_minion {
struct ax_module root; struct ax_module root;
struct ax_todo todo; struct ax_todo todo;
struct cstuff_list tasks;
struct cstuff_list projects;
struct cstuff_list tags;
}; };

View File

@ -46,7 +46,7 @@ struct ax_module {
const char *end_point; /**< Root mod's URL or NULL */ const char *end_point; /**< Root mod's URL or NULL */
AxObserver on_event; /**< Mod's stream event observer */ AxObserver on_event; /**< Mod's stream event observer */
size_t ctx_size; /**< Mod's context size */ size_t ctx_size; /**< Mod's context size */
uint8_t ep_length; /**< End-Point length */ uint32_t ep_length; /**< End-Point length */
}; };