stamail

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }