#include #include #include #include "sqlite3.h" #include "x_string.h" #include "x_curses.h" #include "x_ctypes.h" #include "dodo.h" //TODO: check that only one row is modified with sqlite3_change() // FOR DEBUG int callback(void *NotUsed, int argc, char **argv, char **azColName){ int i; for(i=0; iyear = local->tm_year + 1900; d->month = local->tm_mon; d->year = local->tm_mday; return 0; } void init_filtered_tasks(filtered_tasks* task){ task->selected_columns = NULL; task->title = NULL; task->new_title = NULL; task->active_id = -1; task->status = NULL; task->project_tag = NULL; task->due_date = NULL; } char* concat_with_mem_cleanup(char* str1, char* str2){ char *temp; if ( str1 == NULL || str2 == NULL ){ return NULL; } temp = str1; str1 = x_strconcat(str1, str2); free(temp); return str1; } int prepare_sql_update_stmt(sqlite3 *db, sqlite3_stmt** out_stmt, filtered_tasks* task){ char *sql_query; int rc = 0; int num_params = 0; sql_query = malloc(SQLQUERY_MAX * sizeof(char)); x_strcpy(sql_query,"UPDATE tasks SET ", SQLQUERY_MAX); if ( task->new_title ){ sql_query = concat_with_mem_cleanup(sql_query, "title=?"); num_params++; } if ( task->due_date ){ if ( num_params > 0 ){ sql_query = concat_with_mem_cleanup(sql_query, ","); } sql_query = concat_with_mem_cleanup(sql_query, "due_date=?"); num_params++; } if ( task->status ){ if ( num_params > 0 ){ sql_query = concat_with_mem_cleanup(sql_query, ","); } sql_query = concat_with_mem_cleanup(sql_query, "status=?"); num_params++; if ( !(x_strcmp(task->status, "complete")) ){ sql_query = concat_with_mem_cleanup(sql_query, ","); sql_query = concat_with_mem_cleanup(sql_query, "active_id='NULL'"); } } sql_query = concat_with_mem_cleanup(sql_query, " WHERE "); if ( task->title ){ sql_query = concat_with_mem_cleanup(sql_query, "title=?"); num_params++; } else if ( task->active_id != -1 ){ sql_query = concat_with_mem_cleanup(sql_query, "active_id=?"); num_params++; } else { free(sql_query); return -1; } rc = sqlite3_prepare_v2(db, sql_query, -1, out_stmt, NULL); free(sql_query); if (checksqlerr(rc, "prepare broken in gen_sql_insert_stmt")){ return -1; } return num_params; } int bind_sql_update_stmt(filtered_tasks* task, int num_params, sqlite3_stmt* out_stmt){ int max_param = 0; int param_pos = 0; int rc = 0; max_param = num_params; if ( task->new_title ){ param_pos = max_param - num_params + 1; rc = sqlite3_bind_text(out_stmt, param_pos, task->new_title, -1, SQLITE_STATIC); num_params--; } if (checksqlerr(rc, "prepare broken in bind_sql_update_stmt")){ return -1; } if ( task->due_date ){ param_pos = max_param - num_params + 1; rc = sqlite3_bind_text(out_stmt, param_pos, task->due_date, -1, SQLITE_STATIC); num_params--; } if (checksqlerr(rc, "prepare broken in bind_sql_update_stmt")){ return -1; } if ( task->status ){ param_pos = max_param - num_params + 1; rc = sqlite3_bind_text(out_stmt, param_pos, task->status, -1, SQLITE_STATIC); num_params--; } if ( task->title ){ param_pos = max_param - num_params + 1; rc = sqlite3_bind_text(out_stmt, param_pos, task->title, -1, SQLITE_STATIC); num_params--; } else if ( task->active_id != -1 ){ param_pos = max_param - num_params + 1; rc = sqlite3_bind_int(out_stmt, param_pos, task->active_id); num_params--; } else{ return -1; } if (checksqlerr(rc, "prepare broken in bind_sql_update_stmt")){ return -1; } return 0; } int prepare_sql_delete_stmt(sqlite3 *db, sqlite3_stmt** out_stmt, filtered_tasks* task){ char *sql_query; int rc = 0; sql_query = malloc(SQLQUERY_MAX * sizeof(char)); x_strcpy(sql_query,"DELETE FROM tasks WHERE ", SQLQUERY_MAX); if ( task->title ){ sql_query = concat_with_mem_cleanup(sql_query, "title=? AND active_id IS NOT NULL"); } else if ( task->active_id != -1 ){ sql_query = concat_with_mem_cleanup(sql_query, "active_id=?"); } else{ free(sql_query); return -1; } rc = sqlite3_prepare_v2(db, sql_query, -1, out_stmt, NULL); free(sql_query); if (checksqlerr(rc, "prepare broken in gen_sql_insert_stmt")){ return -1; } return 0; } int bind_sql_delete_stmt(filtered_tasks* task, sqlite3_stmt* out_stmt){ int rc = 0; const int param_pos = 1; if ( task->title ){ rc = sqlite3_bind_text(out_stmt, param_pos, task->title, -1, SQLITE_STATIC); } else if ( task->active_id != -1 ){ rc = sqlite3_bind_int(out_stmt, param_pos, task->active_id); } else{ return -1; } if (checksqlerr(rc, "prepare broken in gen_sql_insert_stmt")){ return -1; } return 0; } int prepare_sql_insert_stmt(sqlite3 *db, sqlite3_stmt** out_stmt, filtered_tasks* task, char* values){ char sql_query[SQLQUERY_MAX]; int rc = 0; snprintf(sql_query, SQLQUERY_MAX, "INSERT INTO tasks %s VALUES %s", task->selected_columns, 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; } int bind_sql_insert_stmt(filtered_tasks* task, sqlite3_stmt* out_stmt){ int rc = 0; int param_pos = 1; if ( task->title ){ rc = sqlite3_bind_text(out_stmt, param_pos, task->title, -1, SQLITE_STATIC); } if (checksqlerr(rc, "prepare broken in gen_sql_insert_stmt")){ return -1; } if ( task->due_date ){ param_pos = 2; rc = sqlite3_bind_text(out_stmt, param_pos, task->due_date, -1, SQLITE_STATIC); } 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 prepare_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 tasks WHERE status=? AND active_id!='NULL' ORDER BY due_date NULLS LAST", task->selected_columns); 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 bind_sql_select_stmt(filtered_tasks* task, sqlite3_stmt* out_stmt){ int rc = 0; const int param_pos = 1; if ( task->status ){ rc = sqlite3_bind_text(out_stmt, param_pos, task->status, -1, SQLITE_STATIC); } if (checksqlerr(rc, "prepare broken in gen_sql_insert_stmt")){ return -1; } return 0; } // TODO: I think this needs a bit of a refactor not sure how 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"); } // 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 ){ if ( x_isnumber(argv[2]) ){ task->active_id = atoi(argv[2]); } else { task->title = argv[2]; } } if ( argc > 3 ){ if ( x_strcmp(argv[1], "update") ){ task->due_date = argv[3]; return 0; } else{ task->new_title = argv[3]; } } if ( argc > 4 ){ task->due_date = argv[4]; } if ( argc > 5 ){ task->project_tag = argv[5]; } 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)); task->status = status; task->selected_columns = "COUNT(*)"; sqlite3_stmt *out_stmt; int rc = 0; if ( prepare_sql_select_stmt(db, &out_stmt, task) == 1 ){ return -1; } if ( ( rc = bind_sql_select_stmt(task, out_stmt) ) ){ 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, ""); } } // 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(); } 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; char* table = "tasks"; // Get the number of rows in current task list num_rows = get_num_rows(db, 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 ( prepare_sql_select_stmt(db, &out_stmt, task) ){ return -1; } if ( bind_sql_select_stmt(task, out_stmt) ){ 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); } // end // move down one and over to the start of the current task column printf("\n"); X_goright(start_col); } // reset to the beginning of the line printf("\r"); // 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 init_filtered_tasks(tasks); tasks->selected_columns = "active_id, title, due_date"; // Print Heading display_heading(); // Print "today" tasks tasks->status = "today"; if ( display_task_list(TODAY_COL_START, db, tasks) ){ free(tasks); return -1; } // Print "backlog" tasks tasks->status = "backlog"; if ( display_task_list(BACKLOG_COL_START, db, tasks) ){ free(tasks); return -1; } // Print "blocked" tasks tasks->status = "blocked"; if ( display_task_list(BLOCKED_COL_START, db, tasks) ){ free(tasks); return -1; } free(tasks); return 0; } 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)); init_filtered_tasks(task); parse_args(argc, argv, task); if ( task->title != NULL ){ if ( task->due_date != NULL ){ task->selected_columns = "(title, due_date)"; snprintf(values, ARG_MAX, "(?, ?)"); } else{ task->selected_columns = "(title)"; snprintf(values, ARG_MAX, "(?)"); } } else{ return -1; } if ( ( rc = prepare_sql_insert_stmt(db, &out_stmt, task, values) ) ){ return -1; } if ( ( rc = bind_sql_insert_stmt(task, out_stmt) ) ){ 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; int num_params = 0; sqlite3_stmt* out_stmt; filtered_tasks* task = malloc(sizeof(filtered_tasks)); init_filtered_tasks(task); parse_args(argc, argv, task); task->status = argv[1]; if ( ( num_params = prepare_sql_update_stmt(db, &out_stmt, task) ) == -1 ){ free(task); return -1; } if ( ( rc = bind_sql_update_stmt(task, num_params, out_stmt) ) ){ 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; int num_params = 0; sqlite3_stmt* out_stmt; filtered_tasks* task = malloc(sizeof(filtered_tasks)); init_filtered_tasks(task); task->status = "complete"; parse_args(argc, argv, task); if ( ( num_params = prepare_sql_update_stmt(db, &out_stmt, task) ) == -1 ){ free(task); return -1; } if ( ( rc = bind_sql_update_stmt(task, num_params, out_stmt) ) ){ 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 update_task(sqlite3 *db, int argc, char** argv){ int rc = 0; int num_params = 0; sqlite3_stmt* out_stmt; filtered_tasks* task = malloc(sizeof(filtered_tasks)); init_filtered_tasks(task); parse_args(argc, argv, task); if ( ( num_params = prepare_sql_update_stmt(db, &out_stmt, task) ) == -1 ){ free(task); return -1; } if ( ( rc = bind_sql_update_stmt(task, num_params, out_stmt) ) ){ free(task); return -1; } if ( ( rc = sqlite3_step(out_stmt) ) == SQLITE_DONE){ free(task); return 0; } checksqlerr(rc, "broken in update_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)); init_filtered_tasks(task); parse_args(argc, argv, task); if ( prepare_sql_delete_stmt(db, &out_stmt, task) ){ free(task); return -1; } if ( ( rc = bind_sql_delete_stmt(task, out_stmt) ) ){ 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; }