Helper to keep me on track with self-care through my day
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

#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;
}