/src/neomutt/email/thread.c
Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * @file |
3 | | * Create/manipulate threading in emails |
4 | | * |
5 | | * @authors |
6 | | * Copyright (C) 2018-2023 Richard Russon <rich@flatcap.org> |
7 | | * Copyright (C) 2019 Federico Kircheis <federico.kircheis@gmail.com> |
8 | | * Copyright (C) 2020 Pietro Cerutti <gahr@gahr.ch> |
9 | | * Copyright (C) 2021 Eric Blake <eblake@redhat.com> |
10 | | * |
11 | | * @copyright |
12 | | * This program is free software: you can redistribute it and/or modify it under |
13 | | * the terms of the GNU General Public License as published by the Free Software |
14 | | * Foundation, either version 2 of the License, or (at your option) any later |
15 | | * version. |
16 | | * |
17 | | * This program is distributed in the hope that it will be useful, but WITHOUT |
18 | | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
19 | | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
20 | | * details. |
21 | | * |
22 | | * You should have received a copy of the GNU General Public License along with |
23 | | * this program. If not, see <http://www.gnu.org/licenses/>. |
24 | | */ |
25 | | |
26 | | /** |
27 | | * @page email_thread Email threading |
28 | | * |
29 | | * Create/manipulate threading in emails |
30 | | */ |
31 | | |
32 | | #include "config.h" |
33 | | #include <stdbool.h> |
34 | | #include <stdlib.h> |
35 | | #include "mutt/lib.h" |
36 | | #include "thread.h" |
37 | | #include "email.h" |
38 | | #include "envelope.h" |
39 | | |
40 | | /** |
41 | | * is_descendant - Is one thread a descendant of another |
42 | | * @param a Parent thread |
43 | | * @param b Child thread |
44 | | * @retval true b is a descendent of a (child, grandchild, etc) |
45 | | */ |
46 | | bool is_descendant(const struct MuttThread *a, const struct MuttThread *b) |
47 | 0 | { |
48 | 0 | while (a) |
49 | 0 | { |
50 | 0 | if (a == b) |
51 | 0 | return true; |
52 | 0 | a = a->parent; |
53 | 0 | } |
54 | 0 | return false; |
55 | 0 | } |
56 | | |
57 | | /** |
58 | | * unlink_message - Break the message out of the thread |
59 | | * @param[in,out] old Root of thread |
60 | | * @param[in] cur Child thread to separate |
61 | | * |
62 | | * Remove cur and its descendants from their current location. Also make sure |
63 | | * ancestors of cur no longer are sorted by the fact that cur is their |
64 | | * descendant. |
65 | | */ |
66 | | void unlink_message(struct MuttThread **old, struct MuttThread *cur) |
67 | 0 | { |
68 | 0 | if (!old || !cur) |
69 | 0 | return; |
70 | | |
71 | 0 | struct MuttThread *tmp = NULL; |
72 | |
|
73 | 0 | if (cur->prev) |
74 | 0 | cur->prev->next = cur->next; |
75 | 0 | else |
76 | 0 | *old = cur->next; |
77 | |
|
78 | 0 | if (cur->next) |
79 | 0 | cur->next->prev = cur->prev; |
80 | |
|
81 | 0 | if (cur->sort_thread_key) |
82 | 0 | { |
83 | 0 | for (tmp = cur->parent; |
84 | 0 | tmp && (tmp->sort_thread_key == cur->sort_thread_key); tmp = tmp->parent) |
85 | 0 | { |
86 | 0 | tmp->sort_thread_key = NULL; |
87 | 0 | } |
88 | 0 | } |
89 | 0 | if (cur->sort_aux_key) |
90 | 0 | { |
91 | 0 | for (tmp = cur->parent; tmp && (tmp->sort_aux_key == cur->sort_aux_key); tmp = tmp->parent) |
92 | 0 | tmp->sort_aux_key = NULL; |
93 | 0 | } |
94 | 0 | } |
95 | | |
96 | | /** |
97 | | * insert_message - Insert a message into a thread |
98 | | * @param[in,out] add New thread to add |
99 | | * @param[in] parent Parent of new thread |
100 | | * @param[in] cur Current thread to add after |
101 | | * |
102 | | * add cur as a prior sibling of *add, with parent parent |
103 | | */ |
104 | | void insert_message(struct MuttThread **add, struct MuttThread *parent, struct MuttThread *cur) |
105 | 0 | { |
106 | 0 | if (!cur || !add) |
107 | 0 | return; |
108 | | |
109 | 0 | if (*add) |
110 | 0 | (*add)->prev = cur; |
111 | |
|
112 | 0 | cur->parent = parent; |
113 | 0 | cur->next = *add; |
114 | 0 | cur->prev = NULL; |
115 | 0 | *add = cur; |
116 | 0 | } |
117 | | |
118 | | /** |
119 | | * find_virtual - Find an email with a Virtual message number |
120 | | * @param cur Thread to search |
121 | | * @param reverse If true, reverse the direction of the search |
122 | | * @retval ptr Matching Email |
123 | | */ |
124 | | struct Email *find_virtual(struct MuttThread *cur, bool reverse) |
125 | 0 | { |
126 | 0 | if (!cur) |
127 | 0 | return NULL; |
128 | | |
129 | 0 | struct MuttThread *top = NULL; |
130 | |
|
131 | 0 | if (cur->message && (cur->message->vnum >= 0)) |
132 | 0 | return cur->message; |
133 | | |
134 | 0 | top = cur; |
135 | 0 | cur = cur->child; |
136 | 0 | if (!cur) |
137 | 0 | return NULL; |
138 | | |
139 | 0 | while (reverse && cur->next) |
140 | 0 | cur = cur->next; |
141 | |
|
142 | 0 | while (true) |
143 | 0 | { |
144 | 0 | if (cur->message && (cur->message->vnum >= 0)) |
145 | 0 | return cur->message; |
146 | | |
147 | 0 | if (cur->child) |
148 | 0 | { |
149 | 0 | cur = cur->child; |
150 | |
|
151 | 0 | while (reverse && cur->next) |
152 | 0 | cur = cur->next; |
153 | 0 | } |
154 | 0 | else if (reverse ? cur->prev : cur->next) |
155 | 0 | { |
156 | 0 | cur = reverse ? cur->prev : cur->next; |
157 | 0 | } |
158 | 0 | else |
159 | 0 | { |
160 | 0 | while (!(reverse ? cur->prev : cur->next)) |
161 | 0 | { |
162 | 0 | cur = cur->parent; |
163 | 0 | if (cur == top) |
164 | 0 | return NULL; |
165 | 0 | } |
166 | 0 | cur = reverse ? cur->prev : cur->next; |
167 | 0 | } |
168 | | /* not reached */ |
169 | 0 | } |
170 | 0 | } |
171 | | |
172 | | /** |
173 | | * clean_references - Update email references for a broken Thread |
174 | | * @param brk Broken thread |
175 | | * @param cur Current thread |
176 | | */ |
177 | | void clean_references(struct MuttThread *brk, struct MuttThread *cur) |
178 | 0 | { |
179 | 0 | struct ListNode *ref = NULL; |
180 | 0 | bool done = false; |
181 | |
|
182 | 0 | for (; cur; cur = cur->next, done = false) |
183 | 0 | { |
184 | | /* parse subthread recursively */ |
185 | 0 | clean_references(brk, cur->child); |
186 | |
|
187 | 0 | if (!cur->message) |
188 | 0 | break; /* skip pseudo-message */ |
189 | | |
190 | | /* Looking for the first bad reference according to the new threading. |
191 | | * Optimal since NeoMutt stores the references in reverse order, and the |
192 | | * first loop should match immediately for mails respecting RFC2822. */ |
193 | 0 | for (struct MuttThread *p = brk; !done && p; p = p->parent) |
194 | 0 | { |
195 | 0 | for (ref = STAILQ_FIRST(&cur->message->env->references); |
196 | 0 | p->message && ref; ref = STAILQ_NEXT(ref, entries)) |
197 | 0 | { |
198 | 0 | if (mutt_istr_equal(ref->data, p->message->env->message_id)) |
199 | 0 | { |
200 | 0 | done = true; |
201 | 0 | break; |
202 | 0 | } |
203 | 0 | } |
204 | 0 | } |
205 | |
|
206 | 0 | if (done) |
207 | 0 | { |
208 | 0 | struct Email *e = cur->message; |
209 | | |
210 | | /* clearing the References: header from obsolete Message-ID(s) */ |
211 | 0 | struct ListNode *np = NULL; |
212 | 0 | while ((np = STAILQ_NEXT(ref, entries))) |
213 | 0 | { |
214 | 0 | STAILQ_REMOVE_AFTER(&cur->message->env->references, ref, entries); |
215 | 0 | FREE(&np->data); |
216 | 0 | FREE(&np); |
217 | 0 | } |
218 | |
|
219 | 0 | e->changed = true; |
220 | 0 | e->env->changed |= MUTT_ENV_CHANGED_REFS; |
221 | 0 | } |
222 | 0 | } |
223 | 0 | } |
224 | | |
225 | | /** |
226 | | * mutt_break_thread - Break the email Thread |
227 | | * @param e Email to break at |
228 | | */ |
229 | | void mutt_break_thread(struct Email *e) |
230 | 0 | { |
231 | 0 | if (!e) |
232 | 0 | return; |
233 | | |
234 | 0 | mutt_list_free(&e->env->in_reply_to); |
235 | 0 | mutt_list_free(&e->env->references); |
236 | 0 | e->changed = true; |
237 | 0 | e->env->changed |= (MUTT_ENV_CHANGED_IRT | MUTT_ENV_CHANGED_REFS); |
238 | |
|
239 | 0 | clean_references(e->thread, e->thread->child); |
240 | 0 | } |