/* * buffer.c * * Message buffer implementation for CProof. * * This module provides an in-memory buffer for managing active chat entries in the * console-based XMPP client. Separate from persistent SQLite storage, it holds * messages and metadata solely for UI rendering, ensuring responsive scrolling and * display without exceeding ncurses pad limits (10k lines). By tracking rendered * line counts per entry, it proactively trims content to prevent overflow, enabling * seamless integration with ncurses for position-aware rendering. * * Key features and operations: * - Append/prepend entries (messages) with automatic line-based cleanup. * - Track cumulative lines for overflow prevention and efficient buffer sizing. * - Remove entries by ID or index; mark delivery receipts as received. * - Retrieve entries by index or ID for rendering and history queries. * - Store metadata including timestamps, senders, themes, and ncurses y-positions. * * CProof. Fork of Profanity (since 2025). * * Copyright (C) 2012 - 2019 James Booth * Copyright (C) 2019 - 2025 Michael Vetter * Copyright (C) 2025 CProof Developers * * Licensed under the GNU General Public License, version 3, * with the OpenSSL exception. See LICENSE for details. * * vim: expandtab:ts=4:sts=4:sw=4 */ #include "config.h" #include "ui/window_list.h" #include #include #include #include #include #ifdef HAVE_NCURSESW_NCURSES_H #include #elif HAVE_NCURSES_H #include #elif HAVE_CURSES_H #include #endif #include "log.h" #include "ui/window.h" #include "ui/buffer.h" #define STRDUP_OR_NULL(str) ((str) ? strdup(str) : NULL) static void _free_entry(ProfBuffEntry* entry); static ProfBuffEntry* _create_entry(const char* show_char, int pad_indent, GDateTime* time, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message, DeliveryReceipt* receipt, const char* const id, int y_start_pos, int y_end_pos); static void _buffer_add(ProfBuff buffer, const char* show_char, int pad_indent, GDateTime* time, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message, DeliveryReceipt* receipt, const char* const id, int y_start_pos, int y_end_pos, gboolean append); ProfBuff buffer_create(void) { ProfBuff new_buff = malloc(sizeof(struct prof_buff_t)); new_buff->entries = NULL; new_buff->lines = 0; return new_buff; } int buffer_size(ProfBuff buffer) { return g_slist_length(buffer->entries); } void buffer_free(ProfBuff buffer) { g_slist_free_full(buffer->entries, (GDestroyNotify)_free_entry); free(buffer); } void buffer_append(ProfBuff buffer, const char* show_char, int pad_indent, GDateTime* time, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message, DeliveryReceipt* receipt, const char* const id, int y_start_pos, int y_end_pos) { _buffer_add(buffer, show_char, pad_indent, time, flags, theme_item, display_from, from_jid, message, receipt, id, y_start_pos, y_end_pos, TRUE); } void buffer_prepend(ProfBuff buffer, const char* show_char, int pad_indent, GDateTime* time, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message, DeliveryReceipt* receipt, const char* const id, int y_start_pos, int y_end_pos) { _buffer_add(buffer, show_char, pad_indent, time, flags, theme_item, display_from, from_jid, message, receipt, id, y_start_pos, y_end_pos, FALSE); } static void _buffer_add(ProfBuff buffer, const char* show_char, int pad_indent, GDateTime* time, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message, DeliveryReceipt* receipt, const char* const id, int y_start_pos, int y_end_pos, gboolean append) { ProfBuffEntry* e = _create_entry(show_char, pad_indent, time, flags, theme_item, display_from, from_jid, message, receipt, id, y_start_pos, y_end_pos); buffer->lines += e->_lines; // With 10% margin to ensure overflow prevention while (buffer->lines >= 20 && g_slist_length(buffer->entries) > 1) { log_debug("DELETING"); // Delete message from the opposite size to free buffer GSList* buffer_entry_to_delete = append ? buffer->entries : g_slist_last(buffer->entries); ProfBuffEntry* entry_to_delete = (ProfBuffEntry*)buffer_entry_to_delete->data; // log_debug("(Messages left in buffer: %d) DELETING: %s", g_slist_length(buffer->entries), entry_to_delete->id); buffer->lines -= entry_to_delete->_lines; _free_entry(entry_to_delete); buffer->entries = g_slist_delete_link(buffer->entries, buffer_entry_to_delete); } if (from_jid && y_end_pos == y_start_pos) { // win_redraw will redraw with entries above removed, // it will also recalculate actual size of the message that caused overflow int old_lines = buffer->lines; win_redraw(wins_get_current()); log_debug("Ncurses Overflow! From: %s, position: %d, ID: %s, append: %s, used message buffer size: %d, rendered lines: (old: %d/ actual: %d/ max: %d)", from_jid, y_start_pos, id, append ? "TRUE" : "FALSE", g_slist_length(buffer->entries), old_lines, buffer->lines, PAD_SIZE); } buffer->entries = append ? g_slist_append(buffer->entries, e) : g_slist_prepend(buffer->entries, e); } void buffer_remove_entry_by_id(ProfBuff buffer, const char* const id) { GSList* entries = buffer->entries; while (entries) { ProfBuffEntry* entry = entries->data; if (entry->id && (g_strcmp0(entry->id, id) == 0)) { buffer->lines -= entry->_lines; _free_entry(entry); buffer->entries = g_slist_delete_link(buffer->entries, entries); break; } entries = g_slist_next(entries); } } void buffer_remove_entry(ProfBuff buffer, int entry) { GSList* node = g_slist_nth(buffer->entries, entry); ProfBuffEntry* e = node->data; buffer->lines -= e->_lines; _free_entry(e); buffer->entries = g_slist_delete_link(buffer->entries, node); } gboolean buffer_mark_received(ProfBuff buffer, const char* const id) { GSList* entries = buffer->entries; while (entries) { ProfBuffEntry* entry = entries->data; if (entry->receipt && g_strcmp0(entry->id, id) == 0) { if (!entry->receipt->received) { entry->receipt->received = TRUE; return TRUE; } } entries = g_slist_next(entries); } return FALSE; } ProfBuffEntry* buffer_get_entry(ProfBuff buffer, int entry) { GSList* node = g_slist_nth(buffer->entries, entry); return node->data; } ProfBuffEntry* buffer_get_entry_by_id(ProfBuff buffer, const char* const id) { GSList* entries = buffer->entries; while (entries) { ProfBuffEntry* entry = entries->data; if (g_strcmp0(entry->id, id) == 0) { return entry; } entries = g_slist_next(entries); } return NULL; } static ProfBuffEntry* _create_entry(const char* show_char, int pad_indent, GDateTime* time, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message, DeliveryReceipt* receipt, const char* const id, int y_start_pos, int y_end_pos) { ProfBuffEntry* e = malloc(sizeof(struct prof_buff_entry_t)); e->show_char = STRDUP_OR_NULL(show_char); e->pad_indent = pad_indent; e->flags = flags; e->theme_item = theme_item; e->time = g_date_time_ref(time); e->display_from = STRDUP_OR_NULL(display_from); e->from_jid = STRDUP_OR_NULL(from_jid); e->message = STRDUP_OR_NULL(message); e->receipt = receipt; e->id = STRDUP_OR_NULL(id); e->y_start_pos = y_start_pos; e->y_end_pos = y_end_pos; e->_lines = e->y_end_pos - e->y_start_pos; return e; } static void _free_entry(ProfBuffEntry* entry) { free(entry->id); free(entry->message); free(entry->from_jid); free(entry->display_from); g_date_time_unref(entry->time); free(entry->show_char); free(entry->receipt); free(entry); }