Compare commits

..

1 Commits

Author SHA1 Message Date
d9f1c25c7c fix(database): make v2->v3 chatlog migration deterministic and FK-safe
All checks were successful
CI Code / Check spelling (pull_request) Successful in 18s
CI Code / Check coding style (pull_request) Successful in 34s
CI Code / Linux (debian) (pull_request) Successful in 4m45s
CI Code / Linux (ubuntu) (pull_request) Successful in 4m53s
CI Code / Code Coverage (pull_request) Successful in 6m57s
CI Code / Linux (arch) (pull_request) Successful in 10m19s
_migrate_to_v3 deduplicated duplicate archive_id rows with a bare-column
GROUP BY, which lets SQLite keep an arbitrary row per group (in practice the
older row, dropping the latest), and copied rows with SELECT *, which relies
on positional column matching between the v2 and v3 schemas.

- Keep MAX(id) per archive_id group ("latest insert wins") instead of
  GROUP BY's implementation-defined pick.
- Sweep replaces_db_id / replaced_by_db_id after dedupe, NULLing references
  to dropped rows so the migrated table carries no dangling XEP-0308 (LMC)
  links (the "Broken LMC reference" condition verify_integrity flags).
- List columns explicitly instead of SELECT * so the copy cannot mis-bind,
  and note at the schema that column changes must be mirrored here.

Refs #128
2026-05-27 14:20:45 +03:00
23 changed files with 38 additions and 218 deletions

View File

@@ -2638,8 +2638,7 @@ static const struct cmd_t command_defs[] = {
CMD_DESC(
"Spawn external editor to edit message. "
"After editing the inputline may appear empty. Press enter to send the text anyways. "
"Use /executable to set your favourite editor. "
"Ctrl-Z in the editor aborts the session and returns to Profanity." )
"Use /executable to set your favourite editor." )
},
{ CMD_PREAMBLE("/correct-editor",

View File

@@ -748,17 +748,17 @@ call_external(gchar** argv)
*
* This function constructs an argument vector (argv) based on the provided template string, replacing placeholders ("%u" and "%p") with the provided URL and filename, respectively.
*
* @param template_fmt The template string with placeholders.
* @param url The URL to replace "%u" (or NULL to skip).
* @param filename The filename to replace "%p" (or NULL to skip).
* @return The constructed argument vector (argv) as a null-terminated array of strings.
* @param template The template string with placeholders.
* @param url The URL to replace "%u" (or NULL to skip).
* @param filename The filename to replace "%p" (or NULL to skip).
* @return The constructed argument vector (argv) as a null-terminated array of strings.
*
* @note Remember to free the returned argument vector using `auto_gcharv` or `g_strfreev()`.
*/
gchar**
format_call_external_argv(const char* template_fmt, const char* url, const char* filename)
format_call_external_argv(const char* template, const char* url, const char* filename)
{
gchar** argv = g_strsplit(template_fmt, " ", 0);
gchar** argv = g_strsplit(template, " ", 0);
guint num_args = 0;
while (argv[num_args]) {
@@ -767,7 +767,7 @@ format_call_external_argv(const char* template_fmt, const char* url, const char*
argv[num_args] = g_strdup(url);
} else if (0 == g_strcmp0(argv[num_args], "%p") && filename != NULL) {
g_free(argv[num_args]);
argv[num_args] = g_strdup(filename);
argv[num_args] = strdup(filename);
}
num_args++;
}

View File

@@ -181,7 +181,7 @@ void get_file_paths_recursive(const char* directory, GSList** contents);
gchar* get_random_string(int length);
gboolean call_external(gchar** argv);
gchar** format_call_external_argv(const char* template_fmt, const char* url, const char* filename);
gchar** format_call_external_argv(const char* template, const char* url, const char* filename);
gchar* unique_filename_from_url(const char* url, const char* path);
gchar* get_expanded_path(const char* path);

View File

@@ -10,8 +10,6 @@
#ifndef EVENT_COMMON_H
#define EVENT_COMMON_H
#include "glib.h"
void ev_disconnect_cleanup(void);
void ev_inc_connection_counter(void);
void ev_reset_connection_counter(void);

View File

@@ -341,7 +341,7 @@ sv_ev_room_message(ProfMessage* message)
status_bar_active(num, WIN_MUC, mucwin->roomjid);
if ((g_strcmp0(mynick, message->from_jid->resourcepart) != 0) && (prefs_get_boolean(PREF_BEEP))) {
ui_beep();
beep();
}
// not currently on groupchat window
@@ -349,7 +349,7 @@ sv_ev_room_message(ProfMessage* message)
status_bar_new(num, WIN_MUC, mucwin->roomjid);
if ((g_strcmp0(mynick, message->from_jid->resourcepart) != 0) && (prefs_get_boolean(PREF_FLASH))) {
ui_flash();
flash();
}
cons_show_incoming_room_message(message->from_jid->resourcepart, mucwin->roomjid, num, mention, triggers, mucwin->unread, window);

View File

@@ -10,8 +10,6 @@
#ifndef PGP_GPG_H
#define PGP_GPG_H
#include "glib.h"
typedef struct pgp_key_t
{
gchar* id;

View File

@@ -70,38 +70,6 @@ static void _connect_default(const char* const account);
pthread_mutex_t lock;
static gboolean force_quit = FALSE;
static volatile sig_atomic_t suspend_requested = FALSE;
static volatile sig_atomic_t cont_requested = FALSE;
static void
_handle_suspend_signals(void)
{
if (cont_requested) {
cont_requested = FALSE;
if (!editor_is_active()) {
clearok(stdscr, TRUE);
ui_resize();
}
}
if (editor_check_stopped()) {
log_info("[Editor] stopped; aborting back to profanity");
suspend_requested = FALSE;
editor_emergency_kill();
} else if (suspend_requested) {
suspend_requested = FALSE;
if (editor_is_active()) {
log_info("[Editor] SIGTSTP from vim group; deferring to next poll");
} else {
log_info("[Suspend] dropping to shell");
if (!isendwin()) {
endwin();
}
kill(0, SIGSTOP);
}
}
}
void
prof_run(gchar* log_level, gchar* account_name, gchar* config_file, gchar* log_file, gchar* theme_name, gchar** commands)
{
@@ -125,7 +93,6 @@ prof_run(gchar* log_level, gchar* account_name, gchar* config_file, gchar* log_f
int waittime = 0;
while (cont && !force_quit) {
log_stderr_handler();
_handle_suspend_signals();
session_check_autoaway();
line = commands ? *commands : inp_readline();
@@ -215,18 +182,6 @@ sigterm_handler(int sig)
force_quit = TRUE;
}
static void
sigtstp_handler(int sig)
{
suspend_requested = TRUE;
}
static void
sigcont_handler(int sig)
{
cont_requested = TRUE;
}
static void
_init(char* log_level, char* config_file, char* log_file, char* theme_name)
{
@@ -234,8 +189,7 @@ _init(char* log_level, char* config_file, char* log_file, char* theme_name)
// ignore SIGPIPE
signal(SIGPIPE, SIG_IGN);
signal(SIGINT, SIG_IGN);
signal(SIGTSTP, sigtstp_handler);
signal(SIGCONT, sigcont_handler);
signal(SIGTSTP, SIG_IGN);
signal(SIGWINCH, ui_sigwinch_handler);
signal(SIGTERM, sigterm_handler);
signal(SIGHUP, sigterm_handler);

View File

@@ -10,10 +10,8 @@
#ifndef BOOKMARK_IGNORE_H
#define BOOKMARK_IGNORE_H
#include "xmpp/xmpp.h"
void bookmark_ignore_on_connect(const char* const barejid);
void bookmark_ignore_on_disconnect();
void bookmark_ignore_on_disconnect(void);
gboolean bookmark_ignored(Bookmark* bookmark);
gchar** bookmark_ignore_list(gsize* len);
void bookmark_ignore_add(const char* const barejid);

View File

@@ -32,15 +32,9 @@ typedef struct EditorContext
void* user_data;
} EditorContext;
static gboolean editor_active = FALSE; // re-entrancy guard
static pid_t editor_pid = 0;
static void
_editor_exit_cb(GPid pid, gint status, gpointer data)
{
editor_active = FALSE;
editor_pid = 0;
EditorContext* ctx = data;
gchar* contents = NULL;
GError* error = NULL;
@@ -58,10 +52,8 @@ _editor_exit_cb(GPid pid, gint status, gpointer data)
cons_show_error("Could not read edited content: %s", error->message);
g_error_free(error);
}
} else if (WIFEXITED(status)) {
cons_show_error("Editor exited with status %d", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
cons_show("Editor session cancelled.");
} else {
cons_show_error("Editor exited with error status %d", WEXITSTATUS(status));
}
if (remove(ctx->filename) != 0) {
@@ -78,11 +70,6 @@ _editor_exit_cb(GPid pid, gint status, gpointer data)
gboolean
launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* data), void* user_data)
{
if (editor_active) {
cons_show_error("An editor session is already active.");
return TRUE;
}
auto_gchar gchar* filename = NULL;
auto_gerror GError* glib_error = NULL;
const char* jid = connection_get_barejid();
@@ -131,7 +118,6 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat
ctx->callback = callback;
ctx->user_data = user_data;
editor_active = TRUE;
ui_suspend();
// Force GLib to install its SIGCHLD handler before forking. Creating a
@@ -143,7 +129,6 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat
pid_t pid = fork();
if (pid == -1) {
log_error("[Editor] Failed to fork: %s", strerror(errno));
editor_active = FALSE;
ui_resume();
ui_resize();
cons_show_error("Failed to start editor: %s", strerror(errno));
@@ -154,8 +139,7 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat
} else if (pid == 0) {
// Child process: Inherits TTY from parent
// SIGTSTP=SIG_DFL lets vim's :stop / Ctrl-Z work; profanity catches
// the STOPPED state via editor_check_stopped() and drops to the shell.
// Reset signal handlers that profanity sets so the editor doesn't inherit them
signal(SIGINT, SIG_DFL);
signal(SIGTSTP, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
@@ -167,39 +151,7 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat
}
// Parent process: Watch the child asynchronously
editor_pid = pid;
g_child_watch_add((GPid)pid, _editor_exit_cb, ctx);
g_strfreev(editor_argv);
return FALSE;
}
void
editor_emergency_kill(void)
{
if (!editor_active || editor_pid <= 0) {
return;
}
log_warning("[Editor] aborting editor pid %d", editor_pid);
kill(editor_pid, SIGCONT); // wake if STOPPED; no-op otherwise
kill(editor_pid, SIGTERM);
// _editor_exit_cb will restore the UI when the child reaps.
}
gboolean
editor_is_active(void)
{
return editor_active;
}
gboolean
editor_check_stopped(void)
{
// Poll for STOPPED without reaping (g_child_watch handles exit via its
// own WNOHANG). Lets prof_run drop the group to the shell on Ctrl-Z.
if (!editor_active || editor_pid <= 0) {
return FALSE;
}
int status;
pid_t r = waitpid(editor_pid, &status, WNOHANG | WUNTRACED);
return (r == editor_pid && WIFSTOPPED(status));
}

View File

@@ -16,13 +16,4 @@
gboolean launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* data), void* user_data);
void editor_emergency_kill(void); // SIGCONT+SIGTERM the editor child
// TRUE if editor child is STOPPED (Ctrl-Z / :stop / gdb-attach). Does not
// reap; g_child_watch keeps handling exit.
gboolean editor_check_stopped(void);
// Editor-session flag accessor. Used by prof_run on SIGCONT resume.
gboolean editor_is_active(void);
#endif

View File

@@ -327,7 +327,7 @@ chatwin_incoming_msg(ProfChatWin* chatwin, ProfMessage* message, gboolean win_cr
cons_show_incoming_message(display_name, num, chatwin->unread, window);
if (prefs_get_boolean(PREF_FLASH)) {
ui_flash();
flash();
}
chatwin->unread++;
@@ -364,7 +364,7 @@ chatwin_incoming_msg(ProfChatWin* chatwin, ProfMessage* message, gboolean win_cr
wins_add_quotes_ac(window, message->plain, FALSE);
if (prefs_get_boolean(PREF_BEEP)) {
ui_beep();
beep();
}
}
@@ -399,6 +399,8 @@ chatwin_outgoing_msg(ProfChatWin* chatwin, const char* const message, const char
win_print_outgoing((ProfWin*)chatwin, enc_char, id, replace_id, display_message);
}
plugins_post_chat_message_display(myjid->barejid, myjid->resourcepart, display_message);
// Save last id and message for LMC
// Note: if the same message is to be corrected several times, the id of the original message is used in each case
// https://xmpp.org/extensions/xep-0308.html#rules
@@ -432,6 +434,7 @@ chatwin_outgoing_carbon(ProfChatWin* chatwin, ProfMessage* message)
win_print_outgoing(window, enc_char, message->id, message->replace_id, message->plain);
plugins_post_chat_message_display(message->from_jid->barejid, message->from_jid->resourcepart, message->plain);
message->plain = old_plain;
int num = wins_get_num(window);
@@ -558,6 +561,7 @@ _chatwin_history(ProfChatWin* chatwin, const char* const contact_barejid)
if (plugin_msg)
msg->plain = plugin_msg;
win_print_history((ProfWin*)chatwin, msg);
plugins_post_chat_message_display(msg->from_jid->barejid, msg->from_jid->resourcepart, msg->plain);
msg->plain = old_plain;
curr = g_slist_next(curr);
}
@@ -597,6 +601,7 @@ chatwin_db_history(ProfChatWin* chatwin, const gchar* start_time, const gchar* e
} else {
win_print_history((ProfWin*)chatwin, msg);
}
plugins_post_chat_message_display(msg->from_jid->barejid, msg->from_jid->resourcepart, msg->plain);
msg->plain = old_plain;
curr = g_slist_next(curr);
}

View File

@@ -54,7 +54,6 @@
static int inp_size;
static gboolean perform_resize = FALSE;
static gboolean ui_suspended = FALSE;
static GTimer* ui_idle_time;
static WINDOW* main_scr;
@@ -64,14 +63,6 @@ static Display* display;
static void _ui_draw_term_title(void);
// Fail-safe gate: authoritative flag (doesn't depend on isendwin staying
// TRUE), plus the regular endwin check for shutdown.
static gboolean
_ui_redraw_suspended(void)
{
return ui_suspended || isendwin();
}
static void
_ui_close(void)
{
@@ -129,7 +120,8 @@ ui_sigwinch_handler(int sig)
void
ui_update(void)
{
if (_ui_redraw_suspended()) {
// UI is suspended
if (isendwin()) {
return;
}
@@ -146,7 +138,7 @@ ui_update(void)
title_bar_update_virtual();
status_bar_draw();
inp_put_back();
prof_doupdate();
doupdate();
if (perform_resize) {
perform_resize = FALSE;
@@ -188,49 +180,21 @@ void
ui_suspend(void)
{
inp_suspend();
// Flush before flipping the flag (prof_doupdate skips while suspended).
prof_doupdate();
ui_suspended = TRUE;
endwin();
}
void
ui_resume(void)
{
ui_suspended = FALSE;
clearok(stdscr, TRUE); // ncurses can't see vim's screen clobber
refresh();
inp_resume();
}
void
ui_beep(void)
{
if (!isendwin()) {
beep();
}
}
void
ui_flash(void)
{
if (!isendwin()) {
flash();
}
}
void
prof_doupdate(void)
{
if (!_ui_redraw_suspended()) {
doupdate();
}
}
void
ui_resize(void)
{
if (_ui_redraw_suspended()) {
// UI is suspended
if (isendwin()) {
return;
}
@@ -252,7 +216,8 @@ ui_resize(void)
void
ui_redraw(void)
{
if (_ui_redraw_suspended()) {
// UI is suspended
if (isendwin()) {
return;
}

View File

@@ -182,11 +182,7 @@ inp_readline(void)
{
// UI is suspended
if (is_suspended || isendwin()) {
// Mirror the normal-path unlock so workers aren't starved
// for the whole editor session.
pthread_mutex_unlock(&lock);
g_usleep(100000); // 100ms
pthread_mutex_lock(&lock);
return NULL;
}
@@ -315,13 +311,11 @@ inp_get_line(void)
werase(inp_win);
wmove(inp_win, 0, 0);
_inp_win_update_virtual();
prof_doupdate();
doupdate();
char* line = NULL;
while (!line) {
line = inp_readline();
ui_update();
while (g_main_context_iteration(NULL, FALSE)) // non-blocking; bounded by ready sources
;
}
status_bar_clear_prompt();
return line;
@@ -344,14 +338,12 @@ inp_get_password(void)
werase(inp_win);
wmove(inp_win, 0, 0);
_inp_win_update_virtual();
prof_doupdate();
doupdate();
char* password = NULL;
get_password = TRUE;
while (!password) {
password = inp_readline();
ui_update();
while (g_main_context_iteration(NULL, FALSE)) // non-blocking; bounded by ready sources
;
}
get_password = FALSE;
status_bar_clear_prompt();
@@ -467,7 +459,7 @@ _inp_write(char* line, int offset)
_inp_win_handle_scroll();
_inp_win_update_virtual();
prof_doupdate();
doupdate();
}
static int

View File

@@ -57,7 +57,7 @@ privwin_incoming_msg(ProfPrivateWin* privatewin, ProfMessage* message)
privatewin->unread++;
if (prefs_get_boolean(PREF_FLASH)) {
ui_flash();
flash();
}
}
@@ -65,7 +65,7 @@ privwin_incoming_msg(ProfPrivateWin* privatewin, ProfMessage* message)
wins_add_quotes_ac(window, message->plain, TRUE);
if (prefs_get_boolean(PREF_BEEP)) {
ui_beep();
beep();
}
if (notify) {

View File

@@ -10,8 +10,6 @@
#ifndef UI_TITLEBAR_H
#define UI_TITLEBAR_H
#include "glib.h"
void create_title_bar(void);
void free_title_bar(void);
void title_bar_update_virtual(void);

View File

@@ -43,9 +43,6 @@ void ui_load_colours(void);
void ui_update(void);
void ui_redraw(void);
void ui_resize(void);
// doupdate() wrapper that skips when the UI is suspended. Use this instead
// of raw doupdate() to avoid the per-site whitelist class of bug.
void prof_doupdate(void);
void ui_focus_win(ProfWin* window);
void ui_sigwinch_handler(int sig);
void ui_handle_otr_error(const char* const barejid, const char* const message);
@@ -87,8 +84,6 @@ void ui_handle_recipient_error(const char* const recipient, const char* const er
void ui_handle_error(const char* const err_msg);
void ui_clear_win_title(void);
void ui_goodbye_title(void);
void ui_beep(void);
void ui_flash(void);
void ui_handle_room_configuration_form_error(const char* const roomjid, const char* const message);
void ui_handle_room_config_submit_result(const char* const roomjid);
void ui_handle_room_config_submit_result_error(const char* const roomjid, const char* const message);

View File

@@ -43,7 +43,7 @@
#include "ai/ai_client.h"
static const int PAD_MIN_HEIGHT = 100;
static const int PAD_THRESHOLD = 12000; // above buffer cap (~9000): reclaims dead pad space, never fires while scrolling
static const int PAD_THRESHOLD = 3000;
static gboolean _in_redraw = FALSE;
static void
@@ -2172,10 +2172,9 @@ win_redraw(ProfWin* window)
unsigned int size = buffer_size(window->layout->buffer);
_in_redraw = TRUE;
// size the pad to fit the whole buffer (plus headroom) and erase it
// shrink pad back to minimum size and erase it
int cols = getmaxx(window->layout->win);
int needed = window->layout->buffer->lines + 100;
wresize(window->layout->win, MAX(needed, PAD_MIN_HEIGHT), cols);
wresize(window->layout->win, PAD_MIN_HEIGHT, cols);
werase(window->layout->win);
for (unsigned int i = 0; i < size; i++) {

View File

@@ -10,8 +10,6 @@
#ifndef XMPP_BLOCKING_H
#define XMPP_BLOCKING_H
#include <strophe.h>
void blocking_request(void);
int blocked_set_handler(xmpp_stanza_t* stanza);
int reporting_set_handler(xmpp_stanza_t* stanza);

View File

@@ -10,8 +10,6 @@
#ifndef XMPP_IQ_H
#define XMPP_IQ_H
#include <strophe.h>
typedef int (*ProfIqCallback)(xmpp_stanza_t* const stanza, void* const userdata);
typedef void (*ProfIqFreeCallback)(void* userdata);

View File

@@ -7,8 +7,6 @@
* SPDX-License-Identifier: GPL-3.0-or-later WITH OpenSSL-exception
*/
#include "glib.h"
/*!
* \page OX OX Implementation
*

View File

@@ -10,9 +10,6 @@
#ifndef XMPP_ROSTER_H
#define XMPP_ROSTER_H
#include "glib.h"
#include <strophe.h>
void roster_request(void);
void roster_set_handler(xmpp_stanza_t* const stanza);
void roster_result_handler(xmpp_stanza_t* const stanza);

View File

@@ -12,7 +12,6 @@
#include "ui/win_types.h"
#include "xmpp/vcard.h"
#include <strophe.h>
vCard* vcard_new();
void vcard_free(vCard* vcard);

View File

@@ -85,10 +85,6 @@ void
ui_resize(void)
{
}
void
prof_doupdate(void)
{
}
void
ui_focus_win(ProfWin* win)
@@ -1479,13 +1475,3 @@ ui_is_suspended(void)
{
return FALSE;
}
void
ui_beep(void)
{
}
void
ui_flash(void)
{
}