You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
338 lines
9.4 KiB
338 lines
9.4 KiB
#include <errno.h> |
|
#include <ncurses.h> |
|
#include <stdbool.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <time.h> |
|
#include <unistd.h> |
|
|
|
#include "bonzo.h" |
|
|
|
int main(int argc, char *argv[]) { |
|
char listNames[MAX_LISTS][MAX_LENGTH]; |
|
getListNames(listNames, argc, argv); |
|
|
|
time_t now = time(NULL); |
|
int nowTimestamp = now; |
|
|
|
bool inSelectedLists; |
|
|
|
int startingCount = 0; |
|
int priorityCount = 0; |
|
int normalCount = 0; |
|
|
|
task *startingTasks = malloc(MAX_ITEMS * sizeof(*startingTasks)); |
|
task *priorityTasks = malloc(MAX_ITEMS * sizeof(*priorityTasks)); |
|
task *normalTasks = malloc(MAX_ITEMS * sizeof(*normalTasks)); |
|
|
|
char rawTask[MAX_LENGTH]; |
|
|
|
errno = 0; |
|
FILE *file = openHomeFile(TASKS_FILE, "r"); |
|
|
|
if (file == NULL) { |
|
printf("An error occurred when loading file '%s'; errno %d.", TASKS_FILE, errno); |
|
return 1; |
|
} |
|
|
|
while (fgets(rawTask, MAX_LENGTH, file) != NULL) { |
|
// Remove newline from end of fgets |
|
rawTask[strcspn(rawTask, "\n")] = 0; |
|
|
|
if (strlen(rawTask) == 0) { |
|
continue; |
|
} else if (strncmp(rawTask, LIST_DELIMITER, LIST_DELIMITER_LENGTH) == STRCMP_MATCH) { |
|
int listNamesLength = sizeof(listNames) / sizeof(listNames[0]); |
|
inSelectedLists = isInSelectedLists(rawTask, listNames, listNamesLength); |
|
} else if (inSelectedLists) { |
|
if (rawTask[0] == '!') { |
|
if (rawTask[1] == '!') { |
|
// rawTask + 2 to get rid of the leading exclamation points |
|
createTask(&startingTasks[startingCount], rawTask + 2, nowTimestamp); |
|
startingCount++; |
|
} else { |
|
// rawTask + 1 to get rid of the leading exclamation point |
|
createTask(&priorityTasks[priorityCount], rawTask + 1, nowTimestamp); |
|
priorityCount++; |
|
} |
|
} else { |
|
createTask(&normalTasks[normalCount], rawTask, nowTimestamp); |
|
normalCount++; |
|
} |
|
} |
|
} |
|
|
|
task *taskQueue[MAX_ITEMS]; |
|
int queueCount = 0; |
|
task currentTask; |
|
int notDoneDelay; |
|
char formattedName[MAX_LENGTH]; |
|
char formattedTime[TIMESTAMP_LENGTH]; |
|
char formattedDate[TIMESTAMP_LENGTH]; |
|
char expandedInputForLog[22]; |
|
|
|
bool isStarting; |
|
bool isPriority; |
|
int runningDots = 0; |
|
|
|
errno = 0; |
|
file = openHomeFile(LOG_FILE, "a"); |
|
|
|
if (file == NULL) { |
|
printf("An error occurred when loading file '%s'; %s.", LOG_FILE, strerror(errno)); |
|
return 1; |
|
} |
|
|
|
// Add a header for today's date |
|
strftime(formattedDate, TIMESTAMP_LENGTH, "%F", localtime(&now)); |
|
fprintf(file, "\n%s\n-----------\n", formattedDate); |
|
fclose(file); |
|
|
|
initscr(); |
|
curs_set(0); |
|
move(1, 1); |
|
|
|
while (true) { |
|
clear(); |
|
|
|
now = time(NULL); |
|
nowTimestamp = now; |
|
|
|
move(1, 1); |
|
addstr(getRunningText(&runningDots)); |
|
refresh(); |
|
|
|
for (int i = 0; i < startingCount; i++) { |
|
isStarting = true; |
|
currentTask = startingTasks[i]; |
|
notDoneDelay = S_NOT_DONE_DELAY; |
|
if (currentTask.completed == false) { |
|
taskQueue[queueCount] = &startingTasks[i]; |
|
queueCount++; |
|
} |
|
} |
|
|
|
if (queueCount == 0) { |
|
isStarting = false; |
|
isPriority = true; |
|
for (int i = 0; i < priorityCount; i++) { |
|
currentTask = priorityTasks[i]; |
|
notDoneDelay = P_NOT_DONE_DELAY; |
|
if (currentTask.completed == false && currentTask.scheduled < nowTimestamp) { |
|
taskQueue[queueCount] = &priorityTasks[i]; |
|
queueCount++; |
|
} |
|
} |
|
} |
|
|
|
// If no priority tasks are in the queue, use normal tasks |
|
if (queueCount == 0) { |
|
isPriority = false; |
|
notDoneDelay = N_NOT_DONE_DELAY; |
|
for (int i = 0; i < normalCount; i++) { |
|
currentTask = normalTasks[i]; |
|
if (currentTask.completed == false && currentTask.scheduled < nowTimestamp) { |
|
taskQueue[queueCount] = &normalTasks[i]; |
|
queueCount++; |
|
} |
|
} |
|
} |
|
|
|
if (queueCount == 0) { |
|
sleep(1); |
|
continue; |
|
} |
|
|
|
FILE *snd; |
|
if ((snd = popen(ALARM_SOUND, "r"))) { |
|
pclose(snd); |
|
} |
|
|
|
char punc[2] = "."; |
|
if (isPriority || isStarting) { |
|
strncpy(punc, "!", 2); |
|
isStarting = isPriority = false; |
|
} |
|
|
|
for (int i = 0; i < queueCount; i++) { |
|
formattedName[0] = '\0'; |
|
formattedTime[0] = '\0'; |
|
|
|
if (taskQueue[i]->skipCount > 0) { |
|
sprintf(formattedName, "%s (%i%s)", taskQueue[i]->name, taskQueue[i]->skipCount, punc); |
|
} else { |
|
strcpy(formattedName, taskQueue[i]->name); |
|
} |
|
|
|
strftime(formattedTime, TIMESTAMP_LENGTH, "[%T]", localtime(&now)); |
|
bool validInputReceived = false; |
|
while (!validInputReceived) { |
|
validInputReceived = true; |
|
move(1, 1); |
|
printw("%s %s %s ", formattedTime, formattedName, OPTIONS); |
|
refresh(); |
|
char userInput = getch(); |
|
move(1, 1); |
|
clrtoeol(); |
|
move(3, 1); |
|
clrtoeol(); |
|
// Get current time only after input |
|
now = time(NULL); |
|
nowTimestamp = now; |
|
switch (userInput) { |
|
case 'd': |
|
case 'D': |
|
strcpy(expandedInputForLog, "done"); |
|
if (taskQueue[i]->regularity == 0) { |
|
taskQueue[i]->completed = true; |
|
} else { |
|
taskQueue[i]->scheduled = nowTimestamp + taskQueue[i]->regularity; |
|
taskQueue[i]->skipCount = 0; |
|
} |
|
break; |
|
case 'n': |
|
case 'N': |
|
strcpy(expandedInputForLog, "not done"); |
|
taskQueue[i]->scheduled = nowTimestamp + notDoneDelay; |
|
taskQueue[i]->skipCount = taskQueue[i]->skipCount + 1; |
|
break; |
|
case 's': |
|
case 'S': |
|
strcpy(expandedInputForLog, "skip"); |
|
if (taskQueue[i]->regularity != 0) { |
|
taskQueue[i]->scheduled = nowTimestamp + taskQueue[i]->regularity; |
|
} else { |
|
taskQueue[i]->scheduled = nowTimestamp + notDoneDelay; |
|
} |
|
taskQueue[i]->skipCount = taskQueue[i]->skipCount + 1; |
|
break; |
|
case 'w': |
|
case 'W': |
|
errno = 0; |
|
char *endptr; |
|
char waitTimeInput[WAIT_TIME_INPUT_LENGTH + 1]; |
|
int waitTime = 0; |
|
bool firstEntry = true; |
|
|
|
while (firstEntry || waitTimeInput == endptr || (errno != 0 && waitTime == 0)) { |
|
move(2, 1); |
|
addstr("How many minutes do you want to wait for? "); |
|
move(3, 1); |
|
|
|
if (firstEntry == false) { |
|
move(4, 1); |
|
addch('?'); |
|
move(3, 1); |
|
} else { |
|
firstEntry = false; |
|
} |
|
|
|
clrtoeol(); |
|
refresh(); |
|
getnstr(waitTimeInput, WAIT_TIME_INPUT_LENGTH); |
|
waitTime = strtol(waitTimeInput, &endptr, 10); |
|
} |
|
sprintf(expandedInputForLog, "wait for %i minutes", waitTime); |
|
taskQueue[i]->scheduled = nowTimestamp + (waitTime * SECONDS_IN_MINUTE); |
|
break; |
|
default: |
|
move(3, 1); |
|
addch('?'); |
|
validInputReceived = false; |
|
} |
|
|
|
if (validInputReceived) { |
|
file = openHomeFile(LOG_FILE, "a"); |
|
fprintf(file, "%s %s -> %s\n", formattedTime, formattedName, expandedInputForLog); |
|
fclose(file); |
|
expandedInputForLog[0] = '\0'; |
|
} |
|
|
|
clear(); |
|
} |
|
} |
|
|
|
queueCount = 0; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
void getListNames(char listNames[][MAX_LENGTH], int argc, char *argv[]) { |
|
// `always` should always be present |
|
strcpy(listNames[0], "always"); |
|
for (int i = 1; i < argc; i++) { |
|
strcpy(listNames[i], argv[i]); |
|
} |
|
} |
|
|
|
FILE *openHomeFile(char filename[], char mode[]) { |
|
char path[MAX_LENGTH] = "\0"; |
|
strcat(path, getenv("HOME")); |
|
strcat(path, "/"); |
|
strcat(path, filename); |
|
|
|
return fopen(path, mode); |
|
} |
|
|
|
void createTask(task *newTask, char rawTask[], int nowTimestamp) { |
|
char currentRawTask[MAX_LENGTH]; |
|
strcpy(currentRawTask, rawTask); |
|
|
|
char *name = strtok(currentRawTask, TASK_DELIMITER); |
|
|
|
strcpy(newTask->name, name); |
|
|
|
char *regularity = strtok(NULL, TASK_DELIMITER); |
|
int regularityInMinutes; |
|
if (regularity) { |
|
regularityInMinutes = atoi(regularity); |
|
} else { |
|
// If no token exists/token == NULL, it is a one-time task |
|
regularityInMinutes = 0; |
|
} |
|
|
|
srand(clock()); |
|
|
|
// Make timing offset so not all tasks happen at once at first. |
|
int timingOffset = regularityInMinutes > MIN_OFFSET_IN_MINUTES ? |
|
(rand() % (regularityInMinutes / 2)) * SECONDS_IN_MINUTE : |
|
(rand() % MIN_OFFSET_IN_MINUTES) * SECONDS_IN_MINUTE; |
|
newTask->regularity = regularityInMinutes * SECONDS_IN_MINUTE; |
|
newTask->scheduled = nowTimestamp + timingOffset; |
|
newTask->skipCount = 0; |
|
newTask->completed = false; |
|
} |
|
|
|
bool isInSelectedLists(char rawText[], char listNames[][MAX_LENGTH], int listNamesLength) { |
|
// remove delimiters |
|
char rawListName[MAX_LENGTH]; |
|
sprintf(rawListName, "%s", rawText + LIST_DELIMITER_LENGTH); |
|
rawListName[strlen(rawListName) - LIST_DELIMITER_LENGTH] = '\0'; |
|
|
|
// Check if list name is in selected list of names |
|
for (int i = 0; i < listNamesLength; i++) { |
|
if (strcmp(rawListName, listNames[i]) == STRCMP_MATCH) { |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
char *getRunningText(int *runningDots) { |
|
char *runningText = malloc(strlen(RUNNING_TEXT_BASE) + 3); |
|
strcpy(runningText, RUNNING_TEXT_BASE); |
|
*runningDots = (*runningDots + 1) % 3; |
|
switch (*runningDots) { |
|
case 2: |
|
strcat(runningText, "."); |
|
case 1: |
|
strcat(runningText, "."); |
|
case 0: |
|
strcat(runningText, "."); |
|
} |
|
return runningText; |
|
} |
|
|
|
|