From 0ed782c186153dad5bcd7bfb7d2bc8123e755b4e Mon Sep 17 00:00:00 2001 From: Jacob Date: Wed, 11 Mar 2026 00:34:34 -0400 Subject: [PATCH] Things. --- client/start.sh | 3 + server/config.mk | 1 - server/src/chat.c | 147 +++++++--------------------- server/src/db.c | 201 -------------------------------------- server/src/include/chat.h | 14 ++- server/src/include/db.h | 25 ----- server/src/main.c | 7 -- server/start.sh | 1 + 8 files changed, 50 insertions(+), 349 deletions(-) create mode 100755 client/start.sh delete mode 100644 server/src/db.c delete mode 100644 server/src/include/db.h create mode 100755 server/start.sh diff --git a/client/start.sh b/client/start.sh new file mode 100755 index 0000000..594e2c7 --- /dev/null +++ b/client/start.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +npm start diff --git a/server/config.mk b/server/config.mk index 57d29b8..10479b3 100644 --- a/server/config.mk +++ b/server/config.mk @@ -11,7 +11,6 @@ CC = clang -std=c23 LINK = clang CFLAGS = -Wall -DDBG -ggdb -fsanitize=leak -I$(INC_DIR) -I$(SRC_DIR) LDFLAGS = -lwebsockets -LDFLAGS += -lsqlite3 PRINT = echo -e SRC_FILES = $(wildcard $(SRC_DIR)/*.c) diff --git a/server/src/chat.c b/server/src/chat.c index d7601c5..61457ec 100644 --- a/server/src/chat.c +++ b/server/src/chat.c @@ -1,7 +1,6 @@ #include "include/chat.h" #include "include/api.h" #include "include/data.h" -#include "include/db.h" #include "include/session.h" #include @@ -11,9 +10,33 @@ #include #include -static const char DEFAULT_ROOM[] = "global"; +MsgData* chat_history[CHAT_HISTORY_SZ] = {NULL}; +size_t chat_history_head = 0; // Next insertion index. +size_t chat_history_count = 0; // Number of valid entries. +static MsgID next_msg_id = 1; -// Legacy in-memory ring removed; history is now persisted in SQLite. +MsgData* chat_history_msg_add(MsgData* msg) { + chat_history[chat_history_head] = msg; + chat_history_head = (chat_history_head + 1) % CHAT_HISTORY_SZ; + if (chat_history_count < CHAT_HISTORY_SZ) { chat_history_count++; } + + return msg; +} + +MsgData** chat_history_nice(void) { + MsgData** msgs = calloc(CHAT_HISTORY_SZ, sizeof(MsgData*)); + + // Oldest entry is head - count (mod size). + size_t start = (chat_history_head + CHAT_HISTORY_SZ - chat_history_count) % + CHAT_HISTORY_SZ; + + for (size_t j = 0; j < chat_history_count; j++) { + size_t idx = (start + j) % CHAT_HISTORY_SZ; + msgs[j] = chat_history[idx]; + } + + return msgs; +} // Parse a raw packet. Packet* packet_parse(const char* in, size_t len) { @@ -46,9 +69,6 @@ void do_join(Session* sess, Packet* packet) { session_set_name(sess, name); } -#define WELCOME_HISTORY_LIMIT 50 -#define HISTORY_LIMIT_MAX 200 - // Do a welcome packet. void do_welcome(Session* sess) { // Build list of online users (only sessions with a name). @@ -65,20 +85,10 @@ void do_welcome(Session* sess) { oi++; } - // Fetch latest messages from DB, then reverse to oldest-first. - MsgData** history = NULL; + // Build history list. + MsgData** history = chat_history_nice(); size_t historyc = 0; - if (db_fetch_messages( - DEFAULT_ROOM, 0, WELCOME_HISTORY_LIMIT, &history, &historyc - ) != 0) { - history = NULL; - historyc = 0; - } - for (size_t i = 0; i < historyc / 2; i++) { - MsgData* tmp = history[i]; - history[i] = history[historyc - 1 - i]; - history[historyc - 1 - i] = tmp; - } + while (historyc < CHAT_HISTORY_SZ && history[historyc]) historyc++; PacketWelcome data = { .id = session_get_id(sess), @@ -93,84 +103,7 @@ void do_welcome(Session* sess) { session_send(sess, packet); free(packet); free(online); - if (history) { - for (size_t i = 0; i < historyc; i++) free(history[i]); - free(history); - } -} - -// Handle a history request packet. -static void do_history_req(Session* sess, Packet* packet) { - if (!sess || !packet) return; - - const yyjson_val* data = (const yyjson_val*)packet->data; - const yyjson_val* jroom = data ? yyjson_obj_get(data, "room") : NULL; - const char* room_str = - (jroom && yyjson_is_str(jroom)) ? yyjson_get_str(jroom) : DEFAULT_ROOM; - if (!room_verify(room_str)) room_str = DEFAULT_ROOM; - - MsgID before = 0; - const yyjson_val* jbefore = data ? yyjson_obj_get(data, "before") : NULL; - if (jbefore) { - if (yyjson_is_int(jbefore)) { - int64_t v = yyjson_get_int(jbefore); - if (v > 0) before = (MsgID)v; - } else if (yyjson_is_str(jbefore)) { - const char* s = yyjson_get_str(jbefore); - char* endp = NULL; - unsigned long long tmp = strtoull(s, &endp, 10); - if (endp && *endp == '\0') before = (MsgID)tmp; - } - } - - size_t limit = WELCOME_HISTORY_LIMIT; - const yyjson_val* jlimit = data ? yyjson_obj_get(data, "limit") : NULL; - if (jlimit && yyjson_is_int(jlimit)) { - int64_t v = yyjson_get_int(jlimit); - if (v > 0 && v <= HISTORY_LIMIT_MAX) limit = (size_t)v; - } - - MsgData** rows = NULL; - size_t count = 0; - if (db_fetch_messages(room_str, before, limit + 1, &rows, &count) != 0) { - return; - } - - bool has_more = false; - if (count > limit) { - has_more = true; - free(rows[count - 1]); - rows[count - 1] = NULL; - count = limit; - } - - // reverse to oldest-first - for (size_t i = 0; i < count / 2; i++) { - MsgData* tmp = rows[i]; - rows[i] = rows[count - 1 - i]; - rows[count - 1 - i] = tmp; - } - - MsgID oldest_id = count ? rows[0]->id : 0; - - PacketHistoryRes res = { - .room = {0}, - .historyc = count, - .history = rows, - .has_more = has_more, - .oldest_id = oldest_id - }; - strncpy(res.room, room_str, ROOM_MAX_LENGTH - 1); - res.room[ROOM_MAX_LENGTH - 1] = '\0'; - - Packet* out = packet_init(PACKET_TYPE_HISTORY_RES, &res); - session_send(sess, out); - free(out); - - if (rows) { - for (size_t i = 0; i < count; i++) free(rows[i]); - free(rows); - } + free(history); } // Do a welcome packet. @@ -255,18 +188,15 @@ void do_msg(Session* sess, Packet* packet) { } } - MsgData* msg = calloc(1, sizeof(MsgData)); + MsgData* msg = malloc(sizeof(MsgData)); if (!msg) { lwsl_err("Failed to allocate MsgData.\n"); return; } + msg->id = next_msg_id++; msg->author.id = session_get_id(sess); msg->author.name = session_get_name(sess); - strncpy(msg->author_name, *msg->author.name, NAME_MAX_LENGTH - 1); - msg->author_name[NAME_MAX_LENGTH - 1] = '\0'; - strncpy(msg->room, DEFAULT_ROOM, ROOM_MAX_LENGTH - 1); - msg->room[ROOM_MAX_LENGTH - 1] = '\0'; msg->parent = parent; strncpy(msg->content, content, MSG_MAX_LENGTH - 1); msg->content[MSG_MAX_LENGTH - 1] = '\0'; @@ -278,14 +208,7 @@ void do_msg(Session* sess, Packet* packet) { (msg->parent == UINT64_MAX ? "null" : "") ); - if (db_insert_message(msg, &msg->id) != 0) { - ack.status = "db_error"; - Packet* p = packet_init(PACKET_TYPE_MSG_ACK, &ack); - session_send(sess, p); - free(p); - free(msg); - return; - } + chat_history_msg_add(msg); ack.id = msg->id; Packet* ackp = packet_init(PACKET_TYPE_MSG_ACK, &ack); @@ -295,7 +218,6 @@ void do_msg(Session* sess, Packet* packet) { Packet* evt = packet_init(PACKET_TYPE_MSG_EVT, msg); session_send_all(evt); free(evt); - free(msg); } static void do_ping(Session* sess) { @@ -427,9 +349,6 @@ int cb_chat( free(ackp); break; } - case PACKET_TYPE_HISTORY_REQ: - do_history_req(sess, packet); - break; case PACKET_TYPE_PONG: // Client responded; nothing else to do (timer continues). if (sess) { diff --git a/server/src/db.c b/server/src/db.c deleted file mode 100644 index d80302b..0000000 --- a/server/src/db.c +++ /dev/null @@ -1,201 +0,0 @@ -#include "include/db.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static sqlite3* db = NULL; - -static int ensure_dir(const char* path) { - struct stat st = {0}; - if (stat(path, &st) == -1) { - if (mkdir(path, 0755) == -1 && errno != EEXIST) return -1; - } else if (!S_ISDIR(st.st_mode)) { - errno = ENOTDIR; - return -1; - } - return 0; -} - -static int exec_simple(const char* sql) { - char* err = NULL; - int rc = sqlite3_exec(db, sql, NULL, NULL, &err); - if (rc != SQLITE_OK) { - lwsl_err("SQLite exec failed: %s\n", err ? err : "(null)"); - sqlite3_free(err); - } - return rc; -} - -int db_init(const char* path) { - if (db) return 0; - - // Ensure parent directory exists. - char dir[256]; - const char* slash = strrchr(path, '/'); - size_t dlen = slash ? (size_t)(slash - path) : 0; - if (dlen >= sizeof(dir)) return -1; - if (dlen > 0) { - memcpy(dir, path, dlen); - dir[dlen] = '\0'; - if (ensure_dir(dir) != 0) { - lwsl_err( - "Failed to create data dir %s: %s\n", dir, strerror(errno) - ); - return -1; - } - } - - if (sqlite3_open(path, &db) != SQLITE_OK) { - lwsl_err("Failed to open db at %s: %s\n", path, sqlite3_errmsg(db)); - return -1; - } - - exec_simple("PRAGMA journal_mode=WAL;"); - exec_simple("PRAGMA synchronous=NORMAL;"); - exec_simple("PRAGMA foreign_keys=ON;"); - - const char* create_messages = "CREATE TABLE IF NOT EXISTS messages (" - " id INTEGER PRIMARY KEY," - " room TEXT NOT NULL," - " author_id INTEGER NOT NULL," - " author_name TEXT NOT NULL," - " parent INTEGER," - " content TEXT NOT NULL," - " timestamp INTEGER NOT NULL," - " deleted_at INTEGER" - ");"; - - const char* create_idx_ts = - "CREATE INDEX IF NOT EXISTS idx_messages_room_ts " - "ON messages(room, timestamp DESC);"; - const char* create_idx_id = - "CREATE INDEX IF NOT EXISTS idx_messages_room_id " - "ON messages(room, id DESC);"; - - if (exec_simple(create_messages) != SQLITE_OK) return -1; - if (exec_simple(create_idx_ts) != SQLITE_OK) return -1; - if (exec_simple(create_idx_id) != SQLITE_OK) return -1; - - return 0; -} - -void db_close(void) { - if (db) { - sqlite3_close(db); - db = NULL; - } -} - -int db_insert_message(const MsgData* msg, MsgID* out_id) { - if (!db || !msg) return -1; - - const char* sql = - "INSERT INTO messages " - "(room, author_id, author_name, parent, content, timestamp) " - "VALUES (?1, ?2, ?3, ?4, ?5, ?6);"; - - sqlite3_stmt* stmt = NULL; - if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) { - lwsl_err("prepare insert failed: %s\n", sqlite3_errmsg(db)); - return -1; - } - - sqlite3_bind_text(stmt, 1, msg->room, -1, SQLITE_STATIC); - sqlite3_bind_int64(stmt, 2, (sqlite3_int64)msg->author.id); - sqlite3_bind_text( - stmt, 3, (msg->author.name ? *msg->author.name : msg->author_name), -1, - SQLITE_STATIC - ); - if (msg->parent == UINT64_MAX) { - sqlite3_bind_null(stmt, 4); - } else { - sqlite3_bind_int64(stmt, 4, (sqlite3_int64)msg->parent); - } - sqlite3_bind_text(stmt, 5, msg->content, -1, SQLITE_STATIC); - sqlite3_bind_int64(stmt, 6, (sqlite3_int64)msg->timestamp); - - int rc = sqlite3_step(stmt); - if (rc != SQLITE_DONE) { - lwsl_err("insert failed: %s\n", sqlite3_errmsg(db)); - sqlite3_finalize(stmt); - return -1; - } - - sqlite3_finalize(stmt); - - if (out_id) { *out_id = (MsgID)sqlite3_last_insert_rowid(db); } - return 0; -} - -int db_fetch_messages( - const char* room, MsgID before_id, size_t limit, MsgData*** out_msgs, - size_t* out_count -) { - if (!db || !room || !out_msgs || !out_count) return -1; - - const char* sql = "SELECT id, room, author_id, author_name, " - " parent, content, timestamp " - "FROM messages " - "WHERE room = ?1 " - " AND deleted_at IS NULL " - " AND (?2 = 0 OR id < ?2) " - "ORDER BY id DESC " - "LIMIT ?3;"; - - sqlite3_stmt* stmt = NULL; - if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) { - lwsl_err("prepare select failed: %s\n", sqlite3_errmsg(db)); - return -1; - } - - sqlite3_bind_text(stmt, 1, room, -1, SQLITE_STATIC); - sqlite3_bind_int64(stmt, 2, (sqlite3_int64)before_id); - sqlite3_bind_int64(stmt, 3, (sqlite3_int64)limit); - - size_t cap = limit; - MsgData** rows = calloc(cap ? cap : 1, sizeof(MsgData*)); - size_t count = 0; - - while (sqlite3_step(stmt) == SQLITE_ROW) { - MsgData* m = calloc(1, sizeof(MsgData)); - m->id = (MsgID)sqlite3_column_int64(stmt, 0); - const unsigned char* r = sqlite3_column_text(stmt, 1); - if (r) { - strncpy(m->room, (const char*)r, ROOM_MAX_LENGTH - 1); - m->room[ROOM_MAX_LENGTH - 1] = '\0'; - } - m->author.id = (UserID)sqlite3_column_int64(stmt, 2); - const unsigned char* an = sqlite3_column_text(stmt, 3); - if (an) { - strncpy(m->author_name, (const char*)an, NAME_MAX_LENGTH - 1); - m->author_name[NAME_MAX_LENGTH - 1] = '\0'; - m->author.name = &m->author_name; - } - if (sqlite3_column_type(stmt, 4) == SQLITE_NULL) { - m->parent = UINT64_MAX; - } else { - m->parent = (MsgID)sqlite3_column_int64(stmt, 4); - } - const unsigned char* c = sqlite3_column_text(stmt, 5); - if (c) { - strncpy(m->content, (const char*)c, MSG_MAX_LENGTH - 1); - m->content[MSG_MAX_LENGTH - 1] = '\0'; - } - m->timestamp = (time_t)sqlite3_column_int64(stmt, 6); - - rows[count++] = m; - if (count == cap) break; - } - - sqlite3_finalize(stmt); - *out_msgs = rows; - *out_count = count; - return 0; -} diff --git a/server/src/include/chat.h b/server/src/include/chat.h index 7c56f32..b61ca4f 100644 --- a/server/src/include/chat.h +++ b/server/src/include/chat.h @@ -7,7 +7,19 @@ #include #include -/** +// Message history (ring). +#define CHAT_HISTORY_SZ 128 +extern MsgData* chat_history[CHAT_HISTORY_SZ]; +extern size_t chat_history_head; // Next insertion index. +extern size_t chat_history_count; // Number of valid history entries. + +// Add message to history ring. +MsgData* chat_history_msg_add(MsgData* msg); + +// Get a list of messages in order. +MsgData** chat_history_nice(void); + +/** * cb_chat - libwebsockets protocol callback for COMS chat. * * This function is registered in the protocols array passed to the diff --git a/server/src/include/db.h b/server/src/include/db.h deleted file mode 100644 index 670ffdd..0000000 --- a/server/src/include/db.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef DB__H -#define DB__H - -#include "data.h" -#include -#include - -// Initialize the SQLite database at the given path. Creates directories/tables. -int db_init(const char* path); - -// Close the global DB handle. -void db_close(void); - -// Insert a message; on success sets *out_id to the assigned rowid. -int db_insert_message(const MsgData* msg, MsgID* out_id); - -// Fetch newest-first messages for a room, optionally before a given id. -// Returns an allocated array of MsgData* in *out_msgs (caller must free each -// MsgData* and the array). Count in *out_count. If before_id==0, fetch latest. -int db_fetch_messages( - const char* room, MsgID before_id, size_t limit, MsgData*** out_msgs, - size_t* out_count -); - -#endif diff --git a/server/src/main.c b/server/src/main.c index ef95478..282b60b 100644 --- a/server/src/main.c +++ b/server/src/main.c @@ -1,5 +1,4 @@ #include "include/chat.h" -#include "include/db.h" #include "include/session.h" #include @@ -30,11 +29,6 @@ static struct lws_protocols protocols[] = { int main(void) { signal(SIGINT, handle_sigint); - if (db_init("var/data/chat.sqlite") != 0) { - fprintf(stderr, "Failed to initialize database.\n"); - return EXIT_FAILURE; - } - // Create libws context. struct lws_context_creation_info info; memset(&info, 0, sizeof(info)); @@ -55,7 +49,6 @@ int main(void) { // Cleanse. lws_context_destroy(context); - db_close(); printf("Server shutting down.\n"); return EXIT_SUCCESS; } diff --git a/server/start.sh b/server/start.sh new file mode 100755 index 0000000..9d79fee --- /dev/null +++ b/server/start.sh @@ -0,0 +1 @@ +make clean all run