Coverage Report

Created: 2026-08-31 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}