/src/dovecot/src/lib-storage/index/index-thread-finish.c
Line | Count | Source |
1 | | /* Copyright (c) Dovecot authors, see top-level COPYING file */ |
2 | | |
3 | | #include "lib.h" |
4 | | #include "array.h" |
5 | | #include "hash.h" |
6 | | #include "imap-base-subject.h" |
7 | | #include "mail-storage-private.h" |
8 | | #include "index-thread-private.h" |
9 | | |
10 | | |
11 | | struct mail_thread_shadow_node { |
12 | | uint32_t first_child_idx, next_sibling_idx; |
13 | | }; |
14 | | |
15 | | struct mail_thread_root_node { |
16 | | /* node.idx usually points to indexes from mail hash. However |
17 | | REFERENCES step (5) may add temporary dummy roots. They use larger |
18 | | index numbers than exist in the hash. */ |
19 | | struct mail_thread_child_node node; |
20 | | |
21 | | /* Used temporarily by (5)(B) base subject gathering. |
22 | | root_idx1 is node's index in roots[] array + 1. |
23 | | parent_root_idx points to root_idx1, or 0 for root. */ |
24 | | unsigned int root_idx1; |
25 | | uint32_t parent_root_idx1; |
26 | | |
27 | | /* subject contained a Re: or Fwd: */ |
28 | | bool reply_or_forward:1; |
29 | | /* a dummy node */ |
30 | | bool dummy:1; |
31 | | /* ignore this node - it's a dummy without children */ |
32 | | bool ignore:1; |
33 | | }; |
34 | | |
35 | | struct thread_finish_context { |
36 | | unsigned int refcount; |
37 | | |
38 | | struct mail *tmp_mail; |
39 | | struct mail_thread_cache *cache; |
40 | | |
41 | | ARRAY(struct mail_thread_root_node) roots; |
42 | | ARRAY(struct mail_thread_shadow_node) shadow_nodes; |
43 | | unsigned int next_new_root_idx; |
44 | | |
45 | | bool use_sent_date:1; |
46 | | bool return_seqs:1; |
47 | | }; |
48 | | |
49 | | struct mail_thread_iterate_context { |
50 | | struct thread_finish_context *ctx; |
51 | | |
52 | | ARRAY_TYPE(mail_thread_child_node) children; |
53 | | unsigned int next_idx; |
54 | | bool failed; |
55 | | }; |
56 | | |
57 | | struct subject_gather_context { |
58 | | struct thread_finish_context *ctx; |
59 | | |
60 | | pool_t subject_pool; |
61 | | HASH_TABLE(char *, struct mail_thread_root_node *) subject_hash; |
62 | | }; |
63 | | |
64 | | static void |
65 | | add_base_subject(struct subject_gather_context *ctx, const char *subject, |
66 | | struct mail_thread_root_node *node) |
67 | 0 | { |
68 | 0 | struct mail_thread_root_node *hash_node; |
69 | 0 | char *hash_subject; |
70 | 0 | bool is_reply_or_forward; |
71 | |
|
72 | 0 | subject = imap_get_base_subject_cased(pool_datastack_create(), subject, |
73 | 0 | &is_reply_or_forward); |
74 | | /* (ii) If the thread subject is empty, skip this message. */ |
75 | 0 | if (*subject == '\0') |
76 | 0 | return; |
77 | | |
78 | | /* (iii) Look up the message associated with the thread |
79 | | subject in the subject table. */ |
80 | 0 | if (!hash_table_lookup_full(ctx->subject_hash, subject, &hash_subject, |
81 | 0 | &hash_node)) { |
82 | | /* (iv) If there is no message in the subject table with the |
83 | | thread subject, add the current message and the thread |
84 | | subject to the subject table. */ |
85 | 0 | hash_subject = p_strdup(ctx->subject_pool, subject); |
86 | 0 | hash_table_insert(ctx->subject_hash, hash_subject, node); |
87 | 0 | } else { |
88 | | /* Otherwise, if the message in the subject table is not a |
89 | | dummy, AND either of the following criteria are true: |
90 | | |
91 | | The current message is a dummy, OR |
92 | | |
93 | | The message in the subject table is a reply or forward |
94 | | and the current message is not. |
95 | | |
96 | | then replace the message in the subject table with the |
97 | | current message. */ |
98 | 0 | if (!hash_node->dummy && |
99 | 0 | (node->dummy || |
100 | 0 | (hash_node->reply_or_forward && !is_reply_or_forward))) { |
101 | 0 | hash_node->parent_root_idx1 = node->root_idx1; |
102 | 0 | hash_table_update(ctx->subject_hash, hash_subject, node); |
103 | 0 | } else { |
104 | 0 | node->parent_root_idx1 = hash_node->root_idx1; |
105 | 0 | } |
106 | 0 | } |
107 | |
|
108 | 0 | node->reply_or_forward = is_reply_or_forward; |
109 | 0 | } |
110 | | |
111 | | static int mail_thread_child_node_cmp(const struct mail_thread_child_node *c1, |
112 | | const struct mail_thread_child_node *c2) |
113 | 0 | { |
114 | 0 | if (c1->sort_date < c2->sort_date) |
115 | 0 | return -1; |
116 | 0 | if (c1->sort_date > c2->sort_date) |
117 | 0 | return 1; |
118 | | |
119 | 0 | if (c1->uid < c2->uid) |
120 | 0 | return -1; |
121 | 0 | if (c1->uid > c2->uid) |
122 | 0 | return 1; |
123 | 0 | return 0; |
124 | 0 | } |
125 | | |
126 | | static int mail_thread_root_node_cmp(const struct mail_thread_root_node *r1, |
127 | | const struct mail_thread_root_node *r2) |
128 | 0 | { |
129 | 0 | return mail_thread_child_node_cmp(&r1->node, &r2->node); |
130 | 0 | } |
131 | | |
132 | | static uint32_t |
133 | | thread_lookup_existing(struct thread_finish_context *ctx, uint32_t idx) |
134 | 0 | { |
135 | 0 | const struct mail_thread_node *node; |
136 | |
|
137 | 0 | node = array_idx(&ctx->cache->thread_nodes, idx); |
138 | 0 | i_assert(MAIL_THREAD_NODE_EXISTS(node)); |
139 | 0 | i_assert(node->uid != 0); |
140 | 0 | return node->uid; |
141 | 0 | } |
142 | | |
143 | | static void |
144 | | thread_child_node_fill(struct thread_finish_context *ctx, |
145 | | struct mail_thread_child_node *child) |
146 | 0 | { |
147 | 0 | int tz; |
148 | |
|
149 | 0 | child->uid = thread_lookup_existing(ctx, child->idx); |
150 | |
|
151 | 0 | if (!mail_set_uid(ctx->tmp_mail, child->uid)) { |
152 | | /* the UID should have existed. we would have rebuild |
153 | | the thread tree otherwise. */ |
154 | 0 | i_unreached(); |
155 | 0 | } |
156 | | |
157 | | /* get sent date if we want to use it and if it's valid */ |
158 | 0 | if (!ctx->use_sent_date) |
159 | 0 | child->sort_date = 0; |
160 | 0 | else if (mail_get_date(ctx->tmp_mail, &child->sort_date, &tz) < 0) |
161 | 0 | child->sort_date = 0; |
162 | |
|
163 | 0 | if (child->sort_date == 0) { |
164 | | /* fallback to received date */ |
165 | 0 | (void)mail_get_received_date(ctx->tmp_mail, &child->sort_date); |
166 | 0 | } |
167 | 0 | } |
168 | | |
169 | | static void |
170 | | thread_sort_children(struct thread_finish_context *ctx, uint32_t parent_idx, |
171 | | ARRAY_TYPE(mail_thread_child_node) *sorted_children) |
172 | 0 | { |
173 | 0 | const struct mail_thread_shadow_node *shadows; |
174 | 0 | struct mail_thread_child_node child; |
175 | 0 | unsigned int count; |
176 | |
|
177 | 0 | i_zero(&child); |
178 | 0 | array_clear(sorted_children); |
179 | | |
180 | | /* add all child indexes to the array */ |
181 | 0 | shadows = array_get(&ctx->shadow_nodes, &count); |
182 | 0 | child.idx = shadows[parent_idx].first_child_idx; |
183 | 0 | i_assert(child.idx != 0); |
184 | 0 | if (shadows[child.idx].next_sibling_idx == 0) { |
185 | | /* only child - don't bother setting sort date */ |
186 | 0 | child.uid = thread_lookup_existing(ctx, child.idx); |
187 | |
|
188 | 0 | array_push_back(sorted_children, &child); |
189 | 0 | return; |
190 | 0 | } |
191 | 0 | while (child.idx != 0) { |
192 | 0 | thread_child_node_fill(ctx, &child); |
193 | |
|
194 | 0 | array_push_back(sorted_children, &child); |
195 | 0 | child.idx = shadows[child.idx].next_sibling_idx; |
196 | 0 | } |
197 | | |
198 | | /* sort the children */ |
199 | 0 | array_sort(sorted_children, mail_thread_child_node_cmp); |
200 | 0 | } |
201 | | |
202 | | static void gather_base_subjects(struct thread_finish_context *ctx) |
203 | 0 | { |
204 | 0 | struct subject_gather_context gather_ctx; |
205 | 0 | struct mail_thread_root_node *roots; |
206 | 0 | const char *subject; |
207 | 0 | unsigned int i, count; |
208 | 0 | ARRAY_TYPE(mail_thread_child_node) sorted_children; |
209 | 0 | const struct mail_thread_child_node *children; |
210 | 0 | uint32_t idx, uid; |
211 | |
|
212 | 0 | i_zero(&gather_ctx); |
213 | 0 | gather_ctx.ctx = ctx; |
214 | |
|
215 | 0 | roots = array_get_modifiable(&ctx->roots, &count); |
216 | 0 | if (count == 0) |
217 | 0 | return; |
218 | 0 | gather_ctx.subject_pool = |
219 | 0 | pool_alloconly_create(MEMPOOL_GROWING"base subjects", |
220 | 0 | nearest_power(count * 20)); |
221 | 0 | hash_table_create(&gather_ctx.subject_hash, gather_ctx.subject_pool, |
222 | 0 | count * 2, str_hash, strcmp); |
223 | |
|
224 | 0 | i_array_init(&sorted_children, 64); |
225 | 0 | for (i = 0; i < count; i++) { |
226 | 0 | roots[i].root_idx1 = i + 1; |
227 | 0 | if (!roots[i].dummy) |
228 | 0 | idx = roots[i].node.idx; |
229 | 0 | else if (!roots[i].ignore) { |
230 | | /* find the oldest child */ |
231 | 0 | thread_sort_children(ctx, roots[i].node.idx, |
232 | 0 | &sorted_children); |
233 | 0 | children = array_front(&sorted_children); |
234 | 0 | idx = children[0].idx; |
235 | 0 | } else { |
236 | | /* dummy without children */ |
237 | 0 | continue; |
238 | 0 | } |
239 | | |
240 | 0 | uid = thread_lookup_existing(ctx, idx); |
241 | 0 | if (!mail_set_uid(ctx->tmp_mail, uid)) { |
242 | | /* the UID should have existed. we would have rebuild |
243 | | the thread tree otherwise. */ |
244 | 0 | i_unreached(); |
245 | 0 | } |
246 | 0 | if (mail_get_first_header(ctx->tmp_mail, HDR_SUBJECT, |
247 | 0 | &subject) > 0) T_BEGIN { |
248 | 0 | add_base_subject(&gather_ctx, subject, &roots[i]); |
249 | 0 | } T_END; |
250 | 0 | } |
251 | 0 | i_assert(roots[count-1].parent_root_idx1 <= count); |
252 | 0 | array_free(&sorted_children); |
253 | 0 | hash_table_destroy(&gather_ctx.subject_hash); |
254 | 0 | pool_unref(&gather_ctx.subject_pool); |
255 | 0 | } |
256 | | |
257 | | static void thread_add_shadow_child(struct thread_finish_context *ctx, |
258 | | uint32_t parent_idx, uint32_t child_idx) |
259 | 0 | { |
260 | 0 | struct mail_thread_shadow_node *parent_shadow, *child_shadow; |
261 | |
|
262 | 0 | parent_shadow = array_idx_get_space(&ctx->shadow_nodes, parent_idx); |
263 | 0 | child_shadow = array_idx_modifiable(&ctx->shadow_nodes, child_idx); |
264 | |
|
265 | 0 | child_shadow->next_sibling_idx = parent_shadow->first_child_idx; |
266 | 0 | parent_shadow->first_child_idx = child_idx; |
267 | 0 | } |
268 | | |
269 | | static void mail_thread_root_thread_merge(struct thread_finish_context *ctx, |
270 | | struct mail_thread_root_node *cur) |
271 | 0 | { |
272 | 0 | struct mail_thread_root_node *roots, *root, new_root; |
273 | 0 | struct mail_thread_shadow_node *shadows; |
274 | 0 | unsigned int count; |
275 | 0 | uint32_t idx, next_idx; |
276 | |
|
277 | 0 | i_assert(cur->parent_root_idx1 != 0); |
278 | | |
279 | | /* The highest parent is the same as the current message in the |
280 | | subject table. */ |
281 | 0 | roots = array_get_modifiable(&ctx->roots, &count); |
282 | 0 | root = cur; |
283 | 0 | do { |
284 | 0 | i_assert(root->parent_root_idx1 <= count); |
285 | 0 | root = &roots[root->parent_root_idx1 - 1]; |
286 | 0 | } while (root->parent_root_idx1 != 0); |
287 | 0 | i_assert(!root->ignore); |
288 | | |
289 | 0 | shadows = array_front_modifiable(&ctx->shadow_nodes); |
290 | 0 | if (cur->dummy) { |
291 | | /* If both messages are dummies, append the current |
292 | | message's children to the children of the message in |
293 | | the subject table (the children of both messages |
294 | | become siblings), and then delete the current message. */ |
295 | 0 | i_assert(root->dummy); |
296 | | |
297 | 0 | idx = shadows[cur->node.idx].first_child_idx; |
298 | 0 | while (idx != 0) { |
299 | 0 | next_idx = shadows[idx].next_sibling_idx; |
300 | 0 | thread_add_shadow_child(ctx, root->node.idx, idx); |
301 | 0 | idx = next_idx; |
302 | 0 | } |
303 | |
|
304 | 0 | shadows[cur->node.idx].first_child_idx = 0; |
305 | 0 | cur->ignore = TRUE; |
306 | 0 | } else if (root->dummy || (cur->reply_or_forward && |
307 | 0 | !root->reply_or_forward)) { |
308 | | /* a) If the message in the subject table is a dummy and the |
309 | | current message is not, make the current message a |
310 | | child of the message in the subject table (a sibling |
311 | | of its children). |
312 | | |
313 | | b) If the current message is a reply or forward and the |
314 | | message in the subject table is not, make the current |
315 | | message a child of the message in the subject table (a |
316 | | sibling of its children). */ |
317 | 0 | thread_add_shadow_child(ctx, root->node.idx, cur->node.idx); |
318 | 0 | cur->ignore = TRUE; |
319 | 0 | } else { |
320 | | /* Otherwise, create a new dummy message and make both |
321 | | the current message and the message in the subject |
322 | | table children of the dummy. Then replace the message |
323 | | in the subject table with the dummy message. */ |
324 | 0 | i_zero(&new_root); |
325 | 0 | new_root.root_idx1 = array_count(&ctx->roots) + 1; |
326 | 0 | new_root.node.idx = ctx->next_new_root_idx++; |
327 | 0 | new_root.dummy = TRUE; |
328 | |
|
329 | 0 | thread_add_shadow_child(ctx, new_root.node.idx, root->node.idx); |
330 | 0 | thread_add_shadow_child(ctx, new_root.node.idx, cur->node.idx); |
331 | |
|
332 | 0 | root->parent_root_idx1 = new_root.root_idx1; |
333 | 0 | root->ignore = TRUE; |
334 | 0 | cur->ignore = TRUE; |
335 | | |
336 | | /* append last, since it breaks root and cur pointers */ |
337 | 0 | array_push_back(&ctx->roots, &new_root); |
338 | 0 | } |
339 | 0 | } |
340 | | |
341 | | static bool merge_subject_threads(struct thread_finish_context *ctx) |
342 | 0 | { |
343 | 0 | struct mail_thread_root_node *roots; |
344 | 0 | unsigned int i, count; |
345 | 0 | bool changed = FALSE; |
346 | |
|
347 | 0 | roots = array_get_modifiable(&ctx->roots, &count); |
348 | 0 | for (i = 0; i < count; i++) { |
349 | 0 | if (roots[i].parent_root_idx1 != 0 && !roots[i].ignore) { |
350 | 0 | mail_thread_root_thread_merge(ctx, &roots[i]); |
351 | | /* more roots may have been added */ |
352 | 0 | roots = array_front_modifiable(&ctx->roots); |
353 | 0 | changed = TRUE; |
354 | 0 | } |
355 | 0 | } |
356 | |
|
357 | 0 | return changed; |
358 | 0 | } |
359 | | |
360 | | static void sort_root_nodes(struct thread_finish_context *ctx) |
361 | 0 | { |
362 | 0 | ARRAY_TYPE(mail_thread_child_node) sorted_children; |
363 | 0 | const struct mail_thread_child_node *children; |
364 | 0 | const struct mail_thread_shadow_node *shadows; |
365 | 0 | struct mail_thread_root_node *roots; |
366 | 0 | unsigned int i, count, child_count; |
367 | |
|
368 | 0 | i_array_init(&sorted_children, 64); |
369 | 0 | shadows = array_front(&ctx->shadow_nodes); |
370 | 0 | roots = array_get_modifiable(&ctx->roots, &count); |
371 | 0 | for (i = 0; i < count; i++) { |
372 | 0 | if (roots[i].ignore) |
373 | 0 | continue; |
374 | 0 | if (roots[i].dummy) { |
375 | | /* sort by the first child */ |
376 | 0 | if (shadows[roots[i].node.idx].first_child_idx == 0) { |
377 | | /* childless dummy node */ |
378 | 0 | roots[i].ignore = TRUE; |
379 | 0 | continue; |
380 | 0 | } |
381 | 0 | thread_sort_children(ctx, roots[i].node.idx, |
382 | 0 | &sorted_children); |
383 | 0 | children = array_get(&sorted_children, &child_count); |
384 | 0 | if (child_count == 1) { |
385 | | /* only one child - deferred step (3). |
386 | | promote the child to the root. */ |
387 | 0 | roots[i].node = children[0]; |
388 | 0 | thread_child_node_fill(ctx, &roots[i].node); |
389 | 0 | roots[i].dummy = FALSE; |
390 | 0 | } else { |
391 | 0 | roots[i].node.uid = children[0].uid; |
392 | 0 | roots[i].node.sort_date = children[0].sort_date; |
393 | 0 | } |
394 | 0 | } else { |
395 | 0 | thread_child_node_fill(ctx, &roots[i].node); |
396 | 0 | } |
397 | 0 | } |
398 | 0 | array_free(&sorted_children); |
399 | 0 | array_sort(&ctx->roots, mail_thread_root_node_cmp); |
400 | 0 | } |
401 | | |
402 | | static int mail_thread_root_node_idx_cmp(const void *key, const void *value) |
403 | 0 | { |
404 | 0 | const uint32_t *idx = key; |
405 | 0 | const struct mail_thread_root_node *root = value; |
406 | |
|
407 | 0 | return *idx < root->node.idx ? -1 : |
408 | 0 | *idx > root->node.idx ? 1 : 0; |
409 | 0 | } |
410 | | |
411 | | static void sort_root_nodes_ref2(struct thread_finish_context *ctx, |
412 | | uint32_t record_count) |
413 | 0 | { |
414 | 0 | const struct mail_thread_node *node; |
415 | 0 | struct mail_thread_root_node *roots, *root; |
416 | 0 | struct mail_thread_child_node child; |
417 | 0 | const struct mail_thread_shadow_node *shadows; |
418 | 0 | unsigned int root_count; |
419 | 0 | uint32_t idx, parent_idx; |
420 | |
|
421 | 0 | roots = array_get_modifiable(&ctx->roots, &root_count); |
422 | | |
423 | | /* drop childless dummy nodes */ |
424 | 0 | shadows = array_front(&ctx->shadow_nodes); |
425 | 0 | for (idx = 1; idx < root_count; idx++) { |
426 | 0 | if (roots[idx].dummy && |
427 | 0 | shadows[roots[idx].node.idx].first_child_idx == 0) |
428 | 0 | roots[idx].ignore = TRUE; |
429 | 0 | } |
430 | |
|
431 | 0 | for (idx = 1; idx < record_count; idx++) { |
432 | 0 | node = array_idx(&ctx->cache->thread_nodes, idx); |
433 | 0 | if (!MAIL_THREAD_NODE_EXISTS(node)) |
434 | 0 | continue; |
435 | | |
436 | 0 | child.idx = idx; |
437 | 0 | thread_child_node_fill(ctx, &child); |
438 | |
|
439 | 0 | parent_idx = idx; |
440 | 0 | while (node->parent_idx != 0) { |
441 | 0 | parent_idx = node->parent_idx; |
442 | 0 | node = array_idx(&ctx->cache->thread_nodes, |
443 | 0 | node->parent_idx); |
444 | 0 | } |
445 | 0 | root = bsearch(&parent_idx, roots, root_count, sizeof(*roots), |
446 | 0 | mail_thread_root_node_idx_cmp); |
447 | 0 | i_assert(root != NULL); |
448 | | |
449 | 0 | if (root->node.sort_date < child.sort_date) |
450 | 0 | root->node.sort_date = child.sort_date; |
451 | 0 | } |
452 | 0 | array_sort(&ctx->roots, mail_thread_root_node_cmp); |
453 | 0 | } |
454 | | |
455 | | static void mail_thread_create_shadows(struct thread_finish_context *ctx, |
456 | | uint32_t record_count) |
457 | 0 | { |
458 | 0 | const struct mail_thread_node *node, *parent; |
459 | 0 | struct mail_thread_root_node root; |
460 | 0 | struct mail_thread_child_node child; |
461 | 0 | uint32_t idx, parent_idx; |
462 | |
|
463 | 0 | ctx->use_sent_date = FALSE; |
464 | |
|
465 | 0 | i_zero(&root); |
466 | 0 | i_zero(&child); |
467 | | |
468 | | /* We may see dummy messages without parents or children. We can't |
469 | | free them since the nodes are in an array, but they may get reused |
470 | | later so just leave them be. With the current algorithm when this |
471 | | happens all the struct fields are always zero at that point, so |
472 | | we don't even have to try to zero them. */ |
473 | 0 | for (idx = 1; idx < record_count; idx++) { |
474 | 0 | node = array_idx(&ctx->cache->thread_nodes, idx); |
475 | |
|
476 | 0 | if (node->parent_idx == 0) { |
477 | | /* root node - add to roots list */ |
478 | 0 | root.node.idx = idx; |
479 | 0 | if (!MAIL_THREAD_NODE_EXISTS(node)) { |
480 | 0 | root.dummy = TRUE; |
481 | 0 | root.node.uid = 0; |
482 | 0 | } else { |
483 | 0 | root.dummy = FALSE; |
484 | 0 | root.node.uid = node->uid; |
485 | 0 | } |
486 | 0 | array_push_back(&ctx->roots, &root); |
487 | 0 | continue; |
488 | 0 | } |
489 | 0 | i_assert(node->parent_idx < record_count); |
490 | | |
491 | 0 | if (!MAIL_THREAD_NODE_EXISTS(node)) { |
492 | | /* dummy node */ |
493 | 0 | continue; |
494 | 0 | } |
495 | | |
496 | | /* Find the node's first non-dummy parent and add the |
497 | | node as its child. If there are no non-dummy |
498 | | parents, add it as the highest dummy's child. */ |
499 | 0 | parent_idx = node->parent_idx; |
500 | 0 | parent = array_idx(&ctx->cache->thread_nodes, parent_idx); |
501 | 0 | while (!MAIL_THREAD_NODE_EXISTS(parent) && |
502 | 0 | parent->parent_idx != 0) { |
503 | 0 | parent_idx = parent->parent_idx; |
504 | 0 | parent = array_idx(&ctx->cache->thread_nodes, |
505 | 0 | parent_idx); |
506 | 0 | } |
507 | 0 | thread_add_shadow_child(ctx, parent_idx, idx); |
508 | 0 | } |
509 | 0 | } |
510 | | |
511 | | static void mail_thread_finish(struct thread_finish_context *ctx, |
512 | | enum mail_thread_type thread_type) |
513 | 0 | { |
514 | 0 | unsigned int record_count = array_count(&ctx->cache->thread_nodes); |
515 | |
|
516 | 0 | ctx->next_new_root_idx = record_count + 1; |
517 | | |
518 | | /* (2) save root nodes and (3) remove dummy messages */ |
519 | 0 | i_array_init(&ctx->roots, I_MIN(128, record_count)); |
520 | 0 | i_array_init(&ctx->shadow_nodes, record_count); |
521 | | /* make sure all shadow indexes are accessible directly. */ |
522 | 0 | (void)array_idx_get_space(&ctx->shadow_nodes, record_count); |
523 | |
|
524 | 0 | mail_thread_create_shadows(ctx, record_count); |
525 | | |
526 | | /* (4) */ |
527 | 0 | ctx->use_sent_date = TRUE; |
528 | 0 | switch (thread_type) { |
529 | 0 | case MAIL_THREAD_REFERENCES: |
530 | 0 | sort_root_nodes(ctx); |
531 | | /* (5) Gather together messages under the root that have |
532 | | the same base subject text. */ |
533 | 0 | gather_base_subjects(ctx); |
534 | | /* (5.C) Merge threads with the same thread subject. */ |
535 | 0 | if (merge_subject_threads(ctx)) { |
536 | | /* root ordering may have changed, sort them again. */ |
537 | 0 | sort_root_nodes(ctx); |
538 | 0 | } |
539 | 0 | break; |
540 | 0 | case MAIL_THREAD_REFS: |
541 | 0 | sort_root_nodes_ref2(ctx, record_count); |
542 | 0 | break; |
543 | 0 | default: |
544 | 0 | i_unreached(); |
545 | 0 | } |
546 | 0 | } |
547 | | |
548 | | static void |
549 | | nodes_change_uids_to_seqs(struct mail_thread_iterate_context *iter, bool root) |
550 | 0 | { |
551 | 0 | struct mail_thread_child_node *children; |
552 | 0 | struct mailbox *box = iter->ctx->tmp_mail->box; |
553 | 0 | unsigned int i, count; |
554 | 0 | uint32_t uid, seq; |
555 | |
|
556 | 0 | children = array_get_modifiable(&iter->children, &count); |
557 | 0 | for (i = 0; i < count; i++) { |
558 | 0 | uid = children[i].uid; |
559 | 0 | if (uid == 0) { |
560 | | /* dummy root */ |
561 | 0 | if (root) |
562 | 0 | continue; |
563 | 0 | i_unreached(); |
564 | 0 | } else { |
565 | 0 | mailbox_get_seq_range(box, uid, uid, &seq, &seq); |
566 | 0 | i_assert(seq != 0); |
567 | 0 | } |
568 | 0 | children[i].uid = seq; |
569 | 0 | } |
570 | 0 | } |
571 | | |
572 | | static void |
573 | | mail_thread_iterate_fill_root(struct mail_thread_iterate_context *iter) |
574 | 0 | { |
575 | 0 | struct mail_thread_root_node *roots; |
576 | 0 | unsigned int i, count; |
577 | |
|
578 | 0 | roots = array_get_modifiable(&iter->ctx->roots, &count); |
579 | 0 | i_array_init(&iter->children, count); |
580 | 0 | for (i = 0; i < count; i++) { |
581 | 0 | if (!roots[i].ignore) { |
582 | 0 | if (roots[i].dummy) |
583 | 0 | roots[i].node.uid = 0; |
584 | 0 | array_push_back(&iter->children, &roots[i].node); |
585 | 0 | } |
586 | 0 | } |
587 | 0 | } |
588 | | |
589 | | static struct mail_thread_iterate_context * |
590 | | mail_thread_iterate_children(struct mail_thread_iterate_context *parent_iter, |
591 | | uint32_t parent_idx) |
592 | 0 | { |
593 | 0 | struct mail_thread_iterate_context *child_iter; |
594 | |
|
595 | 0 | child_iter = i_new(struct mail_thread_iterate_context, 1); |
596 | 0 | child_iter->ctx = parent_iter->ctx; |
597 | 0 | child_iter->ctx->refcount++; |
598 | |
|
599 | 0 | i_array_init(&child_iter->children, 8); |
600 | 0 | struct event_reason *reason = event_reason_begin("mailbox:thread"); |
601 | 0 | thread_sort_children(child_iter->ctx, parent_idx, |
602 | 0 | &child_iter->children); |
603 | 0 | if (child_iter->ctx->return_seqs) |
604 | 0 | nodes_change_uids_to_seqs(child_iter, FALSE); |
605 | 0 | event_reason_end(&reason); |
606 | 0 | return child_iter; |
607 | 0 | } |
608 | | |
609 | | struct mail_thread_iterate_context * |
610 | | mail_thread_iterate_init_full(struct mail_thread_cache *cache, |
611 | | struct mail *tmp_mail, |
612 | | enum mail_thread_type thread_type, |
613 | | bool return_seqs) |
614 | 0 | { |
615 | 0 | struct mail_thread_iterate_context *iter; |
616 | 0 | struct thread_finish_context *ctx; |
617 | |
|
618 | 0 | iter = i_new(struct mail_thread_iterate_context, 1); |
619 | 0 | ctx = iter->ctx = i_new(struct thread_finish_context, 1); |
620 | 0 | ctx->refcount = 1; |
621 | 0 | ctx->cache = cache; |
622 | 0 | ctx->tmp_mail = tmp_mail; |
623 | 0 | ctx->return_seqs = return_seqs; |
624 | |
|
625 | 0 | struct event_reason *reason = event_reason_begin("mailbox:thread"); |
626 | 0 | mail_thread_finish(ctx, thread_type); |
627 | |
|
628 | 0 | mail_thread_iterate_fill_root(iter); |
629 | 0 | if (return_seqs) |
630 | 0 | nodes_change_uids_to_seqs(iter, TRUE); |
631 | 0 | event_reason_end(&reason); |
632 | 0 | return iter; |
633 | 0 | } |
634 | | |
635 | | const struct mail_thread_child_node * |
636 | | mail_thread_iterate_next(struct mail_thread_iterate_context *iter, |
637 | | struct mail_thread_iterate_context **child_iter_r) |
638 | 0 | { |
639 | 0 | const struct mail_thread_child_node *children, *child; |
640 | 0 | const struct mail_thread_shadow_node *shadow; |
641 | 0 | unsigned int count; |
642 | |
|
643 | 0 | children = array_get(&iter->children, &count); |
644 | 0 | if (iter->next_idx >= count) |
645 | 0 | return NULL; |
646 | | |
647 | 0 | child = &children[iter->next_idx++]; |
648 | 0 | shadow = array_idx(&iter->ctx->shadow_nodes, child->idx); |
649 | 0 | *child_iter_r = shadow->first_child_idx == 0 ? NULL : |
650 | 0 | mail_thread_iterate_children(iter, child->idx); |
651 | 0 | if (child->uid == 0 && *child_iter_r == NULL) { |
652 | | /* this is a dummy node without children, |
653 | | there's no point in returning it */ |
654 | 0 | return mail_thread_iterate_next(iter, child_iter_r); |
655 | 0 | } |
656 | 0 | return child; |
657 | 0 | } |
658 | | |
659 | | unsigned int mail_thread_iterate_count(struct mail_thread_iterate_context *iter) |
660 | 0 | { |
661 | 0 | return array_count(&iter->children); |
662 | 0 | } |
663 | | |
664 | | int mail_thread_iterate_deinit(struct mail_thread_iterate_context **_iter) |
665 | 0 | { |
666 | 0 | struct mail_thread_iterate_context *iter = *_iter; |
667 | |
|
668 | 0 | *_iter = NULL; |
669 | |
|
670 | 0 | if (--iter->ctx->refcount == 0) { |
671 | 0 | array_free(&iter->ctx->roots); |
672 | 0 | array_free(&iter->ctx->shadow_nodes); |
673 | 0 | i_free(iter->ctx); |
674 | 0 | } |
675 | 0 | array_free(&iter->children); |
676 | | i_free(iter); |
677 | 0 | return 0; |
678 | 0 | } |