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