thread.c (7726B)
1 /* 2 * ============================================================================ 3 * ███████╗████████╗ █████╗ ███╗ ███╗ █████╗ ██╗██╗ 4 * ██╔════╝╚══██╔══╝██╔══██╗████╗ ████║██╔══██╗██║██║ 5 * ███████╗ ██║ ███████║██╔████╔██║███████║██║██║ 6 * ╚════██║ ██║ ██╔══██║██║╚██╔╝██║██╔══██║██║██║ 7 * ███████║ ██║ ██║ ██║██║ ╚═╝ ██║██║ ██║██║███████╗ 8 * ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚══════╝ 9 * ============================================================================ 10 * 11 * Copyright (C) 2026 Binkd. 12 * 13 * This file is part of stamail. 14 * 15 * stamail is free software: you can redistribute it and/or modify it under the 16 * terms of the GNU General Public License as published by the Free Software 17 * Foundation, either version 3 of the License, or (at your option) any later 18 * version. 19 * 20 * stamail is distributed in the hope that it will be useful, but WITHOUT ANY 21 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 22 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 23 * details. 24 * 25 * You should have received a copy of the GNU General Public License 26 * along with stamail. If not, see <https://www.gnu.org/licenses/>. 27 * 28 * ============================================================================= 29 */ 30 31 #include <stdio.h> 32 #include <stdlib.h> 33 #include <string.h> 34 #include <strings.h> 35 #include <ctype.h> 36 37 #include "stamail.h" 38 39 #define HASH_SIZE 1024 40 41 struct msg_entry { 42 const char *msgid; 43 int msgid_len; 44 struct thread_node *node; 45 struct msg_entry *next; 46 }; 47 48 static struct msg_entry *msg_table[HASH_SIZE]; 49 50 /* Simple hash function */ 51 static unsigned int hash_msgid(const char *msgid, int len) { 52 unsigned int hash = 5381; 53 for (int i = 0; i < len; i++) 54 hash = ((hash << 5) + hash) + (unsigned char)msgid[i]; 55 return hash % HASH_SIZE; 56 } 57 58 /* Add message to hash table */ 59 static void hash_add(const char *msgid, int msgid_len, struct thread_node *node) { 60 unsigned int h = hash_msgid(msgid, msgid_len); 61 struct msg_entry *e = malloc(sizeof(*e)); 62 if (!e) return; 63 64 e->msgid = msgid; 65 e->msgid_len = msgid_len; 66 e->node = node; 67 e->next = msg_table[h]; 68 msg_table[h] = e; 69 } 70 71 /* Look up message by Message-ID */ 72 static struct thread_node * hash_find(const char *msgid, int msgid_len) { 73 unsigned int h = hash_msgid(msgid, msgid_len); 74 75 for (struct msg_entry *e = msg_table[h]; e; e = e->next) { 76 if (e->msgid_len == msgid_len && 77 memcmp(e->msgid, msgid, msgid_len) == 0) { 78 return e->node; 79 } 80 } 81 return NULL; 82 } 83 84 /* Clean up hash table */ 85 static void hash_free(void) { 86 for (int i = 0; i < HASH_SIZE; i++) { 87 struct msg_entry *e = msg_table[i]; 88 while (e) { 89 struct msg_entry *next = e->next; 90 free(e); 91 e = next; 92 } 93 msg_table[i] = NULL; 94 } 95 } 96 97 /* Create a new thread node */ 98 static struct thread_node *thread_node_new(struct message *msg) { 99 struct thread_node *n = calloc(1, sizeof(*n)); 100 if (!n) return NULL; 101 102 n->msg = msg; 103 return n; 104 } 105 106 /* Add child to parent node */ 107 static void add_child(struct thread_node *parent, struct thread_node *child) { 108 if (!parent || !child) return; 109 110 child->parent = parent; 111 child->sibling = parent->child; 112 parent->child = child; 113 parent->num_children++; 114 115 /* Set depth */ 116 child->depth = parent->depth + 1; 117 } 118 119 #if 0 120 /* Normalize subject (remove Re:, Fwd:, [list], etc.) */ 121 static char * normalize_subject(const char *subject, int len) { 122 static char buf[512]; 123 const char *s = subject; 124 const char *end = subject + len; 125 126 while (s < end) { 127 while (s < end && isspace(*s)) s++; 128 129 if (end - s >= 3 && strncasecmp(s, "re:", 3) == 0) { 130 s += 3; 131 continue; 132 } 133 134 if (end - s >= 4 && strncasecmp(s, "fwd:", 4) == 0) { 135 s += 4; 136 continue; 137 } 138 139 /* Look for [list-name] */ 140 if (s < end && *s == '[') { 141 while (s < end && *s != ']') s++; 142 if (s < end) s++; /* skip ] */ 143 continue; 144 } 145 146 break; 147 } 148 149 while (s < end && isspace(*s)) s++; 150 151 int out = 0; 152 while (s < end && out < (int)sizeof(buf) - 1) { 153 buf[out++] = *s++; 154 } 155 buf[out] = '\0'; 156 157 return buf; 158 } 159 #endif 160 161 /* Build thread tree from message list */ 162 struct thread_node **build_threads(struct message_node *messages, int *num_threads) { 163 struct thread_node **roots = NULL; 164 int root_count = 0; 165 int root_capacity = 64; 166 167 roots = malloc(root_capacity * sizeof(struct thread_node *)); 168 if (!roots) return NULL; 169 170 /* Create thread nodes for all messages */ 171 for (struct message_node *mn = messages; mn; mn = mn->next) { 172 struct thread_node *node = thread_node_new(&mn->m); 173 if (!node) continue; 174 175 hash_add(mn->m.id, mn->m.id_len, node); 176 } 177 178 /* Build parent-child relationships */ 179 for (struct message_node *mn = messages; mn; mn = mn->next) { 180 struct thread_node *node = hash_find(mn->m.id, mn->m.id_len); 181 if (!node) continue; 182 183 /* Look for parent via In-Reply-To */ 184 if (mn->m.in_reply_to && mn->m.in_reply_to_len > 0) { 185 struct thread_node *parent = hash_find(mn->m.in_reply_to, 186 mn->m.in_reply_to_len); 187 if (parent) { 188 add_child(parent, node); 189 continue; 190 } 191 } 192 193 /* TODO: Parse References header for more complex threading */ 194 195 /* No parent found - this is a root */ 196 if (!node->parent) { 197 if (root_count >= root_capacity) { 198 root_capacity *= 2; 199 roots = realloc(roots, root_capacity * sizeof(struct thread_node *)); 200 } 201 roots[root_count++] = node; 202 } 203 } 204 205 *num_threads = root_count; 206 hash_free(); 207 return roots; 208 } 209 210 /* Print thread tree for debugging (TODO Remove when tested) */ 211 static void print_thread_node(struct thread_node *node, const char *prefix, int is_last) { 212 if (!node) return; 213 214 printf("%s", prefix); 215 printf("%s", is_last ? "└─ " : "├─ "); 216 217 if (node->msg) { 218 printf("%.*s (depth=%d)\n", 219 node->msg->subject_len, node->msg->subject, 220 node->depth); 221 } else { 222 printf("(placeholder)\n"); 223 } 224 225 char new_prefix[1024]; 226 snprintf(new_prefix, sizeof(new_prefix), "%s%s", 227 prefix, is_last ? " " : "│ "); 228 229 struct thread_node *child = node->child; 230 while (child) { 231 int is_last_child = (child->sibling == NULL); 232 print_thread_node(child, new_prefix, is_last_child); 233 child = child->sibling; 234 } 235 } 236 237 void print_threads(struct thread_node **roots, int num_threads) { 238 printf("\n=== Thread Structure ===\n\n"); 239 240 for (int i = 0; i < num_threads; i++) { 241 printf("Thread %d:\n", i + 1); 242 print_thread_node(roots[i], "", 1); 243 printf("\n"); 244 } 245 }