aboutsummaryrefslogtreecommitdiff
path: root/ttt.jai
diff options
context:
space:
mode:
Diffstat (limited to 'ttt.jai')
-rw-r--r--ttt.jai220
1 files changed, 113 insertions, 107 deletions
diff --git a/ttt.jai b/ttt.jai
index f0d22be..7fff02d 100644
--- a/ttt.jai
+++ b/ttt.jai
@@ -125,23 +125,24 @@ Layouts :: enum u8 {
}
-error_message: string;
+error_string_builder: String_Builder;
error_time_limit := Apollo_Time.{0, 0};
-print_error :: (format :string, args : .. Any) {
+print_error :: (message: string, data: *void, info: Log_Info) {
if TUI.is_active() == false {
- print(format, ..args, to_standard_error = true);
- print("\n");
+ write_strings(message, "\n", to_standard_error = true);
return;
}
- if error_message.data != null {
- free(error_message.data);
+ if error_time_limit < current_time_monotonic() {
+ reset(*error_string_builder);
}
- error_message = sprint(format, args);
-
+ else {
+ append(*error_string_builder, " | ");
+ }
+ append(*error_string_builder, message);
error_time_limit = current_time_monotonic() + seconds_to_apollo(5);
}
@@ -170,7 +171,7 @@ draw_error_window :: () {
print_character(#char " ");
}
TUI.set_cursor_position(pos_x + 1, pos_y + 1);
- write_string(error_message);
+ write_builder(*error_string_builder, false);
}
trigger_autosave :: () {
@@ -183,6 +184,14 @@ show_processing :: () {
write_strings(TUI.Commands.DrawingMode, TUI.Drawings.Diamond, TUI.Commands.TextMode);
}
+hide_processing :: () {
+ TUI.set_cursor_position(1, 1);
+ TUI.using_style(style_default);
+ TUI.tui_write_string(TUI.Commands.DrawingMode);
+ TUI.tui_write_string(TUI.Drawings.CornerTL);
+ TUI.tui_write_string(TUI.Commands.TextMode);
+}
+
// Returns true if string to_compare is equal to any of the other passed strings, false otherwise.
is_equal_to_any :: (to_compare :string, test_a :string, test_b :string) -> bool {
return to_compare == test_a || to_compare == test_b;
@@ -306,7 +315,7 @@ add_task :: (db: *Database, task: *Task = null) -> task: *Task, index: s64 {
array_add(*db.tasks, new_task);
for * db.total_times {
- <<it = add(<<it, new_task.times[it_index]);
+ it.* = add(it.*, new_task.times[it_index]);
}
idx := db.tasks.count-1;
@@ -396,7 +405,7 @@ move_task :: (using db: *Database, source: s64, target: s64) {
}
// Find similar task and return it's index, or -1 if not found.
-find_similar_task :: (db: *Database, task: Task) -> idx: s64 {
+find_similar_task :: (db: *Database, task: Task, ignore_times := false) -> idx: s64 {
compare_array :: (a: [] $T, b: [] T) -> int {
for 0..min(a.count, b.count)-1 {
if a[it] > b[it] return 1;
@@ -408,7 +417,7 @@ find_similar_task :: (db: *Database, task: Task) -> idx: s64 {
}
for db.tasks {
- if compare(xx task.name, xx it.name) == 0 && compare_array(task.times, it.times) == 0 {
+ if compare(xx task.name, xx it.name) == 0 && (ignore_times || compare_array(task.times, it.times) == 0) {
return it_index;
}
}
@@ -526,15 +535,12 @@ reset_database :: (db: *Database) {
// Stores data from database into binary file.
// Returns success.
-store_database :: (db: Database, path: string) -> success: bool {
+store_database :: (db: Database, path: string) -> success: bool #must {
assert(xx path, ASSERT_NOT_EMPTY, "path");
// Open file.
- file, open_success := file_open(path, for_writing = true); // log_errors: bool = true
- if open_success == false {
- print_error("Failed to open file '%' while storing database: ERROR_FROM_LOG", path); // TODO Get error from logger ?!
- return false;
- }
+ file, open_success := file_open(path, for_writing = true);
+ if open_success == false return false;
defer file_close(*file);
file_write(*file, DB_FILE_SIGN_STR);
@@ -546,35 +552,30 @@ store_database :: (db: Database, path: string) -> success: bool {
// Loads data from binary file into database.
// Returns success.
-load_database :: (db: *Database, path: string) -> success: bool {
+load_database :: (db: *Database, path: string) -> success: bool #must {
assert(db != null, ASSERT_NOT_NULL, "db");
assert(xx path, ASSERT_NOT_EMPTY, "path");
// Open file.
- file, open_success := file_open(path); // log_errors: bool = true
- if open_success == false {
- print_error("Failed to open file '%' while loading database: ERROR_FROM_LOG", path); // TODO Get error from logger ?!
- return false;
- }
+ file, open_success := file_open(path);
+ if open_success == false then return false;
defer file_close(*file);
// Validate file signature.
file_signature: [DB_FILE_SIGN_STR.count] u8;
read_success := file_read(file, *file_signature, DB_FILE_SIGN_STR.count);
- if read_success == false print_error("Failed to read file signature from '%'.", path);
+ if read_success == false log_error("Failed to read file signature.");
if cast(string)file_signature != DB_FILE_SIGN_STR {
- print_error("Invalid file signature.");
+ log_error("Invalid file signature while loading database.\n");
return false;
}
// Read database structure.
read_success = file_read(file, db, size_of(Database));
- // TODO Use print_error or assert?
if read_success == false {
- print_error("Failed to read database info from '%'.", path);
+ log_error("Failed to read database info.");
return false;
}
- assert(read_success == true, "Failed to read database info from '%'.", path);
// Reserve database capacity for tasks.
tasks_count := db.tasks.count;
@@ -588,14 +589,20 @@ load_database :: (db: *Database, path: string) -> success: bool {
// Make sure we are reading all the file.
buffer: u8;
success, bytes := file_read(file, *buffer, 1);
- assert(bytes == 0, "Unexpected content found at the end of file '%'.", path);
+ if bytes > 0 {
+ log_error("Unexpected content found at the end of file '%'.", path);
+ return false;
+ }
+
+ // Make sure we have a valid selected index.
+ if db.tasks.count > 0 && db.selected_idx < 0 then db.selected_idx = 0;
return true;
}
// Exports data into CSV file.
// Returns success.
-export_to_csv :: (db: Database, path: string) -> success: bool {
+export_to_csv :: (db: Database, path: string) -> success: bool #must {
assert(xx path, ASSERT_NOT_EMPTY, "path");
auto_release_temp();
@@ -616,14 +623,12 @@ export_to_csv :: (db: Database, path: string) -> success: bool {
name, it.times[0], it.times[1], it.times[2], it.times[3], it.times[4], it.times[5], it.times[6]);
}
- write_entire_file(path, *builder);
-
- return true;
+ return write_entire_file(path, *builder);
}
// Imports CSV file into database.
// Returns success.
-import_from_csv :: (db: *Database, path: string) -> bool {
+import_from_csv :: (db: *Database, path: string) -> bool #must {
// TODO Review code.
assert(db != null, ASSERT_NOT_NULL, "db");
assert(xx path, ASSERT_NOT_EMPTY, "path");
@@ -649,7 +654,7 @@ import_from_csv :: (db: *Database, path: string) -> bool {
csv := data;
if success == false {
- print_error("Failed to read file '%' while loading database: ERROR_FROM_LOG", path); // TODO Get error from logger ?!
+ log_error("Failed to read file '%' while loading database: ERROR_FROM_LOG", path); // TODO Get error from logger ?!
return false;
}
@@ -746,15 +751,14 @@ import_from_csv :: (db: *Database, path: string) -> bool {
// Appends task to the end of the CSV file.
// Returns success.
-append_to_csv :: (task: Task, path: string) -> success: bool {
+append_to_csv :: (task: Task, path: string) -> success: bool #must {
assert(xx path, ASSERT_NOT_EMPTY, "path");
+
+ auto_release_temp();
file, file_success := file_open(path, true, true);
+ if file_success == false then return false;
defer file_close(*file);
- if file_success == false {
- //print_error("Failed to open file '%s' while appending to CSV: %s.", path, strerror(errno)); // TODO Show internal error or something
- return false;
- }
file_size := file_length(file);
file_set_position(file, file_size-1);
@@ -763,14 +767,13 @@ append_to_csv :: (task: Task, path: string) -> success: bool {
if (last_char != #char "\n") {
file_write(*file, "\n");
}
-
- task_name := copy_temporary_string(xx task.name); // TODO Cleanup this temp mess.
+
+ task_name := copy_temporary_string(xx task.name);
replace_chars(task_name, ",", #char " ");
csv_line := tprint("%,%,%,%,%,%,%,%\n",
task_name, task.times[0], task.times[1], task.times[2], task.times[3], task.times[4], task.times[5], task.times[6]);
- file_write(*file, csv_line);
-
- return true;
+
+ return file_write(*file, csv_line);
}
// Selects task by index.
@@ -832,7 +835,7 @@ layout_tasks_rows : int;
is_terminal_too_small := true;
-initialize_tui :: () {
+initialize_user_interface :: () {
// Normal layout.
layouts[Layouts.NORMAL] = .{
@@ -881,7 +884,7 @@ initialize_tui :: () {
col.alignment_offset = offset;
}
}
-
+
assert(TUI.setup_terminal(), "Failed to setup TUI.");
}
@@ -926,8 +929,6 @@ dbg_average := 0; // DEBUG
dbg_count := 0; // DEBUG
buffer: String_Builder; // TODO
draw_user_interface :: (db: *Database, layout: *Layout, redraw_all: bool = true) {
-
-
auto_release_temp();
@@ -953,6 +954,7 @@ draw_user_interface :: (db: *Database, layout: *Layout, redraw_all: bool = true)
// If not much is happening, we may just update the active task and it's times.
if redraw_all == false {
+
if active_task == null then return;
layout_idx := get_layout_index_from_day_index(now_week_day);
@@ -1250,6 +1252,8 @@ main :: () {
#if DEBUG_MEMORY {
defer report_memory_leaks(); // TODO Remove after final debug sessions.
}
+
+ context.logger = print_error;
defer free_memory();
@@ -1262,17 +1266,13 @@ main :: () {
home_path, success_path := get_absolute_path(home_dir); // Returns temporary memory.
if success_path == false {
- print_error("Failed to find home directory '%'.", home_dir);
+ log_error("Failed to find home directory '%'.", home_dir);
exit(1);
}
app_directory = join(home_path, "/", APP_FOLDER_NAME);
db_file_path = join(app_directory, "/", DB_FILE_NAME);
ar_file_path = join(app_directory, "/", AR_FILE_NAME);
-
- // TODO app data should be stored under:
- // Windows: APPDATA (~/AppData/Roaming)
- // Unix: XDG_DATA_HOME (~/.local/share)
make_directory_if_it_does_not_exist(app_directory, recursive = true);
}
@@ -1280,14 +1280,14 @@ main :: () {
{ // Initialize database and archive files if needed.
if (file_exists(db_file_path) == false) {
if (store_database(database, db_file_path) == false) {
- print_error("Failed to initialize database.");
+ log_error("Failed to initialize database.");
exit(1);
}
}
if (file_exists(ar_file_path) == false) {
if (export_to_csv(archive, ar_file_path) == false) {
- print_error("Failed to initialize archive.");
+ log_error("Failed to initialize archive.");
exit(1);
}
}
@@ -1367,19 +1367,19 @@ main :: () {
if is_equal_to_any(args[it], "--import-csv", "-i") {
it += 1;
if it >= args.count {
- print_error("Missing CSV file path to import.");
+ log_error("Missing CSV file path to import.");
exit(1);
}
if (load_database(*database, db_file_path) == false) {
- print_error("Failed to load database.");
+ log_error("Failed to load database during import.");
exit(1);
}
if (import_from_csv(*database, args[it]) == false) {
- print_error("Failed to import CSV file.");
+ log_error("Failed to import CSV file.");
exit(1);
}
if (store_database(*database, db_file_path) == false) {
- print_error("Failed to store database.");
+ log_error("Failed to store database during import.");
exit(1);
}
reset_database(*database);
@@ -1390,15 +1390,15 @@ main :: () {
if is_equal_to_any(args[it], "--export-csv", "-e") {
it += 1;
if it >= args.count {
- print_error("Missing CSV file path to export.");
+ log_error("Missing CSV file path to export.");
exit(1);
}
if (load_database(*database, db_file_path) == false) {
- print_error("Failed to load database.");
+ log_error("Failed to load database during export.");
exit(1);
}
if (export_to_csv(*database, args[it]) == false) {
- print_error("Failed to export CSV file.");
+ log_error("Failed to export CSV file.");
exit(1);
}
reset_database(*database);
@@ -1409,7 +1409,7 @@ main :: () {
if is_equal_to_any(args[it], "--start-of-week", "-s") {
it += 1;
if it >= args.count {
- print_error("Missing number for starting day of week.");
+ log_error("Missing number for starting day of week.");
exit(1);
}
first_day_of_week = parse_int(*args[it]);
@@ -1421,7 +1421,7 @@ main :: () {
continue;
}
- print_error("%: invalid option '%'.\nTry '% --help' for more information.", args[0], args[it], args[0]);
+ log_error("%: invalid option '%'.\nTry '% --help' for more information.", args[0], args[it], args[0]);
exit(1);
}
@@ -1431,11 +1431,11 @@ main :: () {
}
if (load_database(*database, db_file_path) == false) {
- print_error("Failed to load database.");
+ log_error("Failed to load database.");
exit(1);
}
- initialize_tui();
+ initialize_user_interface();
db := *database;
layout := *layouts[Layouts.COMPACT];
@@ -1511,9 +1511,14 @@ TUI.tui_print("Average % us (% / % : % bytes) ---------", dbg_average/1000, cont
if (countdown_to_autosave <= 0) {
show_processing();
if (db == *archive) {
- export_to_csv(*archive, ar_file_path);
+ if export_to_csv(*archive, ar_file_path) == false {
+ log_error("Failed to store archive during autosave.");
+ }
+ }
+ if store_database(database, db_file_path) == false {
+ log_error("Failed to store database during autosave.");
}
- store_database(database, db_file_path);
+ hide_processing();
}
}
@@ -1653,8 +1658,8 @@ TUI.tui_print("Average % us (% / % : % bytes) ---------", dbg_average/1000, cont
// Apply changes.
time := cast(s64)input_time;
day := get_day_index_from_layout_index(selected_day);
- if is_assign set_task_time(db, db.selected_idx, day, time);
- else add_task_time(db, db.selected_idx, day, time);
+ if is_assign set_task_time(db, db.selected_idx, day, time);
+ else add_task_time(db, db.selected_idx, day, time);
trigger_autosave();
@@ -1691,10 +1696,7 @@ TUI.tui_print("Average % us (% / % : % bytes) ---------", dbg_average/1000, cont
continue;
}
- if (add_task(db, selected_task) == null) {
- print_error("Failed to duplicate task.");
- continue;
- }
+ add_task(db, selected_task);
trigger_autosave();
// Refresh totals.
@@ -1721,14 +1723,14 @@ TUI.tui_print("Average % us (% / % : % bytes) ---------", dbg_average/1000, cont
if (db == *database) {
if (import_from_csv(*archive, ar_file_path) == false) {
reset_database(*archive);
- print_error("Failed to load archive.");
+ log_error("Failed to load archive.");
continue;
}
db = *archive;
}
else {
if (export_to_csv(*archive, ar_file_path) == false) {
- print_error("Failed to store archive.");
+ log_error("Failed to store archive.");
continue;
}
reset_database(*archive);
@@ -1741,7 +1743,7 @@ TUI.tui_print("Average % us (% / % : % bytes) ---------", dbg_average/1000, cont
if (db != *database || selected_task == null || selected_task == active_task) continue;
if (append_to_csv(selected_task, ar_file_path) == false) {
- print_error("Failed to archive task.");
+ log_error("Failed to archive task.");
continue;
}
delete_task(db, db.selected_idx);
@@ -1758,10 +1760,7 @@ TUI.tui_print("Average % us (% / % : % bytes) ---------", dbg_average/1000, cont
continue;
}
- if (add_task(*database, selected_task) == null) {
- print_error("Failed to restore task.");
- continue;
- }
+ add_task(*database, selected_task);
delete_task(db, db.selected_idx);
trigger_autosave();
@@ -1773,7 +1772,7 @@ TUI.tui_print("Average % us (% / % : % bytes) ---------", dbg_average/1000, cont
show_processing();
sort_procedure: (a: Task, b: Task) -> s64;
- active_task: Task = ifx db.active_idx >= 0 then db.tasks[db.active_idx] else .{};
+ prev_active_task: Task = ifx db.active_idx >= 0 then db.tasks[db.active_idx] else .{};
if sort_by == {
case #char "n"; #through;
case #char "N";
@@ -1809,7 +1808,7 @@ TUI.tui_print("Average % us (% / % : % bytes) ---------", dbg_average/1000, cont
quick_sort(db.tasks, sort_procedure);
if db.active_idx >= 0 {
- db.active_idx = find_similar_task(db, active_task);
+ db.active_idx = find_similar_task(db, prev_active_task);
}
trigger_autosave();
@@ -1820,23 +1819,29 @@ TUI.tui_print("Average % us (% / % : % bytes) ---------", dbg_average/1000, cont
TUI.using_style(action_style);
if (prompt_user_key(selected_task_row, "Press enter to archive duplicates and reset all.") != TUI.Keys.Enter) continue;
show_processing();
-
+
+ failed_to_archive := false;
for db.tasks {
- if (append_to_csv(it, ar_file_path) == false) {
- print_error("Failed to archive task."); // TODO Improve this.
+ if append_to_csv(it, ar_file_path) {
+ reset_task_times(db, it_index);
+ }
+ else {
+ failed_to_archive = true;
}
- reset_task_times(db, it_index);
}
trigger_autosave();
+ if failed_to_archive then log_error("Failed to archive tasks.");
// Coalesce similar tasks.
case #char "c"; #through;
case #char "C";
- // TODO Active task is lost...
if (db.tasks.count <= 0) continue;
TUI.using_style(action_style);
if (prompt_user_key(selected_task_row, "Press enter to coalesce similar tasks.") != TUI.Keys.Enter) continue;
show_processing();
+
+ active_task_idx := db.active_idx;
+ prev_active_task: Task = ifx db.active_idx >= 0 then db.tasks[db.active_idx] else .{};
head_idx := 0;
while head_idx < db.tasks.count - 1 {
@@ -1852,6 +1857,10 @@ TUI.tui_print("Average % us (% / % : % bytes) ---------", dbg_average/1000, cont
}
head_idx += 1;
}
+
+ if active_task_idx >= 0 {
+ db.active_idx = find_similar_task(db, prev_active_task, ignore_times = true);
+ }
trigger_autosave();
case TUI.Keys.Home;
@@ -1876,31 +1885,28 @@ TUI.tui_print("Average % us (% / % : % bytes) ---------", dbg_average/1000, cont
// Save any unsaved changes.
show_processing();
- error_saving := false;
if (db == *archive) {
- if (export_to_csv(archive, ar_file_path) == false) {
- print_error("Failed to save archive.");
- error_saving |= true;
+ while true {
+ if (export_to_csv(archive, ar_file_path) == false) {
+ log_error("Failed to save archive, retry?");
+ draw_error_window();
+ if TUI.get_key() == TUI.Keys.Escape then break;
+ }
+ else break;
}
}
if (countdown_to_autosave > 0 || is_autosave_enabled == false) {
- if (store_database(database, db_file_path) == false) {
- print_error("Failed to save database.");
- error_saving |= true;
+ while true {
+ if (store_database(database, db_file_path) == false) {
+ log_error("Failed to save database, retry?");
+ draw_error_window();
+ if TUI.get_key() == TUI.Keys.Escape then break;
+ }
+ else break;
}
}
- if (error_saving) {
- print_error("Press any key to close.");
- draw_error_window();
- TUI.get_key();
- }
assert(TUI.reset_terminal(), "Failed to reset TUI.");
-#if DEBUG_MEMORY {
return;
-} else {
- exit(xx ifx error_saving then 1 else 0);
-}
}
-