From 0c817cc0ae9a6e1b66d6e6ff45fa80bacabc58a9 Mon Sep 17 00:00:00 2001 From: Jesper Jensen Date: Sat, 21 Feb 2026 23:47:28 +0100 Subject: Parse the status closer to the database --- src/app.c | 52 +++++++++++++++++++++++++++++++++++------------ src/app.h | 14 ++++++++++--- src/html.c | 68 ++++++++++++++++++++++++++++---------------------------------- 3 files changed, 81 insertions(+), 53 deletions(-) (limited to 'src') diff --git a/src/app.c b/src/app.c index 28debe9..7d0df96 100644 --- a/src/app.c +++ b/src/app.c @@ -13,6 +13,28 @@ #include #include +static const char *backup_status_db_strings[] = { + [STATUS_RUNNING] = "RUNNING", + [STATUS_FINISHED] = "FINISHED", + [STATUS_INTERRUPTED] = "INTERRUPTED", +}; + +const char *backup_status_to_db_string(enum BackupStatus status) { + assert(status >= 0 && status <= STATUS_INTERRUPTED); + return backup_status_db_strings[status]; +} + +enum BackupStatus backup_status_from_db_string(const char *str) { + assert(str != NULL); + for (int i = 0; i <= STATUS_INTERRUPTED; i++) { + if (strcmp(str, backup_status_db_strings[i]) == 0) { + return (enum BackupStatus)i; + } + } + + abort(); +} + static void sqliteError(void *pArg, int iErrCode, const char *zMsg) { // @CLEANUP: We should stuff these somewhere, but for now just print it fprintf(stderr, "(%d) %s\n", iErrCode, zMsg); @@ -305,7 +327,7 @@ int submit_report(struct App *app, char *client, char *secret, int is_finish) { sqlite3_stmt *stmt; // Mark any existing RUNNING backup as INTERRUPTED - if(sqlite3_prepare_v2(conn, "UPDATE backups SET status = 'INTERRUPTED', completed = datetime(?1, 'unixepoch', 'subsec') WHERE client = ?2 AND status = 'RUNNING'", -1, &stmt, NULL) != SQLITE_OK) { + if(sqlite3_prepare_v2(conn, "UPDATE backups SET status = ?3, completed = datetime(?1, 'unixepoch', 'subsec') WHERE client = ?2 AND status = ?4", -1, &stmt, NULL) != SQLITE_OK) { abort(); } if(sqlite3_bind_double(stmt, 1, TIME_AS_FLOAT(now)) != SQLITE_OK) { @@ -314,6 +336,12 @@ int submit_report(struct App *app, char *client, char *secret, int is_finish) { if(sqlite3_bind_int64(stmt, 2, clientId) != SQLITE_OK) { abort(); } + if(sqlite3_bind_text(stmt, 3, backup_status_to_db_string(STATUS_INTERRUPTED), -1, SQLITE_STATIC) != SQLITE_OK) { + abort(); + } + if(sqlite3_bind_text(stmt, 4, backup_status_to_db_string(STATUS_RUNNING), -1, SQLITE_STATIC) != SQLITE_OK) { + abort(); + } if(sqlite3_step(stmt) != SQLITE_DONE) { abort(); @@ -331,7 +359,7 @@ int submit_report(struct App *app, char *client, char *secret, int is_finish) { if(sqlite3_bind_int64(stmt, 1, clientId) != SQLITE_OK) { abort(); } - if(sqlite3_bind_text(stmt, 2, "RUNNING", -1, SQLITE_STATIC) != SQLITE_OK) { + if(sqlite3_bind_text(stmt, 2, backup_status_to_db_string(STATUS_RUNNING), -1, SQLITE_STATIC) != SQLITE_OK) { abort(); } if(sqlite3_bind_double(stmt, 3, TIME_AS_FLOAT(now)) != SQLITE_OK) { @@ -350,12 +378,15 @@ int submit_report(struct App *app, char *client, char *secret, int is_finish) { uint64_t backupId; sqlite3_stmt *stmt; - if(sqlite3_prepare_v2(conn, "SELECT id FROM backups WHERE client = ?1 AND status = 'RUNNING'", -1, &stmt, NULL) != SQLITE_OK) { + if(sqlite3_prepare_v2(conn, "SELECT id FROM backups WHERE client = ?1 AND status = ?2", -1, &stmt, NULL) != SQLITE_OK) { abort(); } if(sqlite3_bind_int64(stmt, 1, clientId) != SQLITE_OK) { abort(); } + if(sqlite3_bind_text(stmt, 2, backup_status_to_db_string(STATUS_RUNNING), -1, SQLITE_STATIC) != SQLITE_OK) { + abort(); + } if(sqlite3_step(stmt) != SQLITE_ROW) { abort(); @@ -378,7 +409,7 @@ int submit_report(struct App *app, char *client, char *secret, int is_finish) { if(sqlite3_prepare_v2(conn, "UPDATE backups SET status = ?1, completed = datetime(?2, 'unixepoch', 'subsec') WHERE id = ?3", -1, &stmt, NULL) != SQLITE_OK) { abort(); } - if(sqlite3_bind_text(stmt, 1, "FINISHED", -1, SQLITE_STATIC) != SQLITE_OK) { + if(sqlite3_bind_text(stmt, 1, backup_status_to_db_string(STATUS_FINISHED), -1, SQLITE_STATIC) != SQLITE_OK) { abort(); } @@ -517,16 +548,11 @@ int list_clients(struct App *app, struct ListClientResult *result) { result->clients[index].last_backup = time_from_double(last_backup); if (backup_status == NULL) { - strncpy(result->clients[index].status, "NEVER", BACKUP_STATUS_MAX); - result->clients[index].is_stale = 0; - } else if (strcmp((char*)backup_status, "RUNNING") == 0) { - strncpy(result->clients[index].status, "RUNNING", BACKUP_STATUS_MAX); - result->clients[index].is_stale = 0; - } else if (strcmp((char*)backup_status, "INTERRUPTED") == 0) { - strncpy(result->clients[index].status, "INTERRUPTED", BACKUP_STATUS_MAX); + result->clients[index].last_backup.tv_sec = 0; + result->clients[index].status = 0; result->clients[index].is_stale = 0; } else { - strncpy(result->clients[index].status, "OK", BACKUP_STATUS_MAX); + result->clients[index].status = backup_status_from_db_string((char*)backup_status); result->clients[index].is_stale = 0; } @@ -619,7 +645,7 @@ int get_client_backups(struct App *app, int64_t client_id, struct GetBackupsResu const double started = sqlite3_column_double(stmt, 2); const double completed = sqlite3_column_double(stmt, 3); - strncpy(result->backups[index].status, status ? (char*)status : "", BACKUP_STATUS_MAX); + result->backups[index].status = backup_status_from_db_string(status ? (char*)status : NULL); result->backups[index].started = time_from_double(started); result->backups[index].completed = time_from_double(completed); diff --git a/src/app.h b/src/app.h index 46d2c18..14bc052 100644 --- a/src/app.h +++ b/src/app.h @@ -6,9 +6,17 @@ #define CLIENT_NAME_MAX 32 #define CLIENT_SECRET_LEN 33 -#define BACKUP_STATUS_MAX 16 #define MAX_BACKUPS_DISPLAY 50 +enum BackupStatus { + STATUS_RUNNING, + STATUS_FINISHED, + STATUS_INTERRUPTED, +}; + +const char *backup_status_to_db_string(enum BackupStatus status); +enum BackupStatus backup_status_from_db_string(const char *str); + struct App { const char* dbname; char session_secret[CLIENT_SECRET_LEN]; @@ -32,7 +40,7 @@ struct ListClientResultClient { char name[CLIENT_NAME_MAX]; char secret[CLIENT_SECRET_LEN]; struct timespec last_backup; - char status[BACKUP_STATUS_MAX]; // "OK", "RUNNING", "STALE", "FAILED", "INTERRUPTED" + enum BackupStatus status; int is_stale; }; @@ -46,7 +54,7 @@ int list_clients(struct App *app, struct ListClientResult *result); struct BackupRecord { int64_t id; - char status[BACKUP_STATUS_MAX]; + enum BackupStatus status; struct timespec started; struct timespec completed; }; diff --git a/src/html.c b/src/html.c index b7fd4de..a848ebe 100644 --- a/src/html.c +++ b/src/html.c @@ -2,6 +2,7 @@ #include "mytime.h" #include +#include #include #include @@ -178,7 +179,6 @@ static void emit_stylesheet(FILE *f) { " .clients > a.stale { border-left: 3px solid var(--purple); padding-left: 11px; }\n" " .clients > a.stale .client-name { border-left: none; padding-left: 0; }\n" "}\n" - "/* BADGES */\n" ".badge { border: 1px solid; padding: 2px 7px; font-size: 10px; letter-spacing: 1px; justify-self: center; }\n" ".badge.ok { color: var(--green); background: var(--green-bg); border-color: var(--green-bor); }\n" ".badge.run { color: var(--yellow); background: var(--yellow-bg); border-color: var(--yellow-bor); }\n" @@ -282,7 +282,7 @@ static void emit_stylesheet(FILE *f) { " gap: 12px;\n" "}\n" ".backups > div > time { color: var(--hi); font-size: 13px; flex: 1; }\n" - ".backups > div > span { color: var(--dim); font-size: 12px; min-width: 70px; text-align: right; }\n" + ".backups > div > .duration { color: var(--dim); font-size: 12px; min-width: 70px; text-align: right; }\n" "\n" ); } @@ -298,22 +298,24 @@ static void mask_secret(const char *secret, char *out, size_t out_len) { } } -static const char *status_to_badge_class(const char *status) { - if (strcmp(status, "OK") == 0) return "ok"; - if (strcmp(status, "RUNNING") == 0) return "run"; - if (strcmp(status, "STALE") == 0) return "stale"; - if (strcmp(status, "INTERRUPTED") == 0) return "fail"; - if (strcmp(status, "FINISHED") == 0) return "ok"; - return ""; +static const char *status_to_badge_class(enum BackupStatus status) { + switch (status) { + case STATUS_RUNNING: return "run"; + case STATUS_INTERRUPTED: return "fail"; + case STATUS_FINISHED: return "ok"; + } + + abort(); } -static const char *status_to_display(const char *status) { - if (strcmp(status, "OK") == 0) return "OK"; - if (strcmp(status, "RUNNING") == 0) return "Running"; - if (strcmp(status, "STALE") == 0) return "Stale"; - if (strcmp(status, "INTERRUPTED") == 0) return "Failed"; - if (strcmp(status, "FINISHED") == 0) return "Done"; - return status; +static const char *status_to_display(enum BackupStatus status) { + switch (status) { + case STATUS_RUNNING: return "Running"; + case STATUS_INTERRUPTED: return "Failed"; + case STATUS_FINISHED: return "Done"; + } + + abort(); } char *html_render_clients_page(const struct Request *request, const struct ListClientResult *result, size_t *out_len) { @@ -386,7 +388,7 @@ char *html_render_clients_page(const struct Request *request, const struct ListC " %s\n" " %s\n" " %s\n" - " %s\n" + " %s\n" " View →\n" " \n", result->clients[i].id, @@ -413,13 +415,13 @@ char *html_render_clients_page(const struct Request *request, const struct ListC char *html_render_client_detail_page(const struct Request *request, const struct GetClientOnlyResult *client_result, const struct GetBackupsResult *backups_result, size_t *out_len) { // Determine status and staleness int is_stale = 0; - const char *current_status = "OK"; + enum BackupStatus current_status = 0; if (backups_result->backups_len > 0) { - const char *last_status = backups_result->backups[0].status; - if (strcmp(last_status, "RUNNING") == 0) { - current_status = "RUNNING"; - } else if (strcmp(last_status, "INTERRUPTED") == 0) { - current_status = "INTERRUPTED"; + enum BackupStatus last_status = backups_result->backups[0].status; + if (last_status == STATUS_RUNNING) { + current_status = STATUS_RUNNING; + } else if (last_status == STATUS_INTERRUPTED) { + current_status = STATUS_INTERRUPTED; } } @@ -449,7 +451,7 @@ char *html_render_client_detail_page(const struct Request *request, const struct " ← Back to clients\n" " \n" "

%s

\n" - "

%d backups %s

\n", + "

%d backups %s

\n", is_stale ? " class=\"stale\"" : "", client_result->name, backups_result->backups_len, @@ -485,34 +487,26 @@ char *html_render_client_detail_page(const struct Request *request, const struct fprintf(f, "

Recent Backups

\n"); if(backups_result->backups_len == 0) { + // @CLEANUP: Remove the inline style here fprintf(f, "

No backups recorded.

\n"); } else { fprintf(f, "
\n"); for (int i = 0; i < backups_result->backups_len; i++) { - const char *badge_class; - const char *badge_text; bool duration_valid = false; - if (strcmp(backups_result->backups[i].status, "RUNNING") == 0) { - badge_class = "run"; - badge_text = "Running"; - } else if (strcmp(backups_result->backups[i].status, "INTERRUPTED") == 0) { + if (backups_result->backups[i].status == STATUS_INTERRUPTED) { duration_valid = true; - badge_class = "fail"; - badge_text = "Failed"; } else { duration_valid = true; - badge_class = "ok"; - badge_text = "Done"; } struct timespec duration = time_sub(&backups_result->backups[i].completed, &backups_result->backups[i].started); fprintf(f, - "
%s%s
\n", + "
%s
\n", format_time(backups_result->backups[i].started).buf, duration_valid ? format_duration(duration).buf : "", - badge_class, - badge_text + status_to_badge_class(backups_result->backups[i].status), + status_to_display(backups_result->backups[i].status) ); } -- cgit v1.2.3