dodo/src/dodo.c
xavi fc8af9a53c Fixed bug that was looking for NULL instead of nullterm
TODO: Should prob use pointers over strict arrays
2024-09-17 21:39:54 -07:00

515 lines
13 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include "sqlite3.h"
#include "x_string.h"
#include "x_curses.h"
#include "dodo.h"
// FOR DEBUG
int callback(void *NotUsed, int argc, char **argv, char **azColName){
int i;
for(i=0; i<argc; i++){
printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
}
printf("\n");
return 0;
}
// FOR DEBUG
int view_all(sqlite3 *db){
int rc = 0;
char* errmsg;
//rc = sqlite3_exec(db, "SELECT * FROM all_info;", print_select, 0, &errmsg);
rc = sqlite3_exec(db, "SELECT * FROM tasks;", callback, 0, &errmsg);
//rc = sqlite3_exec(db, "SELECT * FROM entries;", print_select, 0, &errmsg);
checksqlerr(rc, errmsg);
return rc;
}
int gen_sql_update_stmt_v2(sqlite3 *db, sqlite3_stmt** out_stmt, char* table, char* new_title, char* new_due_date, char* new_project_tag, char* title_or_active_id){
char sql_query[SQLQUERY_MAX];
int rc = 0;
if (new_title[0] != '\0' && new_due_date[0] != '\0'){
//snprintf(sql_query, SQLQUERY_MAX, "UPDATE %s SET title='%s', due_date='%s', project_tag='%s' WHERE title='%s' OR active_id='%s'", table, status, title_or_active_id, title_or_active_id);
snprintf(sql_query, SQLQUERY_MAX, "UPDATE %s SET title='%s', due_date='%s' WHERE title='%s' OR active_id='%s'", table, new_title, new_due_date, title_or_active_id, title_or_active_id);
}
else if (new_title[0] != '\0'){
snprintf(sql_query, SQLQUERY_MAX, "UPDATE %s SET title='%s' WHERE title='%s' OR active_id='%s'", table, new_title, title_or_active_id, title_or_active_id);
}
else{
return -1;
}
rc = sqlite3_prepare_v2(db, sql_query, -1, out_stmt, NULL);
if (checksqlerr(rc, "prepare broken in gen_sql_insert_stmt")){
return -1;
}
return 0;
}
// sql generators TODO: might be able to boil this down to 1 func
int gen_sql_update_stmt(sqlite3 *db, sqlite3_stmt** out_stmt, char* table, char* status, char* title_or_active_id){
char sql_query[SQLQUERY_MAX];
int rc = 0;
if (status != NULL){
snprintf(sql_query, SQLQUERY_MAX, "UPDATE %s SET status='%s' WHERE title='%s' OR active_id='%s'", table, status, title_or_active_id, title_or_active_id);
}
else{
snprintf(sql_query, SQLQUERY_MAX, "UPDATE %s SET active_id='NULL',status='complete' WHERE title='%s' OR active_id='%s'", table, title_or_active_id, title_or_active_id);
}
rc = sqlite3_prepare_v2(db, sql_query, -1, out_stmt, NULL);
if (checksqlerr(rc, "prepare broken in gen_sql_insert_stmt")){
return -1;
}
return 0;
}
int gen_sql_delete_stmt(sqlite3 *db, sqlite3_stmt** out_stmt, char* table, char* title_or_active_id){
char sql_query[SQLQUERY_MAX];
int rc = 0;
snprintf(sql_query, SQLQUERY_MAX, "DELETE FROM %s WHERE title='%s' OR active_id='%s'", table, title_or_active_id, title_or_active_id);
rc = sqlite3_prepare_v2(db, sql_query, -1, out_stmt, NULL);
if (checksqlerr(rc, "prepare broken in gen_sql_insert_stmt")){
return -1;
}
return 0;
}
int gen_sql_insert_stmt(sqlite3 *db, sqlite3_stmt** out_stmt, char* table, char* colnames, char* values){
char sql_query[SQLQUERY_MAX];
int rc = 0;
snprintf(sql_query, SQLQUERY_MAX, "INSERT INTO %s %s VALUES %s", table, colnames, values);
rc = sqlite3_prepare_v2(db, sql_query, -1, out_stmt, NULL);
if (checksqlerr(rc, "prepare broken in gen_sql_insert_stmt")){
return -1;
}
return 0;
}
// Generate the sql statement by giving the columns, table, and current task list wanted
int gen_sql_select_stmt(sqlite3 *db, sqlite3_stmt** out_stmt, filtered_tasks* task){
char sql_query[SQLQUERY_MAX];
int rc = 0;
snprintf(sql_query, SQLQUERY_MAX, "SELECT %s FROM %s WHERE status='%s' AND active_id!='NULL' ORDER BY due_date NULLS LAST", task->selected_columns, task->table, task->status);
rc = sqlite3_prepare_v2(db, sql_query, -1, out_stmt, NULL);
if (checksqlerr(rc, "prepare broken in gen_sql_select_stmt")){
return -1;
}
return 0;
}
int checksqlerr(int rc, char *errmsg){
if( rc!=SQLITE_OK ){
fprintf(stderr, "rc = %d\n", rc);
fprintf(stderr, "SQL error: %s\n", errmsg);
//sqlite3_free(errmsg);
return -1;
}
return 0;
}
int opendb(sqlite3 **db, char* filename){
int rc = 0;
rc = sqlite3_open(filename, db);
if ( rc != 0 ){
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(*db));
sqlite3_close(*db);
return(rc);
}
return(rc);
}
void display_task_list_heading(char* heading){
int width = (FIXED_ID_WIDTH + FIXED_TITLE_WIDTH + FIXED_DATE_WIDTH + x_strlen(heading) );
int width_div_2 = width / 2;
printf("%s%*s", X_BOLD, width_div_2, heading);
printf("%*s", width_div_2 - x_strlen(heading), "");
printf(" ");
}
void display_column_heading(const char* str){
int width = 0;
if ( x_strcmp(str, "id") == 0 ){
width = FIXED_ID_WIDTH;
}
else if ( x_strcmp(str, "title") == 0 ){
width = FIXED_TITLE_WIDTH;
}
else if ( x_strcmp(str, "due_date") == 0 ){
width = FIXED_DATE_WIDTH;
}
else{
width = FIXED_MAX_WIDTH;
}
printf("%s%s%-*.*s%s", X_BOLD, X_UNDL, width, width, str, X_RST);
}
void display_column_headings_for_all_task_lists(){
int i = 0;
char* str;
for ( i = 0; i < NUM_TASK_LISTS; i++){
str = "id";
display_column_heading(str);
str = "title";
display_column_heading(str);
str = "due_date";
display_column_heading(str);
printf(" ");
}
printf("\n");
}
// Print Heading
void display_heading(){
char * str;
printf("\n");
str = "Today";
display_task_list_heading(str);
str = "Backlog";
display_task_list_heading(str);
str = "Blocked";
display_task_list_heading(str);
printf("\n");
display_column_headings_for_all_task_lists();
}
// pass in the args and return the title and due date
// due date passed as NULL if for delete
// TODO input validation for strings implement in strings!
// TODO maybe a Abstract Sytnax Tree for parsing
int parse_args(int argc, char** argv, filtered_tasks* task){
if ( argc > 2 ){
x_strcpy(task->title, argv[2], ARG_MAX);
}
if ( argc > 3 ){
if ( x_strcmp(argv[1], "update") ){
x_strcpy(task->due_date, argv[3], ARG_MAX);
return 0;
}
else{
x_strcpy(task->new_title, argv[3], ARG_MAX);
}
}
if ( argc > 4 ){
x_strcpy(task->due_date, argv[4], ARG_MAX);
}
if ( argc > 5 ){
x_strcpy(task->project_tag, argv[5], ARG_MAX);
}
return 0;
}
// Get number of tasks from tasks table give status
int get_num_rows(sqlite3 *db, char* table, char* status){
filtered_tasks* task = malloc(sizeof(filtered_tasks));
x_strcpy(task->table, table, ARG_MAX);
x_strcpy(task->status, status, ARG_MAX);
x_strcpy(task->selected_columns, "COUNT(*)", ARG_MAX);
sqlite3_stmt *out_stmt;
int rc = 0;
if ( gen_sql_select_stmt(db, &out_stmt, task) == 1 ){
return -1;
}
free(task);
while ( rc = sqlite3_step(out_stmt) == SQLITE_ROW ){
return sqlite3_column_int(out_stmt, 0);
}
return rc;
}
// Print with FIXED_WIDTH
int print_fixed_width(const unsigned char* str, int width){
if (str){
printf("%-*.*s", width, width - FIXED_WHITESPACE, str);
}else{
printf("%-*.*s", width, width, "");
}
}
int display_task_list(int start_col, sqlite3 *db, filtered_tasks* task){
static int max_rows = -1;
int rc = 0;
int i = 0;
int num_rows = -1;
int num_cols = 0;
int fixed_width = 0;
char* errmsg;
const unsigned char* col_val;
const unsigned char* col_name;
sqlite3_stmt* out_stmt;
// Get the number of rows in current task list
num_rows = get_num_rows(db, task->table, task->status);
// Then keep track of the furthest down we go
if (num_rows > max_rows){
max_rows = num_rows;
}
// Generate the sql statement by giving the columns, table, and current task list wanted
if ( gen_sql_select_stmt(db, &out_stmt, task) ){
return -1;
}
// TODO: prob should be a func begin
// Start col for the current task list
X_goright(start_col);
// while there is still rows available
while ( rc = sqlite3_step(out_stmt) == SQLITE_ROW ){
// for each column print the column
num_cols = sqlite3_column_count(out_stmt);
for (i = 0; i < num_cols; i++){
col_val = sqlite3_column_text(out_stmt, i);
col_name = sqlite3_column_name(out_stmt, i);
if ( x_strcmp(col_name, "title") == 0 ){
fixed_width = FIXED_TITLE_WIDTH;
}
else if ( x_strcmp(col_name, "active_id") == 0 ){
fixed_width = FIXED_ID_WIDTH;
}
else if ( x_strcmp(col_name, "due_date") == 0 ){
fixed_width = FIXED_DATE_WIDTH;
}
print_fixed_width(col_val, fixed_width);
}
// move down one and over to the start of the current task column
printf("\n");
// end
X_goright(start_col);
}
// if while loop broke and rc returned an error
if ( rc == SQLITE_ERROR ){
X_godown(max_rows);
checksqlerr(rc, "step broken in display_task_list");
return -1;
}
// Once the task list is completely printed
// reset to the top of the task lists
if (num_rows > 0){
X_goup(num_rows);
}
// if it is the last task list move down one past the longest list
if ( x_strcmp(task->status, "blocked") == 0){
X_godown(max_rows);
printf("\n");
}
return 0;
}
// Print kanban table
// All lists with task name and due date
int view_tasks(sqlite3 *db){
// Set the table and cols to be printed
filtered_tasks* tasks = malloc(sizeof(filtered_tasks));
// TODO check that malloc is ok
x_strcpy(tasks->table, "tasks", ARG_MAX);
x_strcpy(tasks->selected_columns, "active_id, title, due_date", ARG_MAX);
// Print Heading
display_heading();
// Print "today" tasks
x_strcpy(tasks->status, "today", ARG_MAX);
if ( display_task_list(TODAY_COL_START, db, tasks) ){
free(tasks);
return -1;
}
// Print "backlog" tasks
x_strcpy(tasks->status, "backlog", ARG_MAX);
if ( display_task_list(BACKLOG_COL_START, db, tasks) ){
free(tasks);
return -1;
}
// Print "blocked" tasks
x_strcpy(tasks->status, "blocked", ARG_MAX);
if ( display_task_list(BLOCKED_COL_START, db, tasks) ){
free(tasks);
return -1;
}
free(tasks);
return 0;
}
// TODO: the way this ensures that we are only passing in
// valid inputs is stupid and ugly FIX
int add_new_task(sqlite3 *db, int argc, char** argv){
int rc = 0;
char values[ARG_MAX];
sqlite3_stmt* out_stmt;
filtered_tasks* task = malloc(sizeof(filtered_tasks));
x_strcpy(task->table, "tasks", ARG_MAX);
task->title[0] = '\0';
task->due_date[0] = '\0';
parse_args(argc, argv, task);
if ( task->title[0] != '\0' ){
if ( task->due_date[0] != '\0' ){
x_strcpy(task->selected_columns, "(title, due_date)", ARG_MAX);
snprintf(values, 100, "('%s', '%s')", task->title, task->due_date);
}else{
x_strcpy(task->selected_columns, "(title)", ARG_MAX);
snprintf(values, 100, "('%s')", task->title);
}
}
if ( gen_sql_insert_stmt(db, &out_stmt, task->table, task->selected_columns, values) ){
return -1;
}
if ( ( rc = sqlite3_step(out_stmt) ) == SQLITE_DONE){
return 0;
}
checksqlerr(rc, "broken in add_new_task");
return -1;
}
int update_task_status(sqlite3 *db, int argc, char** argv){
int rc = 0;
sqlite3_stmt* out_stmt;
filtered_tasks* task = malloc(sizeof(filtered_tasks));
x_strcpy(task->table, "tasks", ARG_MAX);
// TODO: this is not just title but also active ID so fix this
parse_args(argc, argv, task);
if ( gen_sql_update_stmt(db, &out_stmt, task->table, argv[1], task->title) ){
free(task);
return -1;
}
if ( ( rc = sqlite3_step(out_stmt) ) == SQLITE_DONE){
free(task);
return 0;
}
checksqlerr(rc, "broken in update_task_status");
free(task);
return -1;
}
int complete_task(sqlite3 *db, int argc, char** argv){
int rc = 0;
sqlite3_stmt* out_stmt;
filtered_tasks* task = malloc(sizeof(filtered_tasks));
x_strcpy(task->table, "tasks", ARG_MAX);
parse_args(argc, argv, task);
if ( gen_sql_update_stmt(db, &out_stmt, task->table, NULL, task->title) ){
free(task);
return -1;
}
if ( ( rc = sqlite3_step(out_stmt) ) == SQLITE_DONE){
free(task);
return 0;
}
checksqlerr(rc, "broken in complete_task");
free(task);
return -1;
}
// TODO this can definatly be made into one func with all the other updates
int update_task(sqlite3 *db, int argc, char** argv){
int rc = 0;
sqlite3_stmt* out_stmt;
filtered_tasks* task = malloc(sizeof(filtered_tasks));
task->new_title[0] = '\0';
task->due_date[0] = '\0';
x_strcpy(task->table, "tasks", ARG_MAX);
parse_args(argc, argv, task);
if ( gen_sql_update_stmt_v2(db, &out_stmt, task->table, task->new_title, task->due_date, task->project_tag, task->title) ){
free(task);
return -1;
}
if ( ( rc = sqlite3_step(out_stmt) ) == SQLITE_DONE){
free(task);
return 0;
}
checksqlerr(rc, "broken in complete_task");
free(task);
return -1;
}
int del_task(sqlite3 *db, int argc, char** argv){
int rc = 0;
sqlite3_stmt* out_stmt;
filtered_tasks* task = malloc(sizeof(filtered_tasks));
x_strcpy(task->table, "tasks", ARG_MAX);
parse_args(argc, argv, task);
if ( gen_sql_delete_stmt(db, &out_stmt, task->table, task->title) ){
free(task);
return -1;
}
if ( ( rc = sqlite3_step(out_stmt) ) == SQLITE_DONE){
free(task);
return 0;
}
checksqlerr(rc, "broken in del_task");
free(task);
return -1;
}