aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordam <dam@gudinoff>2022-11-02 01:03:16 +0000
committerdam <dam@gudinoff>2022-11-02 01:03:16 +0000
commit4b0f25ca86c6c5d14d414a7ac8ccf2fa52460f1b (patch)
treec0c3e49ba995f87a74f1e08bb8c7ef8f42cc18fe
parenteb5a3b3535057a62728460cce562aa64312e0519 (diff)
downloadtask-time-tracker-4b0f25ca86c6c5d14d414a7ac8ccf2fa52460f1b.tar.zst
task-time-tracker-4b0f25ca86c6c5d14d414a7ac8ccf2fa52460f1b.zip
Implemented autosave feature. Fixed argument/options processing to allow repeating options, and using options to set behavior configurations.
-rw-r--r--main.c198
-rw-r--r--misc.c29
-rw-r--r--readme.md12
3 files changed, 149 insertions, 90 deletions
diff --git a/main.c b/main.c
index daac495..b4756f0 100644
--- a/main.c
+++ b/main.c
@@ -25,6 +25,7 @@
#include <limits.h>
#include <locale.h>
#include <ncurses.h>
+#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
@@ -72,20 +73,30 @@ const int64_t SECONDS_IN_YEAR = (int64_t)365*SECONDS_IN_DAY;
const size_t MAX_DATABASE_TASKS = (PTRDIFF_MAX < (SIZE_MAX / SIZEOF_TASK_ST)) ? PTRDIFF_MAX : (SIZE_MAX / SIZEOF_TASK_ST);
-database_st database = { .tasks = NULL };
-database_st archive = { .tasks = NULL };
-database_st *db = NULL;
-char *app_folder = NULL;
-char *db_file_path = NULL;
-char *ar_file_path = NULL;
-char *string_buffer = NULL;
-size_t string_buffer_size = 0;
+database_st database = { .tasks = NULL };
+database_st archive = { .tasks = NULL };
+database_st *db = NULL;
+bool is_autosave_enabled = true;
+int countdown_to_autosave = -1;
+char *app_folder = NULL;
+char *db_file_path = NULL;
+char *ar_file_path = NULL;
+char *string_buffer = NULL;
+size_t string_buffer_size = 0;
int size_x, size_y, pos_x, pos_y;
void inline static clear_string_buffer() {
memset(string_buffer, 0, string_buffer_size);
}
+void inline static trigger_autosave() {
+ countdown_to_autosave = 13375; // ms
+}
+
+void inline static show_processing() {
+ mvaddch(0, 0, ACS_DIAMOND);
+ refresh();
+}
// Checks if file is exists and is accessible.
// Returns true when the file exists and is accessible.
@@ -247,10 +258,12 @@ task_st *get_selected_task(database_st *db) {
}
-// Creates new task returned in the pointer. If necessary, expands database capacity.
+// Creates new task stored at location given by task pointer.
+// If necessary, expands database capacity.
// Returns success.
bool create_task(database_st *db, task_st **task) {
assert(db != NULL);
+ assert(task != NULL);
if (db->count == MAX_DATABASE_TASKS) {
fprintf(stderr, "Database reached maximum capacity.\n");
@@ -288,9 +301,9 @@ bool create_task(database_st *db, task_st **task) {
return true;
}
-// Adds the given task to the database using (using create_task and memcpy).
+// Duplicates the given task. Duplicated task is appended to the database.
// Returns success.
-bool add_task(database_st *db, task_st *task) {
+bool duplicate_task(database_st *db, task_st *task) {
assert(db != NULL);
assert(task != NULL);
@@ -301,16 +314,17 @@ bool add_task(database_st *db, task_st *task) {
memcpy(new_task, task, SIZEOF_TASK_ST);
- // Add task timer values to total timers.
+ // Add task time values to total times.
for (int idx = 0; idx < NUM_WEEK_DAYS; idx++) {
// db->total_times[idx] += task->times[idx]; TODO
- db->total_times[idx] = add_int64(db->total_times[idx], task->times[idx]);
+ db->total_times[idx] = add_int64(db->total_times[idx], new_task->times[idx]);
}
return true;
}
-// Deletes the provided task. If possible, shrinks the database capacity.
+// Deletes the provided task.
+// If possible, shrinks the database capacity.
// Returns success.
bool delete_task(database_st *db, task_st *task) {
assert(db != NULL);
@@ -531,33 +545,6 @@ bool store_database(const database_st *db, const char *path) {
return true;
}
-// Writes only the database core structure and the provided task if not null.
-// Returns success.
-bool store_database_partial(const database_st *db, const task_st *task, const char *path) {
- assert(db != NULL);
- assert(path != NULL);
-
- // Open file.
- FILE *file = fopen(path, "r+b");
- if (file == NULL) {
- fprintf(stderr, "Failed to open file '%s' while partially storing database: %s.\n", path, strerror(errno));
- return false;
- }
-
- fseek(file, DB_FILE_SIGN_LENGTH, SEEK_SET);
- fwrite(db, SIZEOF_DATABASE_ST, 1, file);
-
- if (task != NULL) {
- assert(task >= db->tasks && task < &db->tasks[db->count]);
- ptrdiff_t offset = task - db->tasks;
- fseek(file, offset * SIZEOF_TASK_ST, SEEK_CUR);
- fwrite(task, SIZEOF_TASK_ST, 1, file);
- }
-
- fclose(file);
- return true;
-}
-
// Loads data from binary file into database.
// Returns success.
bool load_database(database_st *db, const char *path) {
@@ -1100,6 +1087,11 @@ bool initialize_app_folder() {
return true;
}
+void exit_gracefully(int signal) {
+ flushinp();
+ ungetch('q');
+}
+
int main(int argc, char *argv[]) {
if (initialize_app_folder() == false) {
@@ -1121,58 +1113,83 @@ int main(int argc, char *argv[]) {
if (argc > 1) {
char *action;
- bool do_action = false;
- for (int idx = 1; idx < argc; idx++) {
+ bool is_action = false;
+ bool is_exit_requested = false;
+ for (unsigned idx = 1; idx < argc; idx++) {
action = "--help";
- do_action = strncmp(argv[idx], action, strlen(action)+1) == 0;
- if (do_action) {
+ is_action = strncmp(argv[idx], action, strlen(action)+1) == 0;
+ if (is_action) {
fprintf(stdout, "TO BE IMPLEMENTED\n"); // TODO
return EXIT_SUCCESS;
}
action = "--version";
- do_action = strncmp(argv[idx], action, strlen(action)+1) == 0;
- if (do_action) {
- fprintf(stdout, "Task Time Tracker " VERSION "\n");
+ is_action = strncmp(argv[idx], action, strlen(action)+1) == 0;
+ if (is_action) {
+ fprintf(stdout, "Task Time Tracker v" VERSION "\n");
free_memory();
return EXIT_SUCCESS;
}
- action = "--icsv";
- do_action = strncmp(argv[idx], action, strlen(action)+1) == 0;
- if (do_action) {
- if (idx+1 >= argc) {
+ action = "--import-csv";
+ is_action = strncmp(argv[idx], action, strlen(action)+1) == 0;
+ if (is_action) {
+ idx++;
+ if (idx >= argc) {
fprintf(stdout, "Missing CSV file path to import.\n");
+ free_memory();
return EXIT_FAILURE;
}
+ // TODO No error checking... oh noes!
load_database(&database, db_file_path);
- import_from_csv(&database, argv[idx+1]);
+ import_from_csv(&database, argv[idx]);
store_database(&database, db_file_path);
- free_memory();
- return EXIT_SUCCESS;
+ reset_database(&database);
+ is_exit_requested = true;
+ continue;
}
- action = "--ecsv";
- do_action = strncmp(argv[idx], action, strlen(action)+1) == 0;
- if (do_action) {
- if (idx+1 >= argc) {
+ action = "--export-csv";
+ is_action = strncmp(argv[idx], action, strlen(action)+1) == 0;
+ if (is_action) {
+ idx++;
+ if (idx >= argc) {
fprintf(stdout, "Missing CSV file path to export.\n");
+ free_memory();
return EXIT_FAILURE;
}
+ // TODO No error checking... oh noes!
load_database(&database, db_file_path);
- export_to_csv(&database, argv[idx+1]);
- free_memory();
- return EXIT_SUCCESS;
+ export_to_csv(&database, argv[idx]);
+ reset_database(&database);
+ is_exit_requested = true;
+ continue;
+ }
+
+ action = "--no-autosave";
+ is_action = strncmp(argv[idx], action, strlen(action)+1) == 0;
+ if (is_action) {
+ is_autosave_enabled = false;
+ continue;
}
+
+ fprintf(stdout, "Unkown option '%s'.\nUse '%s --help' for list of options.\n", argv[idx], argv[0]);
+ return EXIT_FAILURE;
}
- fprintf(stdout, "Unkown command '%s'.\nUse '%s --help' for list of commands.\n", argv[1], argv[0]);
- return EXIT_FAILURE;
+ if (is_exit_requested) {
+ return EXIT_SUCCESS;
+ }
}
initialize_tui();
+ signal(SIGTERM, exit_gracefully);
+ signal(SIGINT, exit_gracefully);
+ signal(SIGQUIT, exit_gracefully);
+ signal(SIGHUP, exit_gracefully);
+
load_database(&database, db_file_path);
flushinp();
@@ -1192,6 +1209,16 @@ int main(int argc, char *argv[]) {
// When getch() times out.
case ERR: {
+ if (is_autosave_enabled && countdown_to_autosave > 0) {
+ countdown_to_autosave -= INPUT_TIMEOUT_MS;
+ if (countdown_to_autosave <= 0) {
+ show_processing();
+ if (db == &archive) {
+ export_to_csv(&archive, ar_file_path);
+ }
+ store_database(&database, db_file_path);
+ }
+ }
break;
}
@@ -1228,8 +1255,7 @@ int main(int argc, char *argv[]) {
selected_task = new_task;
db->selected_task = selected_task - db->tasks;
- // TODO
- store_database_partial(db, selected_task, db_file_path);
+ trigger_autosave();
// Force rename action.
flushinp();
@@ -1255,6 +1281,7 @@ int main(int argc, char *argv[]) {
mvgetnstr(selected_task_row, 1, string_buffer, MAX_TASK_NAME-1);
noecho();
curs_set(0);
+ attrset(A_NORMAL);
// Apply new task name.
if (is_empty_string(string_buffer) == false) {
@@ -1263,9 +1290,11 @@ int main(int argc, char *argv[]) {
replace_char(string_buffer, '\f', ' ');
replace_char(string_buffer, '\r', ' ');
memcpy(selected_task->name, string_buffer, MAX_TASK_NAME);
+
+ trigger_autosave();
}
- attrset(A_NORMAL);
+
break;
}
@@ -1286,6 +1315,8 @@ int main(int argc, char *argv[]) {
if (getch() == '\n') {
reset_task_times(db, selected_task);
+
+ trigger_autosave();
}
break;
@@ -1397,7 +1428,7 @@ int main(int argc, char *argv[]) {
// Adust time.
set_task_time(db, selected_task, day, time);
- store_database_partial(db, selected_task, db_file_path);
+ trigger_autosave();
break;
}
@@ -1419,6 +1450,8 @@ int main(int argc, char *argv[]) {
if (getch() == '\n') {
delete_task(db, selected_task);
+
+ trigger_autosave();
}
break;
}
@@ -1460,6 +1493,8 @@ int main(int argc, char *argv[]) {
input;
move_task(db, selected_task, target);
+ trigger_autosave();
+
break;
}
@@ -1507,12 +1542,14 @@ int main(int argc, char *argv[]) {
break;
}
- add_task(db, selected_task);
+ duplicate_task(db, selected_task);
+ trigger_autosave();
break;
}
case KEY_F(5): {
update_total_times(db);
+ trigger_autosave();
break;
}
@@ -1538,7 +1575,8 @@ int main(int argc, char *argv[]) {
db->active_task = next_task - db->tasks;
}
db->modified_on = time(NULL);
- store_database(db, db_file_path);
+ active_task = get_active_task(db);
+ trigger_autosave();
break;
}
@@ -1563,7 +1601,7 @@ int main(int argc, char *argv[]) {
}
append_to_csv(selected_task, ar_file_path);
delete_task(db, selected_task);
- // TODO Maybe save stuff? Shoulw we?
+ trigger_autosave();
break;
}
@@ -1572,9 +1610,9 @@ int main(int argc, char *argv[]) {
if (db != &archive || selected_task == NULL) {
break;
}
- add_task(&database, selected_task);
+ duplicate_task(&database, selected_task);
delete_task(db, selected_task);
- // TODO Maybe save stuff? Shoulw we?
+ trigger_autosave();
break;
}
@@ -1641,17 +1679,17 @@ int main(int argc, char *argv[]) {
timeout(INPUT_TIMEOUT_MS);
}
- update_times(&database);
- store_database(&database, db_file_path);
-// task_st *active_task = get_active_task(&database);
-// if (active_task != NULL) {
-// update_times(&database);
-// store_database_partial(&database, active_task, db_file_path);
-// }
+ // Save any unsaved changes.
+ show_processing();
if (db == &archive) {
export_to_csv(&archive, ar_file_path);
}
+ if (countdown_to_autosave > 0 || is_autosave_enabled == false) {
+ store_database(&database, db_file_path);
+ }
+
free_memory();
endwin();
+
return EXIT_SUCCESS;
}
diff --git a/misc.c b/misc.c
index 151e26f..f5a3ada 100644
--- a/misc.c
+++ b/misc.c
@@ -1,3 +1,32 @@
+
+// Writes only the database core structure and the provided task if not null.
+// Returns success.
+bool store_database_partial(const database_st *db, const task_st *task, const char *path) {
+ return store_database(db, path);
+ assert(db != NULL);
+ assert(path != NULL);
+
+ // Open file.
+ FILE *file = fopen(path, "r+b");
+ if (file == NULL) {
+ fprintf(stderr, "Failed to open file '%s' while partially storing database: %s.\n", path, strerror(errno));
+ return false;
+ }
+
+ fseek(file, DB_FILE_SIGN_LENGTH, SEEK_SET);
+ fwrite(db, SIZEOF_DATABASE_ST, 1, file);
+
+ if (task != NULL) {
+ assert(task >= db->tasks && task < &db->tasks[db->count]);
+ ptrdiff_t offset = task - db->tasks;
+ fseek(file, offset * SIZEOF_TASK_ST, SEEK_CUR);
+ fwrite(task, SIZEOF_TASK_ST, 1, file);
+ }
+
+ fclose(file);
+ return true;
+}
+
// Returns the number of characters in a string using UTF8 encoding.
size_t length_utf8(char *string) {
size_t size = 0;
diff --git a/readme.md b/readme.md
index 770fe3e..7525e00 100644
--- a/readme.md
+++ b/readme.md
@@ -54,16 +54,8 @@ Task Time Tracker
- [x] Total times may saturate, but before that the user will see the infinite symbol. Solution: Provide user with possibility to refresh totals.
- [x] Decide on a INVALID_WINDOW_MESSAGE.
- [x] Use backspace to clear all timers for current task.
-- [ ] Save more often using the `store_database_partial` and only save all when quitting:
- - When operating on the archive, will we keep the saves synchronous?
- - Maybe add a cooldown timer and save changes after it times out?
- - Maybe add a partial-save when (de)activating a task (just save the task and the database core?
- - on (de)activate_task : partial w/task
- - on rename, changetimes : partial w/task
- - on new_task : partial w/task
- - on refresh : partial
- - on exit : full
- - on move, delete, archive: full
+- [x] Move `store_database_partial` to misc and save only when leaving or after 15 seconds of inactivity and having dirty flag set.
+- [x] Register kill signals to exit gracefully.
- [ ] Check if string_buffer needs to be cleared. We may be leaking info on the string_buffer.
- [ ] Maybe replace all `sprintf` by `snprintf`;
- [ ] REVISE ALL CODE ptrdiff_t/size_t (signed/unsigned)!