#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <ctype.h> #include <unistd.h> #include "todo.h" // readHeader reads header into the head and sets cursor of the file // to the end of header int readHeader(header *head, FILE *fd) { if (head == NULL) { return 0; // valid error } // rewind to the beginning of the file // NOTE: fseek calls fflush if needed if (fseek(fd, 0L, SEEK_SET) != 0) { return 0; } memset(head, 0, sizeof(header)); if (fread(head, sizeof(header), 1, fd) != 1) { return 0; } /* long int cursor = ftell(fd); */ /* printf("cusor at %ld, size is: %d", cursor, sizeof(header)); */ return 1; } int writeHeader(header *head, FILE *fd) { // TODO: should i zero header space in the file? // get current position of cursor long int cursor = ftell(fd); if (cursor == -1) { return 0; } // rewind to the beginning of the file // NOTE: fseek calls fflush if needed if (fseek(fd, 0L, SEEK_SET) != 0) { return 0; } // wirte header to the file if (fwrite(head, sizeof(header), 1, fd) != 1) { return 0; } // return to the previous position return fseek(fd, cursor, SEEK_SET) == 0; } int isNumber(char *str) { int len = strlen(str); for (int i = 0; i != len; i++) { if (!isdigit(str[i])) return 0; } return 1; } int lookupTask(FILE *stream, int id, task *t) { if (t == NULL) { // TODO: create temporaray task } // TODO: should i use temp task? header head; if (!readHeader(&head, stream)) { fprintf(stderr, "could not read file header: %s", strerror(errno)); return 0; } int low = 0; int high = head.count - 1; int mid; int offset; while (low <= high) { mid = ((unsigned int)low + (unsigned int)high) >> 1; offset = sizeof(header)+(sizeof(task)*mid); // TODO: bounds check if(fseek(stream, offset, SEEK_SET) == -1) { fprintf(stderr, "could not seek file offset %d: %s", offset, strerror(errno)); return 0; } if (fread(t, sizeof(task), 1, stream) != 1) { fprintf(stderr, "could not read task: %s", strerror(errno)); return 0; } if (t->id < id) { low = mid + 1; } else if (t->id > id) { high = mid - 1; } else { if(fseek(stream, offset, SEEK_SET) == -1) { fprintf(stderr, "could not seek file offset %d: %s", offset, strerror(errno)); return 0; } return 1; } } return 0; } int doneCommand(int argc, char **argv, char *file_path) { if (argc < 1) { fprintf(stderr, "done: missing task id argument\n"); return 0; } int target_id = atoi(argv[0]); if(access(file_path, F_OK) != 0) { return 1; } FILE *stream; if (!(stream = fopen(file_path, "r+"))) { fprintf(stderr, "done: could not open todo file %s: %s\n", file_path, strerror(errno)); return 0; } task t; memset(&t, 0, sizeof(task)); if (!lookupTask(stream, target_id, &t)) { fprintf(stderr, "done: could not find task %d\n", target_id); fclose(stream); return 0; } t.done_at = time(NULL); if (fwrite(&t, sizeof(task), 1, stream) != 1) { fprintf(stderr, "done: could not write task to the file: %s\n", strerror(errno)); fclose(stream); return 0; } fclose(stream); return 1; } static int cmptasks(const void *p1, const void *p2) { const task *t1 = (task *)p1; const task *t2 = (task *)p2; const int t1_done = (t1->done_at > 0); const int t2_done = (t2->done_at > 0); if (t1_done != t2_done) return t1_done > t2_done ? 1 : -1; if (t1->priority == t2->priority) { if (t1->id == t1->id) return 0; return t1->id < t2->id ? -1 : 1; } return t1->priority > t2->priority ? -1 : 1; } // list of known priority tags in order highest priority char *colors[] = {"\033[32m","\033[34m","\033[33m","\033[31m"}; int listCommand(int argc, char **argv, char *file_path) { int narg = 0; int show_done = 0; char project[MAX_PROJECT]; memset(&project, 0, sizeof(project)); for (; narg != argc; narg += 2) { if ((narg+1) > argc || argv[narg][0] != '-') { break; } if (strcmp("-p", argv[narg]) == 0 || strcmp("--project", argv[narg]) == 0) { if (strlen(argv[narg + 1]) + 1 > MAX_PROJECT) { fprintf(stderr, "list: project name \"%s\" is to long\n", argv[narg + 1]); continue; } strcpy(project, argv[narg + 1]); continue; } if (strcmp("-d", argv[narg]) == 0 || strcmp("--done", argv[narg]) == 0) { show_done = 1; continue; } } if(access(file_path, F_OK) != 0) { return 1; } FILE *stream; if (!(stream = fopen(file_path, "r"))) { fprintf(stderr, "list: could not open todo file %s: %s\n", file_path, strerror(errno)); return 0; } header head; if (!readHeader(&head, stream)) { fprintf(stderr, "list: could not read file %s header: %s", file_path, strerror(errno)); return 0; } task *tasks = malloc(sizeof(task) * head.count); task *t = tasks; while (fread(t, sizeof(task), 1, stream) == 1) t++; qsort(tasks, head.count, sizeof(task), cmptasks); for (int i = 0; i < head.count; i++) { if (strlen(project) > 0 && strcasecmp(project, tasks[i].project) != 0) { continue; } int is_done = (tasks[i].done_at > 0); if (!show_done && is_done) continue; int color = 0; for (int j = 63; j <= 257; j+= 64) { if (tasks[i].priority < j) { break; } color++; } if (is_done) printf("\033[9m"); printf("%3d [%s]\t%s%s\033[0m", tasks[i].id, tasks[i].project, colors[color], tasks[i].description); if (is_done) printf("\033[0m"); printf("\n"); } if (errno != 0) { fprintf(stderr, "list: could not read task from file: %s\n", strerror(errno)); fclose(stream); return 0; } fclose(stream); return 1; } int addCommand(int argc, char **argv, char *file_path) { task t; header h; memset(&h, 0, sizeof(header)); memset(&t, 0, sizeof(task)); // TODO(nk2ge5k): open file and read header // if file does not eixsts than initialize empty header int narg = 0; for (; narg != argc; narg += 2) { if ((narg+1) > argc || argv[narg][0] != '-') { break; } if (strcmp("-p", argv[narg]) == 0 || strcmp("--project", argv[narg]) == 0) { if (strlen(argv[narg + 1]) + 1 > MAX_PROJECT) { fprintf(stderr, "add: project name \"%s\" is to long\n", argv[narg + 1]); return 0; } strcpy(t.project, argv[narg + 1]); continue; } if (strcmp("-P", argv[narg]) == 0 || strcmp("--priority", argv[narg]) == 0) { if (!isNumber(argv[narg+1])) { fprintf(stderr, "add: invalid value for \"%s\" option, expected number\n", argv[narg]); return 0; } int p = atoi(argv[narg+1]); if (p < 0 || 255 <= p) { fprintf(stderr, "add: invalid value of \"%s\" option, " "expected number between 0 and 255\n", argv[narg]); return 0; } t.priority = (priority)p; continue; } fprintf(stderr, "add: unknown option \"%s\"\n", argv[narg]); return 0; } if (narg == argc) { fprintf(stderr, "add: task description is required\n"); return 0; } for (; narg != argc; narg++) { if (strlen(t.description) + strlen(argv[narg]) + 2 > MAX_DESCRIPTION) { fprintf(stderr, "add: message too long\n"); return 0; } strcat(t.description, argv[narg]); strcat(t.description, " "); } FILE *stream; if (access(file_path, R_OK | W_OK) == 0) { if (!(stream = fopen(file_path, "r+"))) { fprintf(stderr, "add: could not open todo file %s: %s\n", file_path, strerror(errno)); fclose(stream); return 0; } // TODO: read header if (!readHeader(&h, stream)) { fprintf(stderr, "add: could not read file header: %s\n", strerror(errno)); fclose(stream); return 0; } } else { // TODO: initFile(FILE *fd) if (!(stream = fopen(file_path, "w+"))) { fprintf(stderr, "add: could not open todo file %s: %s\n", file_path, strerror(errno)); fclose(stream); return 0; } if (!writeHeader(&h, stream)) { fprintf(stderr, "add: could not write header to the file: %s\n", strerror(errno)); fclose(stream); return 0; } } if (fseek(stream, 0, SEEK_END) != 0) { fprintf(stderr, "add: failed to seek file offset: %s", strerror(errno)); fclose(stream); return 0; } t.created_at = time(NULL); t.id = ++h._id_seq; h.count++; size_t written = fwrite(&t, sizeof(task), 1, stream); if (written != 1) { fprintf(stderr, "add: could not write task to the file: %s\n", strerror(errno)); fclose(stream); return 0; } if (!writeHeader(&h, stream)){ fprintf(stderr, "add: could not write header to the file: %s\n", strerror(errno)); fclose(stream); return 0; } return 1; }