diff options
| -rw-r--r-- | src/app.c | 52 | ||||
| -rw-r--r-- | src/app.h | 14 | ||||
| -rw-r--r-- | src/html.c | 68 | ||||
| -rw-r--r-- | test/http.c | 4 |
4 files changed, 83 insertions, 55 deletions
@@ -13,6 +13,28 @@ #include <sqlite3.h> #include <fcntl.h> +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); @@ -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; }; @@ -2,6 +2,7 @@ #include "mytime.h" #include <stdio.h> +#include <stdlib.h> #include <string.h> #include <stdbool.h> @@ -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" "</style>\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 " <span class=\"client-name\">%s</span>\n" " <code class=\"client-secret\">%s</code>\n" " <span class=\"client-time\">%s</span>\n" - " <b class=\"badge client-badge %s\">%s</b>\n" + " <span class=\"badge client-badge %s\">%s</span>\n" " <span class=\"client-action\">View →</span>\n" " </a>\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 " <a href=\"/clients\">← Back to clients</a>\n" " <article%s>\n" " <h3>%s</h3>\n" - " <p>%d backups <b class=\"badge %s\">%s</b></p>\n", + " <p>%d backups <span class=\"badge %s\">%s</span></p>\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, " <h4>Recent Backups</h4>\n"); if(backups_result->backups_len == 0) { + // @CLEANUP: Remove the inline style here fprintf(f, " <p style=\"color:var(--dim)\">No backups recorded.</p>\n"); } else { fprintf(f, " <div class=\"backups\">\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, - " <div><time>%s</time><span>%s</span><b class=\"badge %s\">%s</b></div>\n", + " <div><time>%s</time><time class=\"duration\">%s</time><span class=\"badge %s\">%s</span></div>\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) ); } diff --git a/test/http.c b/test/http.c index c5a3b2a..11de092 100644 --- a/test/http.c +++ b/test/http.c @@ -291,9 +291,9 @@ int main(int argc, char **argv) { // Backups are ordered by started DESC, so newest first // The most recent backup should be RUNNING - assert(strcmp(backups_result->backups[0].status, "RUNNING") == 0); + assert(backups_result->backups[0].status == STATUS_RUNNING); // The previous backup should be INTERRUPTED - assert(strcmp(backups_result->backups[1].status, "INTERRUPTED") == 0); + assert(backups_result->backups[1].status == STATUS_INTERRUPTED); // The interrupted backup should have a completed timestamp assert(backups_result->backups[1].completed.tv_sec > 0); |
