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