1 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
2 /* Balsa E-Mail Client
3  *
4  * Copyright (C) 1997-2013 Stuart Parmenter and others,
5  *                         See the file AUTHORS for a list.
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2, or (at your option)
10  * any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20  * 02111-1307, USA.
21  */
22 
23 #if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
24 # include "config.h"
25 #endif                          /* HAVE_CONFIG_H */
26 
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30 #include <errno.h>
31 #include <string.h>
32 
33 #include "libbalsa.h"
34 #include "libbalsa_private.h"
35 #include "libbalsa-conf.h"
36 #include "filter-funcs.h"
37 #include "mailbox-filter.h"
38 #include "misc.h"
39 #include <glib/gi18n.h>
40 
41 
42 static LibBalsaMailboxClass *parent_class = NULL;
43 
44 static void libbalsa_mailbox_local_class_init(LibBalsaMailboxLocalClass *klass);
45 static void libbalsa_mailbox_local_init(LibBalsaMailboxLocal * mailbox);
46 static void libbalsa_mailbox_local_finalize(GObject * object);
47 
48 static void libbalsa_mailbox_local_changed(LibBalsaMailbox * mailbox);
49 static void libbalsa_mailbox_local_save_config(LibBalsaMailbox * mailbox,
50 					       const gchar * prefix);
51 static void libbalsa_mailbox_local_load_config(LibBalsaMailbox * mailbox,
52 					       const gchar * prefix);
53 
54 static void libbalsa_mailbox_local_close_mailbox(LibBalsaMailbox * mailbox,
55                                                  gboolean expunge);
56 static LibBalsaMessage *libbalsa_mailbox_local_get_message(LibBalsaMailbox
57                                                            * mailbox,
58                                                            guint msgno);
59 static gboolean libbalsa_mailbox_local_message_match(LibBalsaMailbox *
60 						     mailbox, guint msgno,
61 						     LibBalsaMailboxSearchIter
62 						     * iter);
63 
64 static void libbalsa_mailbox_local_set_threading(LibBalsaMailbox *mailbox,
65 						 LibBalsaMailboxThreadingType
66 						 thread_type);
67 static void lbm_local_update_view_filter(LibBalsaMailbox * mailbox,
68                                          LibBalsaCondition *view_filter);
69 
70 static gboolean libbalsa_mailbox_local_prepare_threading(LibBalsaMailbox *
71                                                          mailbox,
72                                                          guint start);
73 
74 static gboolean libbalsa_mailbox_local_fetch_structure(LibBalsaMailbox *
75                                                        mailbox,
76                                                        LibBalsaMessage *
77                                                        message,
78                                                        LibBalsaFetchFlag
79                                                        flags);
80 static void libbalsa_mailbox_local_fetch_headers(LibBalsaMailbox *mailbox,
81                                                  LibBalsaMessage *message);
82 
83 static gboolean libbalsa_mailbox_local_get_msg_part(LibBalsaMessage *msg,
84 						    LibBalsaMessageBody *,
85                                                     GError **err);
86 
87 static void lbm_local_sort(LibBalsaMailbox * mailbox, GArray *sort_array);
88 static GArray *libbalsa_mailbox_local_duplicate_msgnos(LibBalsaMailbox *
89                                                        mailbox);
90 static gboolean
91 libbalsa_mailbox_local_messages_change_flags(LibBalsaMailbox * mailbox,
92                                              GArray * msgnos,
93                                              LibBalsaMessageFlag set,
94                                              LibBalsaMessageFlag clear);
95 static gboolean libbalsa_mailbox_local_msgno_has_flags(LibBalsaMailbox *
96                                                        mailbox,
97                                                        guint msgno,
98                                                        LibBalsaMessageFlag
99                                                        set,
100                                                        LibBalsaMessageFlag
101                                                        unset);
102 
103 /* LibBalsaMailboxLocal class method: */
104 static void lbm_local_real_remove_files(LibBalsaMailboxLocal * local);
105 
106 GType
libbalsa_mailbox_local_get_type(void)107 libbalsa_mailbox_local_get_type(void)
108 {
109     static GType mailbox_type = 0;
110 
111     if (!mailbox_type) {
112 	static const GTypeInfo mailbox_info = {
113 	    sizeof(LibBalsaMailboxLocalClass),
114             NULL,               /* base_init */
115             NULL,               /* base_finalize */
116 	    (GClassInitFunc) libbalsa_mailbox_local_class_init,
117             NULL,               /* class_finalize */
118             NULL,               /* class_data */
119 	    sizeof(LibBalsaMailboxLocal),
120             0,                  /* n_preallocs */
121 	    (GInstanceInitFunc) libbalsa_mailbox_local_init
122 	};
123 
124 	mailbox_type =
125 	    g_type_register_static(LIBBALSA_TYPE_MAILBOX,
126 	                           "LibBalsaMailboxLocal",
127                                    &mailbox_info, 0);
128     }
129 
130     return mailbox_type;
131 }
132 
133 static void
libbalsa_mailbox_local_class_init(LibBalsaMailboxLocalClass * klass)134 libbalsa_mailbox_local_class_init(LibBalsaMailboxLocalClass * klass)
135 {
136     GObjectClass *object_class;
137     LibBalsaMailboxClass *libbalsa_mailbox_class;
138 
139     object_class = G_OBJECT_CLASS(klass);
140     libbalsa_mailbox_class = LIBBALSA_MAILBOX_CLASS(klass);
141 
142     parent_class = g_type_class_peek_parent(klass);
143 
144     object_class->finalize = libbalsa_mailbox_local_finalize;
145 
146     libbalsa_mailbox_class->changed =
147 	libbalsa_mailbox_local_changed;
148     libbalsa_mailbox_class->save_config =
149 	libbalsa_mailbox_local_save_config;
150     libbalsa_mailbox_class->load_config =
151 	libbalsa_mailbox_local_load_config;
152 
153     libbalsa_mailbox_class->close_mailbox =
154 	libbalsa_mailbox_local_close_mailbox;
155     libbalsa_mailbox_class->get_message =
156 	libbalsa_mailbox_local_get_message;
157     libbalsa_mailbox_class->message_match =
158         libbalsa_mailbox_local_message_match;
159     libbalsa_mailbox_class->set_threading =
160 	libbalsa_mailbox_local_set_threading;
161     libbalsa_mailbox_class->update_view_filter =
162         lbm_local_update_view_filter;
163     libbalsa_mailbox_class->prepare_threading =
164         libbalsa_mailbox_local_prepare_threading;
165     libbalsa_mailbox_class->fetch_message_structure =
166         libbalsa_mailbox_local_fetch_structure;
167     libbalsa_mailbox_class->fetch_headers =
168         libbalsa_mailbox_local_fetch_headers;
169     libbalsa_mailbox_class->get_message_part =
170         libbalsa_mailbox_local_get_msg_part;
171     libbalsa_mailbox_class->sort = lbm_local_sort;
172     libbalsa_mailbox_class->messages_change_flags =
173         libbalsa_mailbox_local_messages_change_flags;
174     libbalsa_mailbox_class->msgno_has_flags =
175         libbalsa_mailbox_local_msgno_has_flags;
176     libbalsa_mailbox_class->duplicate_msgnos =
177         libbalsa_mailbox_local_duplicate_msgnos;
178     klass->check_files  = NULL;
179     klass->set_path     = NULL;
180     klass->remove_files = lbm_local_real_remove_files;
181 }
182 
183 static void
libbalsa_mailbox_local_init(LibBalsaMailboxLocal * mailbox)184 libbalsa_mailbox_local_init(LibBalsaMailboxLocal * mailbox)
185 {
186     mailbox->sync_id   = 0;
187     mailbox->sync_time = 0;
188     mailbox->sync_cnt  = 0;
189     mailbox->thread_id = 0;
190     mailbox->save_tree_id = 0;
191 }
192 
193 GObject *
libbalsa_mailbox_local_new(const gchar * path,gboolean create)194 libbalsa_mailbox_local_new(const gchar * path, gboolean create)
195 {
196     GType magic_type = libbalsa_mailbox_type_from_path(path);
197 
198     if(magic_type == LIBBALSA_TYPE_MAILBOX_MBOX)
199 	return libbalsa_mailbox_mbox_new(path, create);
200     else if(magic_type == LIBBALSA_TYPE_MAILBOX_MH)
201 	return libbalsa_mailbox_mh_new(path, create);
202     else if(magic_type == LIBBALSA_TYPE_MAILBOX_MAILDIR)
203 	return libbalsa_mailbox_maildir_new(path, create);
204     else if(magic_type == LIBBALSA_TYPE_MAILBOX_IMAP) {
205         g_warning("IMAP path given as a path to local mailbox.\n");
206         return NULL;
207     } else {		/* mailbox non-existent or unreadable */
208 	if(create)
209 	    return libbalsa_mailbox_mbox_new(path, TRUE);
210         else {
211             g_warning("Unknown mailbox type\n");
212             return NULL;
213         }
214     }
215 }
216 
217 /* libbalsa_mailbox_local_set_path:
218    returrns errno on error, 0 on success
219    FIXME: proper suport for maildir and mh
220 */
221 gint
libbalsa_mailbox_local_set_path(LibBalsaMailboxLocal * mailbox,const gchar * path,gboolean create)222 libbalsa_mailbox_local_set_path(LibBalsaMailboxLocal * mailbox,
223                                 const gchar * path, gboolean create)
224 {
225     int i;
226     LibBalsaMailboxLocalClass *klass =
227         LIBBALSA_MAILBOX_LOCAL_GET_CLASS(mailbox);
228 
229     g_return_val_if_fail(LIBBALSA_IS_MAILBOX_LOCAL(mailbox), -1);
230     g_return_val_if_fail(path != NULL, -1);
231 
232     if (LIBBALSA_MAILBOX(mailbox)->url != NULL) {
233         const gchar *cur_path = libbalsa_mailbox_local_get_path(mailbox);
234         if (strcmp(path, cur_path) == 0)
235             return 0;
236         else if (access(path, F_OK) == 0)       /* 0 == file does exist */
237             return EEXIST;
238         else
239             i = rename(cur_path, path);
240     } else
241         i = klass->check_files(path, create);
242 
243     /* update mailbox data */
244     if (i == 0) {
245         if (klass->set_path)
246             klass->set_path(mailbox, path);
247         g_free(LIBBALSA_MAILBOX(mailbox)->url);
248         LIBBALSA_MAILBOX(mailbox)->url =
249             g_strconcat("file://", path, NULL);
250         return 0;
251     } else
252         return errno ? errno : -1;
253 }
254 
255 void
libbalsa_mailbox_local_remove_files(LibBalsaMailboxLocal * local)256 libbalsa_mailbox_local_remove_files(LibBalsaMailboxLocal * local)
257 {
258     g_return_if_fail(LIBBALSA_IS_MAILBOX_LOCAL(local));
259 
260     LIBBALSA_MAILBOX_LOCAL_GET_CLASS(local)->remove_files(local);
261 }
262 
263 /* libbalsa_mailbox_load_message:
264    MAKE sure the mailbox is LOCKed before entering this routine.
265 */
266 
267 static void
lbml_add_message_to_pool(LibBalsaMailboxLocal * local,LibBalsaMessage * message)268 lbml_add_message_to_pool(LibBalsaMailboxLocal * local,
269                          LibBalsaMessage * message)
270 {
271     LibBalsaMailboxLocalPool *item, *oldest;
272 
273     ++local->pool_seqno;
274 
275     for (item = oldest = &local->message_pool[0];
276          item < &local->message_pool[LBML_POOL_SIZE]; item++) {
277         if (item->message == message) {
278             item->pool_seqno = local->pool_seqno;
279             return;
280         }
281         if (item->pool_seqno < oldest->pool_seqno)
282             oldest = item;
283     }
284 
285     if (oldest->message)
286         g_object_unref(oldest->message);
287     oldest->message = g_object_ref(message);
288     oldest->pool_seqno = local->pool_seqno;
289 }
290 
291 static void
lbm_local_get_message_with_msg_info(LibBalsaMailboxLocal * local,guint msgno,LibBalsaMailboxLocalMessageInfo * msg_info)292 lbm_local_get_message_with_msg_info(LibBalsaMailboxLocal * local,
293                                     guint msgno,
294                                     LibBalsaMailboxLocalMessageInfo *
295                                     msg_info)
296 {
297     LibBalsaMessage *message;
298 
299     msg_info->message = message = libbalsa_message_new();
300     g_object_add_weak_pointer(G_OBJECT(message),
301                               (gpointer) & msg_info->message);
302 
303     message->flags = msg_info->flags & LIBBALSA_MESSAGE_FLAGS_REAL;
304     message->mailbox = LIBBALSA_MAILBOX(local);
305     message->msgno = msgno;
306     libbalsa_message_load_envelope(message);
307     lbml_add_message_to_pool(local, message);
308 }
309 
310 static gboolean message_match_real(LibBalsaMailbox * mailbox, guint msgno,
311                                    LibBalsaCondition * cond);
312 static void
libbalsa_mailbox_local_load_message(LibBalsaMailboxLocal * local,GNode ** sibling,guint msgno,LibBalsaMailboxLocalMessageInfo * msg_info)313 libbalsa_mailbox_local_load_message(LibBalsaMailboxLocal * local,
314                                     GNode ** sibling, guint msgno,
315                                     LibBalsaMailboxLocalMessageInfo *
316                                     msg_info)
317 {
318     LibBalsaMailbox *mbx = LIBBALSA_MAILBOX(local);
319     gboolean match;
320 
321     msg_info->loaded = TRUE;
322 
323     if ((msg_info->flags & LIBBALSA_MESSAGE_FLAG_NEW)
324         && !(msg_info->flags & LIBBALSA_MESSAGE_FLAG_DELETED)) {
325         mbx->unread_messages++;
326         if (mbx->first_unread == 0)
327             mbx->first_unread = msgno;
328     }
329 
330     if (msg_info->flags & LIBBALSA_MESSAGE_FLAG_RECENT) {
331         gchar *id;
332 
333         if (!msg_info->message)
334             lbm_local_get_message_with_msg_info(local, msgno, msg_info);
335 
336         if (libbalsa_message_is_partial(msg_info->message, &id)) {
337             libbalsa_mailbox_try_reassemble(mbx, id);
338             g_free(id);
339         }
340     }
341 
342     if (!mbx->view_filter)
343         match = TRUE;
344     else if (!libbalsa_condition_is_flag_only(mbx->view_filter,
345                                               mbx, msgno, &match))
346         match = message_match_real(mbx, msgno, mbx->view_filter);
347 
348     if (match)
349         libbalsa_mailbox_msgno_inserted(mbx, msgno, mbx->msg_tree,
350                                         sibling);
351 }
352 
353 /* Threading info. */
354 typedef struct {
355     gchar *message_id;
356     GList *refs_for_threading;
357     gchar *sender;
358 } LibBalsaMailboxLocalInfo;
359 
360 static void
lbm_local_free_info(LibBalsaMailboxLocalInfo * info)361 lbm_local_free_info(LibBalsaMailboxLocalInfo * info)
362 {
363     if (info) {
364         g_free(info->message_id);
365         g_list_foreach(info->refs_for_threading, (GFunc) g_free, NULL);
366         g_list_free(info->refs_for_threading);
367         g_free(info->sender);
368         g_free(info);
369     }
370 }
371 
372 static void
libbalsa_mailbox_local_finalize(GObject * object)373 libbalsa_mailbox_local_finalize(GObject * object)
374 {
375     LibBalsaMailboxLocal *ml;
376 
377     g_return_if_fail(LIBBALSA_IS_MAILBOX_LOCAL(object));
378 
379     ml = LIBBALSA_MAILBOX_LOCAL(object);
380     if(ml->sync_id) {
381         g_source_remove(ml->sync_id);
382         ml->sync_id = 0;
383     }
384 
385     if(ml->thread_id) {
386         g_source_remove(ml->thread_id);
387         ml->thread_id = 0;
388     }
389 
390     if(ml->save_tree_id) {
391         g_source_remove(ml->save_tree_id);
392         ml->save_tree_id = 0;
393     }
394 
395     if (ml->threading_info) {
396 	/* The memory owned by ml->threading_info was freed on closing,
397 	 * so we free only the array itself. */
398 	g_ptr_array_free(ml->threading_info, TRUE);
399 	ml->threading_info = NULL;
400     }
401 
402     if (ml->load_messages_id) {
403         g_source_remove(ml->load_messages_id);
404         ml->load_messages_id = 0;
405     }
406 
407     if (G_OBJECT_CLASS(parent_class)->finalize)
408 	G_OBJECT_CLASS(parent_class)->finalize(object);
409 }
410 
411 static void lbm_local_queue_save_tree(LibBalsaMailboxLocal * local);
412 
413 static void
libbalsa_mailbox_local_changed(LibBalsaMailbox * mailbox)414 libbalsa_mailbox_local_changed(LibBalsaMailbox * mailbox)
415 {
416     lbm_local_queue_save_tree(LIBBALSA_MAILBOX_LOCAL(mailbox));
417 }
418 
419 static void
libbalsa_mailbox_local_save_config(LibBalsaMailbox * mailbox,const gchar * prefix)420 libbalsa_mailbox_local_save_config(LibBalsaMailbox * mailbox,
421 				   const gchar * prefix)
422 {
423     LibBalsaMailboxLocal *local;
424 
425     g_return_if_fail(LIBBALSA_IS_MAILBOX_LOCAL(mailbox));
426 
427     local = LIBBALSA_MAILBOX_LOCAL(mailbox);
428 
429     libbalsa_conf_set_string("Path", libbalsa_mailbox_local_get_path(local));
430 
431     if (LIBBALSA_MAILBOX_CLASS(parent_class)->save_config)
432 	LIBBALSA_MAILBOX_CLASS(parent_class)->save_config(mailbox, prefix);
433 }
434 
435 static void
libbalsa_mailbox_local_load_config(LibBalsaMailbox * mailbox,const gchar * prefix)436 libbalsa_mailbox_local_load_config(LibBalsaMailbox * mailbox,
437 				   const gchar * prefix)
438 {
439     gchar* path;
440     g_return_if_fail(LIBBALSA_IS_MAILBOX_LOCAL(mailbox));
441 
442     g_free(mailbox->url);
443 
444     path = libbalsa_conf_get_string("Path");
445     mailbox->url = g_strconcat("file://", path, NULL);
446     g_free(path);
447 
448     if (LIBBALSA_MAILBOX_CLASS(parent_class)->load_config)
449 	LIBBALSA_MAILBOX_CLASS(parent_class)->load_config(mailbox, prefix);
450 }
451 
452 /*
453  * Save and restore the message tree.
454  */
455 
456 typedef struct {
457     GArray * array;
458     guint (*sti_fileno)(LibBalsaMailboxLocal * local, guint msgno);
459     LibBalsaMailboxLocal *local;
460 } LibBalsaMailboxLocalSaveTreeInfo;
461 
462 typedef struct {
463     guint msgno;
464     union {
465         guint parent;
466         guint total;
467     } value;
468 } LibBalsaMailboxLocalTreeInfo;
469 
470 /*
471  * Save one item; return TRUE on error, to terminate the traverse.
472  */
473 static gboolean
lbm_local_save_tree_item(guint msgno,guint a,LibBalsaMailboxLocalSaveTreeInfo * save_info)474 lbm_local_save_tree_item(guint msgno, guint a,
475                          LibBalsaMailboxLocalSaveTreeInfo * save_info)
476 {
477     LibBalsaMailboxLocalTreeInfo info;
478 
479     if (msgno == 0) {
480         info.msgno = msgno;
481         info.value.total = a;
482     } else if (save_info->sti_fileno) {
483         info.msgno = save_info->sti_fileno(save_info->local, msgno);
484         info.value.parent = save_info->sti_fileno(save_info->local, a);
485     } else {
486         info.msgno = msgno;
487         info.value.parent = a;
488     }
489 
490     return g_array_append_val(save_info->array, info) == NULL;
491 }
492 
493 static gboolean
lbm_local_save_tree_func(GNode * node,gpointer data)494 lbm_local_save_tree_func(GNode * node, gpointer data)
495 {
496     return node->parent ?
497         lbm_local_save_tree_item(GPOINTER_TO_UINT(node->data),
498                                  GPOINTER_TO_UINT(node->parent->data),
499                                  data) :
500         FALSE;
501 }
502 
503 static gchar *
lbm_local_get_cache_filename(LibBalsaMailboxLocal * local)504 lbm_local_get_cache_filename(LibBalsaMailboxLocal * local)
505 {
506     gchar *encoded_path;
507     gchar *filename;
508 
509     encoded_path =
510         libbalsa_urlencode(libbalsa_mailbox_local_get_path(local));
511     filename =
512         g_build_filename(g_get_home_dir(), ".balsa", encoded_path, NULL);
513     g_free(encoded_path);
514 
515     return filename;
516 }
517 
518 static void
lbm_local_save_tree(LibBalsaMailboxLocal * local)519 lbm_local_save_tree(LibBalsaMailboxLocal * local)
520 {
521     LibBalsaMailbox *mailbox = LIBBALSA_MAILBOX(local);
522     gchar *filename;
523     LibBalsaMailboxLocalSaveTreeInfo save_info;
524     GError *err = NULL;
525 
526     if (!mailbox->msg_tree || !mailbox->msg_tree_changed)
527         return;
528     mailbox->msg_tree_changed = FALSE;
529 
530     filename = lbm_local_get_cache_filename(local);
531 
532     if (!mailbox->msg_tree->children
533         || (libbalsa_mailbox_get_threading_type(mailbox) ==
534             LB_MAILBOX_THREADING_FLAT
535             && libbalsa_mailbox_get_sort_field(mailbox) ==
536             LB_MAILBOX_SORT_NO)) {
537         unlink(filename);
538         g_free(filename);
539         return;
540     }
541 
542     save_info.sti_fileno = LIBBALSA_MAILBOX_LOCAL_GET_CLASS(local)->sti_fileno;
543     save_info.local = local;
544     save_info.array =
545         g_array_new(FALSE, FALSE, sizeof(LibBalsaMailboxLocalTreeInfo));
546     lbm_local_save_tree_item(0, libbalsa_mailbox_get_total(mailbox),
547                              &save_info);
548 
549     /* Pre-order is required for the file to be created correctly. */
550     g_node_traverse(mailbox->msg_tree, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
551                     (GNodeTraverseFunc) lbm_local_save_tree_func,
552                     &save_info);
553 
554     if (!g_file_set_contents(filename, save_info.array->data,
555                              save_info.array->len *
556                              sizeof(LibBalsaMailboxLocalTreeInfo), &err)) {
557         libbalsa_information(LIBBALSA_INFORMATION_WARNING,
558                              _("Failed to save cache file \"%s\": %s."),
559                              filename, err->message);
560         g_error_free(err);
561     }
562     g_array_free(save_info.array, TRUE);
563     g_free(filename);
564 }
565 
566 static gboolean
lbm_local_restore_tree(LibBalsaMailboxLocal * local,guint * total)567 lbm_local_restore_tree(LibBalsaMailboxLocal * local, guint * total)
568 {
569     LibBalsaMailbox *mailbox = LIBBALSA_MAILBOX(local);
570     gchar *filename;
571     gchar *name;
572     struct stat st;
573     gchar *contents;
574     gsize length;
575     GError *err = NULL;
576     GNode *parent, *sibling;
577     LibBalsaMailboxLocalTreeInfo *info;
578     guint8 *seen;
579     LibBalsaMailboxLocalMessageInfo *(*get_info) (LibBalsaMailboxLocal *,
580                                                   guint);
581 
582     filename = lbm_local_get_cache_filename(local);
583     name = mailbox->name ? g_strdup(mailbox->name) :
584         g_path_get_basename(libbalsa_mailbox_local_get_path(local));
585 
586     if (stat(filename, &st) < 0
587         || st.st_mtime < libbalsa_mailbox_get_mtime(mailbox)) {
588         /* No error, but we return FALSE so the caller can grab all the
589          * message info needed to rethread from scratch. */
590         if (libbalsa_mailbox_total_messages(mailbox) > 0)
591             libbalsa_information(LIBBALSA_INFORMATION_DEBUG,
592                                  _("Cache file for mailbox %s "
593                                    "will be created"), name);
594         g_free(filename);
595         g_free(name);
596         return FALSE;
597     }
598 
599     if (!g_file_get_contents(filename, &contents, &length, &err)) {
600         libbalsa_information(LIBBALSA_INFORMATION_WARNING,
601                              _("Failed to read cache file %s: %s"),
602                              filename, err->message);
603         g_error_free(err);
604         g_free(filename);
605         g_free(name);
606         return FALSE;
607     }
608     g_free(filename);
609 
610     info = (LibBalsaMailboxLocalTreeInfo *) contents;
611     /* Sanity checks: first the file should have >= 1 record. */
612     if (length < sizeof(LibBalsaMailboxLocalTreeInfo)
613         /* First record is (0, total): */
614         || info->msgno != 0
615         /* Total must be > 0 (no file is created for empty tree). */
616         || info->value.total == 0
617         || info->value.total > libbalsa_mailbox_total_messages(mailbox)) {
618         libbalsa_information(LIBBALSA_INFORMATION_DEBUG,
619                              _("Cache file for mailbox %s "
620                                "will be repaired"), name);
621         g_free(contents);
622         g_free(name);
623         return FALSE;
624     }
625     *total = info->value.total;
626 
627     gdk_threads_enter();
628 
629     seen = g_new0(guint8, *total);
630     parent = mailbox->msg_tree;
631     sibling = NULL;
632     get_info = LIBBALSA_MAILBOX_LOCAL_GET_CLASS(local)->get_info;
633     while (++info < (LibBalsaMailboxLocalTreeInfo *) (contents + length)) {
634         LibBalsaMailboxLocalMessageInfo *msg_info;
635         if (info->msgno == 0 || info->msgno > *total
636             || seen[info->msgno - 1]) {
637             libbalsa_information(LIBBALSA_INFORMATION_DEBUG,
638                                  _("Cache file for mailbox %s "
639                                    "will be repaired"), name);
640             g_free(seen);
641             g_free(contents);
642             g_free(name);
643             gdk_threads_leave();
644             return FALSE;
645         }
646         seen[info->msgno - 1] = TRUE;
647 
648         if (sibling
649             && info->value.parent == GPOINTER_TO_UINT(sibling->data)) {
650             /* This message is the first child of the previous one. */
651             parent = sibling;
652             sibling = NULL;
653         } else {
654             /* Find the parent of this message. */
655             while (info->value.parent != GPOINTER_TO_UINT(parent->data)) {
656                 /* Check one level higher. */
657                 sibling = parent;
658                 parent = parent->parent;
659                 if (!parent) {
660                     /* We got to the root without finding the parent. */
661                     libbalsa_information(LIBBALSA_INFORMATION_DEBUG,
662                                          _("Cache file for mailbox %s "
663                                            "will be repaired"), name);
664                     g_free(seen);
665                     g_free(contents);
666                     g_free(name);
667     		    gdk_threads_leave();
668                     return FALSE;
669                 }
670             }
671         }
672         libbalsa_mailbox_msgno_inserted(mailbox, info->msgno,
673                                         parent, &sibling);
674 
675         msg_info = get_info(local, info->msgno);
676         msg_info->loaded = TRUE;
677 
678         if (libbalsa_mailbox_msgno_has_flags(mailbox, info->msgno,
679                                              LIBBALSA_MESSAGE_FLAG_NEW,
680                                              LIBBALSA_MESSAGE_FLAG_DELETED))
681         {
682             ++mailbox->unread_messages;
683             if (mailbox->first_unread == 0 ||
684                 mailbox->first_unread > info->msgno)
685                 mailbox->first_unread = info->msgno;
686         }
687     }
688 
689     g_free(seen);
690     g_free(contents);
691     g_free(name);
692 
693     gdk_threads_leave();
694 
695     return TRUE;
696 }
697 
698 static void
lbm_local_save_tree_real(LibBalsaMailboxLocal * local)699 lbm_local_save_tree_real(LibBalsaMailboxLocal * local)
700 {
701     LibBalsaMailbox *mailbox = LIBBALSA_MAILBOX(local);
702 
703     libbalsa_lock_mailbox(mailbox);
704 
705     if (MAILBOX_OPEN(mailbox) && mailbox->msg_tree_changed)
706         lbm_local_save_tree(local);
707     local->save_tree_id = 0;
708 
709     libbalsa_unlock_mailbox(mailbox);
710 }
711 
712 static gboolean
lbm_local_save_tree_idle(LibBalsaMailboxLocal * local)713 lbm_local_save_tree_idle(LibBalsaMailboxLocal * local)
714 {
715 #if 0 && defined(BALSA_USE_THREADS)
716     pthread_t save_tree_thread;
717 
718     pthread_create(&save_tree_thread, NULL,
719                    (void *) lbm_local_save_tree_real, local);
720     pthread_detach(save_tree_thread);
721 #else                           /* BALSA_USE_THREADS */
722     lbm_local_save_tree_real(local);
723 #endif                          /* BALSA_USE_THREADS */
724 
725     return FALSE;
726 }
727 
728 static void
lbm_local_queue_save_tree(LibBalsaMailboxLocal * local)729 lbm_local_queue_save_tree(LibBalsaMailboxLocal * local)
730 {
731     libbalsa_lock_mailbox((LibBalsaMailbox *) local);
732     if (!local->save_tree_id)
733         local->save_tree_id =
734             g_idle_add((GSourceFunc) lbm_local_save_tree_idle, local);
735     libbalsa_unlock_mailbox((LibBalsaMailbox *) local);
736 }
737 
738 /*
739  * End of save and restore the message tree.
740  */
741 
742 static void
libbalsa_mailbox_local_close_mailbox(LibBalsaMailbox * mailbox,gboolean expunge)743 libbalsa_mailbox_local_close_mailbox(LibBalsaMailbox * mailbox,
744                                      gboolean expunge)
745 {
746     LibBalsaMailboxLocal *local = LIBBALSA_MAILBOX_LOCAL(mailbox);
747     guint i;
748     LibBalsaMailboxLocalPool *item;
749 
750     if(local->sync_id) {
751         g_source_remove(local->sync_id);
752         local->sync_id = 0;
753     }
754 
755     /* Restore the persistent view before saving the tree. */
756     libbalsa_mailbox_set_view_filter(mailbox,
757                                      mailbox->persistent_view_filter, TRUE);
758 
759     if (local->thread_id) {
760         /* Rethread immediately. */
761         LibBalsaMailboxThreadingType cur_type =
762             libbalsa_mailbox_get_threading_type(mailbox);
763         g_source_remove(local->thread_id);
764         local->thread_id = 0;
765         libbalsa_mailbox_set_threading(mailbox, cur_type);
766     }
767 
768     if (local->save_tree_id) {
769         /* Save immediately. */
770         g_source_remove(local->save_tree_id);
771         local->save_tree_id = 0;
772     }
773     lbm_local_save_tree(local);
774 
775     if (local->threading_info) {
776 	/* Free the memory owned by local->threading_info, but neither
777 	 * free nor truncate the array. */
778         for (i = local->threading_info->len; i > 0;) {
779             gpointer *entry =
780                 &g_ptr_array_index(local->threading_info, --i);
781             lbm_local_free_info(*entry);
782             *entry = NULL;
783         }
784     }
785 
786     for (item = &local->message_pool[0];
787          item < &local->message_pool[LBML_POOL_SIZE]; item++) {
788         if (item->message) {
789             g_object_unref(item->message);
790             item->message = NULL;
791         }
792         item->pool_seqno = 0;
793     }
794     local->pool_seqno = 0;
795 
796     if (LIBBALSA_MAILBOX_CLASS(parent_class)->close_mailbox)
797         LIBBALSA_MAILBOX_CLASS(parent_class)->close_mailbox(mailbox,
798                                                             expunge);
799 }
800 
801 /* LibBalsaMailbox get_message class method */
802 
803 static LibBalsaMessage *
libbalsa_mailbox_local_get_message(LibBalsaMailbox * mailbox,guint msgno)804 libbalsa_mailbox_local_get_message(LibBalsaMailbox * mailbox, guint msgno)
805 {
806     LibBalsaMailboxLocal *local = LIBBALSA_MAILBOX_LOCAL(mailbox);
807     LibBalsaMailboxLocalMessageInfo *msg_info =
808         LIBBALSA_MAILBOX_LOCAL_GET_CLASS(local)->get_info(local, msgno);
809 
810     if (msg_info->message)
811         return g_object_ref(msg_info->message);
812 
813     lbm_local_get_message_with_msg_info(local, msgno, msg_info);
814 
815     return msg_info->message;
816 }
817 
818 /* Search iters. We do not use the fallback version because it does
819    lot of reparsing. Instead, we use LibBalsaMailboxIndex entry
820    whenever possible - it is so quite frequently. This is a big
821    improvement for mailboxes of several megabytes and few thousand
822    messages.
823 */
824 
825 static gboolean
message_match_real(LibBalsaMailbox * mailbox,guint msgno,LibBalsaCondition * cond)826 message_match_real(LibBalsaMailbox *mailbox, guint msgno,
827                    LibBalsaCondition *cond)
828 {
829     LibBalsaMailboxLocal *local = (LibBalsaMailboxLocal *) mailbox;
830     LibBalsaMessage *message = NULL;
831     gboolean match = FALSE;
832     gboolean is_refed = FALSE;
833     LibBalsaMailboxIndexEntry *entry =
834         g_ptr_array_index(mailbox->mindex, msgno-1);
835     LibBalsaMailboxLocalInfo *info =
836         msgno <= local->threading_info->len ?
837         g_ptr_array_index(local->threading_info, msgno - 1) : NULL;
838 
839     /* We may be able to match the msgno from info cached in entry or
840      * info; if those are NULL, we'll need to fetch the message, so we
841      * fetch it here, and that will also populate entry and info. */
842     if (!entry || !info) {
843         message = libbalsa_mailbox_get_message(mailbox, msgno);
844         if (!message)
845             return FALSE;
846         libbalsa_mailbox_local_cache_message(local, msgno, message);
847         entry = g_ptr_array_index(mailbox->mindex, msgno-1);
848         info  = g_ptr_array_index(local->threading_info, msgno - 1);
849     }
850 
851 #if defined(BALSA_USE_THREADS)
852     if (entry->idle_pending)
853         return FALSE;   /* Can't match. */
854 #endif                  /* defined(BALSA_USE_THREADS) */
855 
856     switch (cond->type) {
857     case CONDITION_STRING:
858         if (CONDITION_CHKMATCH(cond, (CONDITION_MATCH_TO |
859                                       CONDITION_MATCH_CC |
860                                       CONDITION_MATCH_BODY))) {
861             if (!message)
862                 message = libbalsa_mailbox_get_message(mailbox, msgno);
863             if (!message)
864                 return FALSE;
865             is_refed = libbalsa_message_body_ref(message, FALSE, FALSE);
866             if (!is_refed) {
867                 libbalsa_information(LIBBALSA_INFORMATION_ERROR,
868                                      _("Unable to load message body to "
869                                        "match filter"));
870                 g_object_unref(message);
871                 return FALSE;   /* We don't want to match if an error occurred */
872             }
873         }
874 
875         /* do the work */
876 	if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_TO)) {
877             g_assert(is_refed);
878             if (message->headers->to_list) {
879                 gchar *str =
880                     internet_address_list_to_string(message->headers->
881                                                     to_list, FALSE);
882                 match =
883                     libbalsa_utf8_strstr(str, cond->match.string.string);
884                 g_free(str);
885                 if (match)
886                     break;
887             }
888 	}
889         if (CONDITION_CHKMATCH(cond, CONDITION_MATCH_FROM)) {
890 	    if (libbalsa_utf8_strstr(info->sender,
891                                      cond->match.string.string)) {
892                 match = TRUE;
893                 break;
894             }
895         }
896 	if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_SUBJECT)) {
897 	    if (libbalsa_utf8_strstr(entry->subject,
898                                      cond->match.string.string)) {
899                 match = TRUE;
900                 break;
901             }
902 	}
903 	if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_CC)) {
904             g_assert(is_refed);
905             if (message->headers->cc_list) {
906                 gchar *str =
907                     internet_address_list_to_string(message->headers->
908                                                     cc_list, FALSE);
909                 match =
910                     libbalsa_utf8_strstr(str, cond->match.string.string);
911                 g_free(str);
912                 if (match)
913                     break;
914             }
915 	}
916 	if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_US_HEAD)) {
917             if (cond->match.string.user_header) {
918                 const gchar *header;
919 
920                 if (!message)
921                     message = libbalsa_mailbox_get_message(mailbox, msgno);
922                 if (!message)
923                     return FALSE;
924                 header =
925                     libbalsa_message_get_user_header(message,
926                                                      cond->match.string.
927                                                      user_header);
928                 if (libbalsa_utf8_strstr(header,
929                                          cond->match.string.string)) {
930                     match = TRUE;
931                     break;
932                 }
933             }
934 	}
935 	if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_BODY)) {
936             GString *body;
937             g_assert(is_refed);
938 	    if (!message->mailbox) {
939                 /* No need to body-unref */
940                 g_object_unref(message);
941 		return FALSE; /* We don't want to match if an error occurred */
942             }
943             body = content2reply(message->body_list, NULL, 0, FALSE, FALSE);
944 	    if (body) {
945 		if (body->str)
946                     match = libbalsa_utf8_strstr(body->str,
947                                                  cond->match.string.string);
948 		g_string_free(body,TRUE);
949 	    }
950 	}
951 	break;
952     case CONDITION_REGEX:
953         break;
954     case CONDITION_DATE:
955         match =
956             entry->msg_date >= cond->match.date.date_low &&
957             (cond->match.date.date_high==0 ||
958              entry->msg_date<=cond->match.date.date_high);
959         break;
960     case CONDITION_FLAG:
961         match = libbalsa_mailbox_msgno_has_flags(mailbox, msgno,
962                                                  cond->match.flags, 0);
963         break;
964     case CONDITION_AND:
965         match =
966             message_match_real(mailbox, msgno, cond->match.andor.left) &&
967             message_match_real(mailbox, msgno, cond->match.andor.right);
968         break;
969     case CONDITION_OR:
970         match =
971             message_match_real(mailbox, msgno, cond->match.andor.left) ||
972             message_match_real(mailbox, msgno, cond->match.andor.right);
973         break;
974     /* To avoid warnings */
975     case CONDITION_NONE:
976         break;
977     }
978     if(message) {
979         if(is_refed) libbalsa_message_body_unref(message);
980         g_object_unref(message);
981     }
982     return cond->negate ? !match : match;
983 }
984 static gboolean
libbalsa_mailbox_local_message_match(LibBalsaMailbox * mailbox,guint msgno,LibBalsaMailboxSearchIter * iter)985 libbalsa_mailbox_local_message_match(LibBalsaMailbox * mailbox,
986 				     guint msgno,
987 				     LibBalsaMailboxSearchIter * iter)
988 {
989     return message_match_real(mailbox, msgno, iter->condition);
990 }
991 
992 /*
993  * private
994  * PS: called by mail_progress_notify_cb:
995  * loads incrementally new messages, if any.
996  *  Mailbox lock MUST BE HELD before calling this function.
997  *
998  *  Caches the message-id, references, and sender,
999  *  and passes the message to libbalsa_mailbox_cache_message for caching
1000  *  other info.
1001  */
1002 void
libbalsa_mailbox_local_cache_message(LibBalsaMailboxLocal * local,guint msgno,LibBalsaMessage * message)1003 libbalsa_mailbox_local_cache_message(LibBalsaMailboxLocal * local,
1004                                      guint msgno,
1005                                      LibBalsaMessage * message)
1006 {
1007     gpointer *entry;
1008     LibBalsaMailboxLocalInfo *info;
1009 
1010     if (!message)
1011         return;
1012 
1013     libbalsa_mailbox_cache_message(LIBBALSA_MAILBOX(local), msgno,
1014                                    message);
1015 
1016     if (!local->threading_info)
1017         return;
1018 
1019     while (local->threading_info->len < msgno)
1020         g_ptr_array_add(local->threading_info, NULL);
1021     entry = &g_ptr_array_index(local->threading_info, msgno - 1);
1022 
1023     if (*entry)
1024         return;
1025 
1026     *entry = info = g_new(LibBalsaMailboxLocalInfo, 1);
1027     info->message_id = g_strdup(message->message_id);
1028     info->refs_for_threading =
1029         libbalsa_message_refs_for_threading(message);
1030 
1031     info->sender = NULL;
1032     if (message->headers->from)
1033         info->sender =
1034             internet_address_list_to_string(message->headers->from, FALSE);
1035     if (!info->sender)
1036         info->sender = g_strdup("");
1037 }
1038 
1039 static gboolean
lbml_load_messages_idle_cb(LibBalsaMailbox * mailbox)1040 lbml_load_messages_idle_cb(LibBalsaMailbox * mailbox)
1041 {
1042     guint msgno;
1043     guint new_messages;
1044     LibBalsaMailboxLocal *local;
1045     guint lastno;
1046     GNode *lastn;
1047     LibBalsaMailboxLocalMessageInfo *(*get_info) (LibBalsaMailboxLocal *,
1048                                                   guint);
1049 
1050     libbalsa_lock_mailbox(mailbox);
1051     gdk_threads_enter();
1052 
1053     if (!mailbox->msg_tree) {
1054 	/* Mailbox is closed, or no view has been created. */
1055         gdk_threads_leave();
1056         libbalsa_unlock_mailbox(mailbox);
1057 	return FALSE;
1058     }
1059 
1060     local = (LibBalsaMailboxLocal *) mailbox;
1061     lastno = libbalsa_mailbox_total_messages(mailbox);
1062     msgno = local->msgno;
1063     new_messages = lastno - msgno;
1064     lastn = g_node_last_child(mailbox->msg_tree);
1065     get_info = LIBBALSA_MAILBOX_LOCAL_GET_CLASS(local)->get_info;
1066     while (++msgno <= lastno){
1067         LibBalsaMailboxLocalMessageInfo *msg_info = get_info(local, msgno);
1068 
1069 	libbalsa_mailbox_local_load_message(local, &lastn, msgno, msg_info);
1070 
1071 	if (msg_info->message)
1072             libbalsa_mailbox_local_cache_message(local, msgno,
1073                                                  msg_info->message);
1074     }
1075 
1076     gdk_threads_leave();
1077 
1078     if (new_messages) {
1079 	libbalsa_mailbox_run_filters_on_reception(mailbox);
1080 	libbalsa_mailbox_set_unread_messages_flag(mailbox,
1081 						  mailbox->
1082 						  unread_messages > 0);
1083     }
1084 
1085     local->load_messages_id = 0;
1086     libbalsa_unlock_mailbox(mailbox);
1087     return FALSE;
1088 }
1089 
1090 void
libbalsa_mailbox_local_load_messages(LibBalsaMailbox * mailbox,guint msgno)1091 libbalsa_mailbox_local_load_messages(LibBalsaMailbox *mailbox,
1092                                      guint msgno)
1093 {
1094     LibBalsaMailboxLocal *local;
1095 
1096     g_return_if_fail(LIBBALSA_IS_MAILBOX_LOCAL(mailbox));
1097 
1098     local = (LibBalsaMailboxLocal *) mailbox;
1099     libbalsa_lock_mailbox(mailbox);
1100     if (!local->load_messages_id) {
1101         local->msgno = msgno;
1102         local->load_messages_id =
1103             g_idle_add((GSourceFunc) lbml_load_messages_idle_cb, mailbox);
1104     }
1105     libbalsa_unlock_mailbox(mailbox);
1106 }
1107 
1108 /*
1109  * Threading
1110  */
1111 
1112 static void lbml_threading_jwz(LibBalsaMailbox * mailbox);
1113 static void lbml_threading_simple(LibBalsaMailbox * mailbox,
1114 				  LibBalsaMailboxThreadingType th_type);
1115 
1116 void
libbalsa_mailbox_local_set_threading_info(LibBalsaMailboxLocal * local)1117 libbalsa_mailbox_local_set_threading_info(LibBalsaMailboxLocal * local)
1118 {
1119     if (!local->threading_info)
1120         local->threading_info = g_ptr_array_new();
1121 }
1122 
1123 static void
lbml_set_threading(LibBalsaMailbox * mailbox,LibBalsaMailboxThreadingType thread_type)1124 lbml_set_threading(LibBalsaMailbox * mailbox,
1125                    LibBalsaMailboxThreadingType thread_type)
1126 {
1127     switch (thread_type) {
1128     case LB_MAILBOX_THREADING_JWZ:
1129         lbml_threading_jwz(mailbox);
1130         break;
1131     case LB_MAILBOX_THREADING_FLAT:
1132     case LB_MAILBOX_THREADING_SIMPLE:
1133         lbml_threading_simple(mailbox, thread_type);
1134         break;
1135     }
1136 }
1137 
1138 #ifdef BALSA_USE_THREADS
1139 typedef struct {
1140     LibBalsaMailbox *mailbox;
1141     LibBalsaMailboxThreadingType thread_type;
1142 } LbmlSetThreadingInfo;
1143 
1144 static gboolean
lbml_set_threading_idle_cb(LbmlSetThreadingInfo * info)1145 lbml_set_threading_idle_cb(LbmlSetThreadingInfo * info)
1146 {
1147     lbml_set_threading(info->mailbox, info->thread_type);
1148     g_object_unref(info->mailbox);
1149     g_slice_free(LbmlSetThreadingInfo, info);
1150     return FALSE;
1151 }
1152 #endif                          /* BALSA_USE_THREADS */
1153 
1154 static void
libbalsa_mailbox_local_set_threading(LibBalsaMailbox * mailbox,LibBalsaMailboxThreadingType thread_type)1155 libbalsa_mailbox_local_set_threading(LibBalsaMailbox * mailbox,
1156                                      LibBalsaMailboxThreadingType
1157                                      thread_type)
1158 {
1159     LibBalsaMailboxLocal *local = LIBBALSA_MAILBOX_LOCAL(mailbox);
1160 
1161     libbalsa_mailbox_local_set_threading_info(local);
1162 #if defined(DEBUG_LOADING_AND_THREADING)
1163     printf("before load_messages: time=%lu\n", (unsigned long) time(NULL));
1164 #endif
1165     if (!mailbox->msg_tree) {   /* first reference */
1166         guint total = 0;
1167         gboolean natural = (thread_type == LB_MAILBOX_THREADING_FLAT
1168                             && libbalsa_mailbox_get_sort_field(mailbox) ==
1169                             LB_MAILBOX_SORT_NO);
1170 
1171         libbalsa_mailbox_set_msg_tree(mailbox, g_node_new(NULL));
1172         if (!lbm_local_restore_tree(local, &total)) {
1173             /* Bad or no cache file: start over. */
1174             libbalsa_mailbox_set_msg_tree(mailbox, g_node_new(NULL));
1175             total = 0;
1176         }
1177         mailbox->msg_tree_changed = FALSE;
1178 
1179         if (total < libbalsa_mailbox_total_messages(mailbox)) {
1180             if (!natural)
1181                 /* Get message info for all messages that weren't restored,
1182                  * so we can thread and sort them correctly before the
1183                  * mailbox is displayed. */
1184                 libbalsa_mailbox_prepare_threading(mailbox, total);
1185             libbalsa_mailbox_local_load_messages(mailbox, total);
1186         }
1187 
1188 #if defined(DEBUG_LOADING_AND_THREADING)
1189         printf("after load messages: time=%lu\n", (unsigned long) time(NULL));
1190 #endif
1191         if (natural)
1192             /* No need to thread. */
1193             return;
1194     }
1195 
1196 #ifdef BALSA_USE_THREADS
1197     if (libbalsa_am_i_subthread()) {
1198         LbmlSetThreadingInfo *info;
1199 
1200         info = g_slice_new(LbmlSetThreadingInfo);
1201         info->mailbox = g_object_ref(mailbox);
1202         info->thread_type = thread_type;
1203         gdk_threads_add_idle((GSourceFunc) lbml_set_threading_idle_cb, info);
1204     } else {
1205         gdk_threads_enter();
1206         lbml_set_threading(mailbox, thread_type);
1207         gdk_threads_leave();
1208     }
1209 #else                           /* BALSA_USE_THREADS */
1210     lbml_set_threading(mailbox, thread_type);
1211 #endif                          /* BALSA_USE_THREADS */
1212 #if defined(DEBUG_LOADING_AND_THREADING)
1213     printf("after threading time=%lu\n", (unsigned long) time(NULL));
1214 #endif
1215 
1216     lbm_local_queue_save_tree(local);
1217 }
1218 
1219 void
libbalsa_mailbox_local_msgno_removed(LibBalsaMailbox * mailbox,guint msgno)1220 libbalsa_mailbox_local_msgno_removed(LibBalsaMailbox * mailbox,
1221 				     guint msgno)
1222 {
1223     LibBalsaMailboxLocal *local = LIBBALSA_MAILBOX_LOCAL(mailbox);
1224 
1225     /* local might not have a threading-info array, and even if it does,
1226      * it might not be populated; we check both. */
1227     if (local->threading_info && msgno <= local->threading_info->len) {
1228 	lbm_local_free_info(g_ptr_array_index(local->threading_info,
1229 			                      msgno - 1));
1230 	g_ptr_array_remove_index(local->threading_info, msgno - 1);
1231     }
1232 
1233     libbalsa_mailbox_msgno_removed(mailbox, msgno);
1234 }
1235 
1236 static void
lbm_local_update_view_filter(LibBalsaMailbox * mailbox,LibBalsaCondition * view_filter)1237 lbm_local_update_view_filter(LibBalsaMailbox * mailbox,
1238                              LibBalsaCondition * view_filter)
1239 {
1240     guint total;
1241     LibBalsaProgress progress = LIBBALSA_PROGRESS_INIT;
1242     LibBalsaMailboxSearchIter *iter_view;
1243     guint msgno;
1244     gboolean is_flag_only = TRUE;
1245 
1246     total = libbalsa_mailbox_total_messages(mailbox);
1247     if (view_filter
1248         && !libbalsa_condition_is_flag_only(view_filter, NULL, 0, NULL)) {
1249         gchar *text;
1250 
1251         text = g_strdup_printf(_("Filtering %s"), mailbox->name);
1252         libbalsa_progress_set_text(&progress, text, total);
1253         g_free(text);
1254         is_flag_only = FALSE;
1255     }
1256 
1257     iter_view = libbalsa_mailbox_search_iter_new(view_filter);
1258     for (msgno = 1; msgno <= total; msgno++) {
1259         libbalsa_mailbox_msgno_filt_check(mailbox, msgno, iter_view,
1260                                           FALSE);
1261         libbalsa_progress_set_fraction(&progress, ((gdouble) msgno) /
1262                                        ((gdouble) total));
1263     }
1264     libbalsa_progress_set_text(&progress, NULL, 0);
1265     libbalsa_mailbox_search_iter_unref(iter_view);
1266 
1267     /* If this is not a flags-only filter, the new mailbox tree is
1268      * temporary, so we don't want to save it. */
1269     if (is_flag_only)
1270         lbm_local_queue_save_tree(LIBBALSA_MAILBOX_LOCAL(mailbox));
1271 }
1272 
1273 /*
1274  * Prepare-threading method: brute force--create and destroy the
1275  * LibBalsaMessage; the back end is responsible for caching it here and
1276  * at LibBalsaMailbox.
1277  */
1278 
1279 /* Helper: returns TRUE if msgno was not already cached, which means we
1280  * have new data for sorting or threading. */
1281 static gboolean
lbm_local_prepare_msgno(LibBalsaMailboxLocal * local,guint msgno)1282 lbm_local_prepare_msgno(LibBalsaMailboxLocal * local, guint msgno)
1283 {
1284     LibBalsaMessage *message;
1285 
1286     if (msgno <= local->threading_info->len
1287         && g_ptr_array_index(local->threading_info, msgno - 1))
1288         return FALSE;
1289 
1290     message =
1291         libbalsa_mailbox_get_message((LibBalsaMailbox *) local, msgno);
1292     if (!message)
1293         return FALSE;
1294 
1295     libbalsa_mailbox_local_cache_message(local, msgno, message);
1296     g_object_unref(message);
1297 
1298     return TRUE;
1299 }
1300 
1301 /* Idle handler. */
1302 static gboolean
lbm_local_thread_idle(LibBalsaMailboxLocal * local)1303 lbm_local_thread_idle(LibBalsaMailboxLocal * local)
1304 {
1305     LibBalsaMailbox *mailbox = LIBBALSA_MAILBOX(local);
1306 
1307     libbalsa_lock_mailbox(mailbox);
1308 
1309     if (MAILBOX_OPEN(mailbox)) {
1310         LibBalsaMailboxThreadingType cur_type =
1311             libbalsa_mailbox_get_threading_type(mailbox);
1312 
1313         libbalsa_mailbox_set_threading(mailbox, cur_type);
1314     }
1315     local->thread_id = 0;
1316 
1317     libbalsa_unlock_mailbox(mailbox);
1318     g_object_unref(local);
1319 
1320     return FALSE;
1321 }
1322 
1323 /* The class method; prepare messages from start + 1 to the end of the
1324  * mailbox; return TRUE if successful. */
1325 static gboolean
libbalsa_mailbox_local_prepare_threading(LibBalsaMailbox * mailbox,guint start)1326 libbalsa_mailbox_local_prepare_threading(LibBalsaMailbox * mailbox,
1327                                          guint start)
1328 {
1329     LibBalsaMailboxLocal *local = LIBBALSA_MAILBOX_LOCAL(mailbox);
1330     guint msgno;
1331     gboolean need_thread = FALSE;
1332     gchar *text;
1333     guint total;
1334     LibBalsaProgress progress = LIBBALSA_PROGRESS_INIT;
1335     gboolean retval = TRUE;
1336 
1337     libbalsa_lock_mailbox(mailbox);
1338     libbalsa_mailbox_local_set_threading_info(local);
1339 
1340     text = g_strdup_printf(_("Preparing %s"), mailbox->name);
1341     total = libbalsa_mailbox_total_messages(mailbox);
1342     libbalsa_progress_set_text(&progress, text, total - start);
1343     g_free(text);
1344 
1345     for (msgno = start + 1; msgno <= total; msgno++) {
1346         if (lbm_local_prepare_msgno(local, msgno)) {
1347             need_thread = TRUE;
1348             libbalsa_progress_set_fraction(&progress,
1349                                            ((gdouble) msgno) /
1350                                            ((gdouble) (total - start)));
1351             if (!MAILBOX_OPEN(mailbox)) {
1352                 /* Mailbox was closed during set-fraction. */
1353                 retval = FALSE;
1354                 break;
1355             }
1356         }
1357     }
1358 
1359     libbalsa_progress_set_text(&progress, NULL, 0);
1360 
1361     if (retval && need_thread && !local->thread_id) {
1362         LibBalsaMailbox *mailbox = LIBBALSA_MAILBOX(local);
1363 
1364         if (libbalsa_mailbox_get_threading_type(mailbox) !=
1365             LB_MAILBOX_THREADING_FLAT
1366             || libbalsa_mailbox_get_sort_field(mailbox) !=
1367             LB_MAILBOX_SORT_NO) {
1368             g_object_ref(local);
1369             local->thread_id =
1370                 g_idle_add((GSourceFunc) lbm_local_thread_idle, local);
1371         }
1372     }
1373     libbalsa_unlock_mailbox(mailbox);
1374 
1375     return retval;
1376 }
1377 
1378 /* fetch message structure method: all local mailboxes have their own
1379  * methods, which ensure that message->mime_msg != NULL, then chain up
1380  * to this one.
1381  */
1382 
1383 static gboolean
libbalsa_mailbox_local_fetch_structure(LibBalsaMailbox * mailbox,LibBalsaMessage * message,LibBalsaFetchFlag flags)1384 libbalsa_mailbox_local_fetch_structure(LibBalsaMailbox *mailbox,
1385                                        LibBalsaMessage *message,
1386                                        LibBalsaFetchFlag flags)
1387 {
1388     GMimeMessage *mime_message = message->mime_msg;
1389 
1390     if (!mime_message || !mime_message->mime_part)
1391 	return FALSE;
1392 
1393     if(flags & LB_FETCH_STRUCTURE) {
1394         LibBalsaMessageBody *body = libbalsa_message_body_new(message);
1395         libbalsa_message_body_set_mime_body(body,
1396                                             mime_message->mime_part);
1397         libbalsa_message_append_part(message, body);
1398         libbalsa_message_headers_from_gmime(message->headers, mime_message);
1399     }
1400     if(flags & LB_FETCH_RFC822_HEADERS) {
1401         message->headers->user_hdrs =
1402             libbalsa_message_user_hdrs_from_gmime(mime_message);
1403         message->has_all_headers = 1;
1404     }
1405 
1406     return TRUE;
1407 }
1408 
1409 static void
libbalsa_mailbox_local_fetch_headers(LibBalsaMailbox * mailbox,LibBalsaMessage * message)1410 libbalsa_mailbox_local_fetch_headers(LibBalsaMailbox * mailbox,
1411 				     LibBalsaMessage * message)
1412 {
1413     g_return_if_fail(message->headers->user_hdrs == NULL);
1414 
1415     if (message->mime_msg)
1416 	message->headers->user_hdrs =
1417 	    libbalsa_message_user_hdrs_from_gmime(message->mime_msg);
1418     else {
1419 	libbalsa_mailbox_fetch_message_structure(mailbox, message,
1420 						 LB_FETCH_RFC822_HEADERS);
1421 	libbalsa_mailbox_release_message(mailbox, message);
1422     }
1423 }
1424 
1425 static gboolean
libbalsa_mailbox_local_get_msg_part(LibBalsaMessage * msg,LibBalsaMessageBody * part,GError ** err)1426 libbalsa_mailbox_local_get_msg_part(LibBalsaMessage *msg,
1427                                     LibBalsaMessageBody *part,
1428                                     GError **err)
1429 {
1430     g_return_val_if_fail(part->mime_part, FALSE);
1431 
1432     return GMIME_IS_PART(part->mime_part)
1433         || GMIME_IS_MULTIPART(part->mime_part)
1434 	|| GMIME_IS_MESSAGE_PART(part->mime_part);
1435 }
1436 
1437 /*--------------------------------*/
1438 /*  Start of threading functions  */
1439 /*--------------------------------*/
1440 /*
1441  * This code includes two message threading functions.
1442  * The first is the implementation of jwz's algorithm describled at
1443  * http://www.jwz.org/doc/threading.html . The another is very simple and
1444  * trivial one. If you confirm that your mailbox includes every threaded
1445  * messages, the later will be enough. Those functions are selectable on
1446  * each mailbox by setting the 'type' member in BalsaIndex. If you don't need
1447  * message threading functionality, just specify 'LB_MAILBOX_THREADING_FLAT'.
1448  *
1449  * ymnk@jcraft.com
1450  */
1451 
1452 struct _ThreadingInfo {
1453     LibBalsaMailbox *mailbox;
1454     GNode *root;
1455     GHashTable *id_table;
1456     GHashTable *subject_table;
1457     GSList *unthreaded;
1458     LibBalsaMailboxThreadingType type;
1459 };
1460 typedef struct _ThreadingInfo ThreadingInfo;
1461 
1462 static gboolean lbml_set_parent(GNode * node, ThreadingInfo * ti);
1463 static GNode *lbml_insert_node(GNode * node,
1464                                LibBalsaMailboxLocalInfo * info,
1465                                ThreadingInfo * ti);
1466 static GNode *lbml_find_parent(LibBalsaMailboxLocalInfo * info,
1467 			       ThreadingInfo * ti);
1468 static gboolean lbml_prune(GNode * node, ThreadingInfo * ti);
1469 static void lbml_subject_gather(GNode * node, ThreadingInfo * ti);
1470 static void lbml_subject_merge(GNode * node, ThreadingInfo * ti);
1471 static const gchar *lbml_chop_re(const gchar * str);
1472 static gboolean lbml_construct(GNode * node, ThreadingInfo * ti);
1473 #ifdef MAKE_EMPTY_CONTAINER_FOR_MISSING_PARENT
1474 static void lbml_clear_empty(GNode * root);
1475 #endif				/* MAKE_EMPTY_CONTAINER_FOR_MISSING_PARENT */
1476 
1477 static void
lbml_info_setup(LibBalsaMailbox * mailbox,ThreadingInfo * ti)1478 lbml_info_setup(LibBalsaMailbox * mailbox, ThreadingInfo * ti)
1479 {
1480     ti->mailbox = mailbox;
1481     ti->root = g_node_new(mailbox->msg_tree);
1482     ti->id_table = g_hash_table_new(g_str_hash, g_str_equal);
1483     ti->subject_table = NULL;
1484     ti->unthreaded =
1485         g_object_get_data(G_OBJECT(mailbox), LIBBALSA_MAILBOX_UNTHREADED);
1486 }
1487 
1488 static void
lbml_info_free(ThreadingInfo * ti)1489 lbml_info_free(ThreadingInfo * ti)
1490 {
1491     g_hash_table_destroy(ti->id_table);
1492     if (ti->subject_table)
1493 	g_hash_table_destroy(ti->subject_table);
1494     g_node_destroy(ti->root);
1495 }
1496 
1497 static void
lbml_threading_jwz(LibBalsaMailbox * mailbox)1498 lbml_threading_jwz(LibBalsaMailbox * mailbox)
1499 {
1500     /* This implementation of JWZ's algorithm uses a second tree, rooted
1501      * at ti.root, for the message IDs.  Each node in the second tree
1502      * that corresponds to a real message has a pointer to the
1503      * corresponding msg_tree node in its data field.  Nodes in the
1504      * mailbox's msg_tree have names beginning with msg_; all other
1505      * GNodes are in the second tree.  The ti.id_table maps message-id
1506      * to a node in the second tree. */
1507     ThreadingInfo ti;
1508 
1509     lbml_info_setup(mailbox, &ti);
1510 
1511     /* Traverse the mailbox's msg_tree, to build the second tree. */
1512     g_node_traverse(mailbox->msg_tree, G_POST_ORDER, G_TRAVERSE_ALL, -1,
1513 		    (GNodeTraverseFunc) lbml_set_parent, &ti);
1514     /* Prune the second tree. */
1515     g_node_traverse(ti.root, G_POST_ORDER, G_TRAVERSE_ALL, -1,
1516 		    (GNodeTraverseFunc) lbml_prune, &ti);
1517 
1518     /* Do the evil subject gather and merge on the second tree. */
1519     ti.subject_table = g_hash_table_new(g_str_hash, g_str_equal);
1520     g_node_children_foreach(ti.root, G_TRAVERSE_ALL,
1521 			    (GNodeForeachFunc) lbml_subject_gather, &ti);
1522     g_node_children_foreach(ti.root, G_TRAVERSE_ALL,
1523 			    (GNodeForeachFunc) lbml_subject_merge, &ti);
1524 
1525     /* Traverse the second tree and reparent corresponding nodes in the
1526      * mailbox's msg_tree. */
1527     g_node_traverse(ti.root, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1528                     (GNodeTraverseFunc) lbml_construct, &ti);
1529 
1530 #ifdef MAKE_EMPTY_CONTAINER_FOR_MISSING_PARENT
1531     lbml_clear_empty(ti.root);
1532 #endif				/* MAKE_EMPTY_CONTAINER_FOR_MISSING_PARENT */
1533 
1534     lbml_info_free(&ti);
1535 }
1536 
1537 static LibBalsaMailboxLocalInfo *
lbml_get_info(GNode * node,ThreadingInfo * ti)1538 lbml_get_info(GNode * node, ThreadingInfo * ti)
1539 {
1540     guint msgno = GPOINTER_TO_UINT(node->data);
1541     LibBalsaMailboxLocalInfo *info;
1542     LibBalsaMailboxLocal *local = LIBBALSA_MAILBOX_LOCAL(ti->mailbox);
1543 
1544     if (msgno == 0 || msgno > local->threading_info->len)
1545         return NULL;
1546 
1547     info = g_ptr_array_index(local->threading_info, msgno - 1);
1548 
1549     return info;
1550 }
1551 
1552 static void
lbml_unlink_and_prepend(GNode * node,GNode * parent)1553 lbml_unlink_and_prepend(GNode * node, GNode * parent)
1554 {
1555     g_node_unlink(node);
1556     g_node_prepend(parent, node);
1557 }
1558 
1559 static void
lbml_move_children(GNode * node,GNode * parent)1560 lbml_move_children(GNode * node, GNode * parent)
1561 {
1562     GNode *child;
1563 
1564     while ((child = node->children))
1565 	lbml_unlink_and_prepend(child, parent);
1566 }
1567 
1568 static gboolean
lbml_set_parent(GNode * msg_node,ThreadingInfo * ti)1569 lbml_set_parent(GNode * msg_node, ThreadingInfo * ti)
1570 {
1571     LibBalsaMailboxLocalInfo *info;
1572     GNode *node;
1573     GNode *parent;
1574     GNode *child;
1575 
1576     if (!msg_node->parent)
1577 	return FALSE;
1578 
1579     info = lbml_get_info(msg_node, ti);
1580 
1581     if (!info) /* FIXME assert this? */
1582 	return FALSE;
1583 
1584     node = lbml_insert_node(msg_node, info, ti);
1585 
1586     /*
1587      * Set the parent of this message to be the last element in References.
1588      * Note that this message may have a parent already: this can happen
1589      * because we saw this ID in a References field, and presumed a
1590      * parent based on the other entries in that field. Now that we have
1591      * the actual message, we can be more definitive, so throw away the
1592      * old parent and use this new one. Find this Container in the
1593      * parent's children list, and unlink it.
1594      *
1595      * Note that this could cause this message to now have no parent, if
1596      * it has no references field, but some message referred to it as the
1597      * non-first element of its references. (Which would have been some
1598      * kind of lie...)
1599      *
1600      * Note that at all times, the various ``parent'' and ``child''
1601      * fields must be kept inter-consistent.
1602      */
1603     /* In this implementation, lbml_find_parent always returns a
1604      * parent; if the message has no parent, it's the root of the
1605      * whole mailbox tree. */
1606 
1607     parent = lbml_find_parent(info, ti);
1608 
1609     if (node->parent == parent
1610 	/* Nothing to do... */
1611 	|| node == parent)
1612 	/* This message listed itself as its parent! Oh well... */
1613 	return FALSE;
1614 
1615     child = node->children;
1616     while (child) {
1617 	GNode *next = child->next;
1618 	if (child == parent || g_node_is_ancestor(child, parent)) {
1619 	    /* Prepending node to parent would create a
1620 	     * loop; in lbml_find_parent, we just omit making
1621 	     * the link, but here we really want to link the
1622 	     * message's node to its parent, so we'll fix
1623 	     * the tree: unlink the offending child and prepend it
1624 	     * to the node's parent. */
1625 	    lbml_unlink_and_prepend(child, node->parent);
1626 	}
1627 	child = next;
1628     }
1629 
1630     lbml_unlink_and_prepend(node, parent);
1631 
1632     return FALSE;
1633 }
1634 
1635 static GNode *
lbml_find_parent(LibBalsaMailboxLocalInfo * info,ThreadingInfo * ti)1636 lbml_find_parent(LibBalsaMailboxLocalInfo * info, ThreadingInfo * ti)
1637 {
1638     /*
1639      * For each element in the message's References field:
1640      *   + Find a Container object for the given Message-ID:
1641      *     + If there's one in id_table use that;
1642      *     + Otherwise, make (and index) one with a null Message.
1643      *   + Link the References field's Containers together in the order
1644      *     implied by the References header.
1645      *     + If they are already linked, don't change the existing links.
1646      *     + Do not add a link if adding that link would introduce a loop:
1647      *       that is, before asserting A->B, search down the children of B
1648      *       to see if A is reachable.
1649      */
1650 
1651     /* The root of the mailbox tree is the default parent. */
1652     GNode *parent = ti->root;
1653     GList *reference;
1654     GHashTable *id_table = ti->id_table;
1655 
1656     for (reference = info->refs_for_threading; reference;
1657 	 reference = reference->next) {
1658 	gchar *id = reference->data;
1659 	GNode *foo = g_hash_table_lookup(id_table, id);
1660 
1661 	if (foo == NULL) {
1662 	    foo = g_node_new(NULL);
1663 	    g_hash_table_insert(id_table, id, foo);
1664 	}
1665 
1666 	/* Avoid nasty surprises. */
1667 	if (foo != parent && !g_node_is_ancestor(foo, parent))
1668 	    if (!foo->parent || foo->parent == ti->root)
1669 		lbml_unlink_and_prepend(foo, parent);
1670 
1671 	parent = foo;
1672     }
1673     return parent;
1674 }
1675 
1676 static gboolean
lbml_is_replied(GNode * msg_node,ThreadingInfo * ti)1677 lbml_is_replied(GNode * msg_node, ThreadingInfo * ti)
1678 {
1679     guint msgno = GPOINTER_TO_UINT(msg_node->data);
1680     return libbalsa_mailbox_msgno_get_status(ti->mailbox, msgno)
1681 	== LIBBALSA_MESSAGE_STATUS_REPLIED;
1682 }
1683 
1684 static GNode *
lbml_insert_node(GNode * msg_node,LibBalsaMailboxLocalInfo * info,ThreadingInfo * ti)1685 lbml_insert_node(GNode * msg_node, LibBalsaMailboxLocalInfo * info,
1686 		 ThreadingInfo * ti)
1687 {
1688     /*
1689      * If id_table contains an *empty* Container for this ID:
1690      *   + Store this message in the Container's message slot.
1691      * else
1692      *   + Create a new Container object holding this message;
1693      *   + Index the Container by Message-ID in id_table.
1694      */
1695     /* We'll make sure that we thread off a replied-to message, if there
1696      * is one. */
1697     GNode *node = NULL;
1698     gchar *id = info->message_id;
1699     GHashTable *id_table = ti->id_table;
1700 
1701     if (id)
1702 	node = g_hash_table_lookup(id_table, id);
1703 
1704     if (node) {
1705 	GNode *prev_msg_node = node->data;
1706 	/* If this message has not been replied to, or if the container
1707 	 * is empty, store it in the container. If there was a message
1708 	 * in the container already, swap it with this one, otherwise
1709 	 * set the current one to NULL. */
1710 	if (!lbml_is_replied(msg_node, ti) || !prev_msg_node) {
1711 	    node->data = msg_node;
1712 	    msg_node = prev_msg_node;
1713 	}
1714     }
1715     /* If we already stored the message in a previously empty container,
1716      * msg_node is NULL. If either the previous message or the current
1717      * one has been replied to, msg_node now points to a replied-to
1718      * message. */
1719     if (msg_node)
1720 	node = g_node_new(msg_node);
1721 
1722     if (id)
1723 	g_hash_table_insert(id_table, id, node);
1724 
1725     return node;
1726 }
1727 
1728 static gboolean
lbml_prune(GNode * node,ThreadingInfo * ti)1729 lbml_prune(GNode * node, ThreadingInfo * ti)
1730 {
1731     /*
1732      * Recursively walk all containers under the root set. For each container:
1733      *
1734      * + If it is an empty container with no children, nuke it.
1735      * + If the Container has no Message, but does have children,
1736      *   remove this container but promote its children to this level
1737      *  (that is, splice them in to the current child list.)
1738      *
1739      * Do not promote the children if doing so would promote them to
1740      * the root set -- unless there is only one child, in which case, do.
1741      */
1742 
1743     if (node->data != NULL || node == ti->root)
1744 	return FALSE;
1745 
1746 #ifdef MAKE_EMPTY_CONTAINER_FOR_MISSING_PARENT
1747     if (node->children != NULL
1748 	&& (node->parent != ti->root || node->children->next == NULL))
1749 	lbml_move_children(node, node->parent);
1750 
1751     if (node->children == NULL)
1752 	g_node_destroy(node);
1753 #else				/* MAKE_EMPTY_CONTAINER_FOR_MISSING_PARENT */
1754     lbml_move_children(node, node->parent);
1755     g_node_destroy(node);
1756 #endif				/* MAKE_EMPTY_CONTAINER_FOR_MISSING_PARENT */
1757 
1758     return FALSE;
1759 }
1760 
1761 static const gchar *
lbml_get_subject(GNode * node,ThreadingInfo * ti)1762 lbml_get_subject(GNode * node, ThreadingInfo * ti)
1763 {
1764     guint msgno = GPOINTER_TO_UINT(((GNode *) node->data)->data);
1765     return libbalsa_mailbox_msgno_get_subject(ti->mailbox, msgno);
1766 }
1767 
1768 static void
lbml_subject_gather(GNode * node,ThreadingInfo * ti)1769 lbml_subject_gather(GNode * node, ThreadingInfo * ti)
1770 {
1771     const gchar *subject = NULL, *old_subject;
1772     const gchar *chopped_subject = NULL;
1773     GNode *old;
1774     GHashTable *subject_table = ti->subject_table;
1775 
1776     /*
1777      * If any two members of the root set have the same subject, merge them.
1778      * This is so that messages which don't have References headers at all
1779      * still get threaded (to the extent possible, at least.)
1780      *
1781      * + Construct a new hash table, subject_table, which associates subject
1782      *   strings with Container objects.
1783      *
1784      * + For each Container in the root set:
1785      *
1786      *   Find the subject of that sub-tree:
1787      *   + If there is a message in the Container, the subject is the subject of
1788      *     that message.
1789      *   + If there is no message in the Container, then the Container will have
1790      *     at least one child Container, and that Container will have a message.
1791      *     Use the subject of that message instead.
1792      *   + Strip ``Re:'', ``RE:'', ``RE[5]:'', ``Re: Re[4]: Re:'' and so on.
1793      *   + If the subject is now "", give up on this
1794      *   + Add this Container to the subject_table if: Container.
1795      *     + There is no container in the table with this subject, or
1796      *     + This one is an empty container and the old one is not: the empty
1797      *       one is more interesting as a root, so put it in the table instead.
1798      *     + The container in the table has a ``Re:'' version of this subject,
1799      *       and this container has a non-``Re:'' version of this subject.
1800      *       The non-re version is the more interesting of the two.
1801      *
1802      * + Now the subject_table is populated with one entry for each subject
1803      *   which occurs in the root set. Now iterate over the root set,
1804      *   and gather together the difference.
1805      *
1806      *   For each Container in the root set:
1807      *
1808      *   Find the subject of this Container (as above.)
1809      *   Look up the Container of that subject in the table.
1810      *   If it is null, or if it is this container, continue.
1811      *   Otherwise, we want to group together this Container and the one
1812      *   in the table. There are a few possibilities:
1813      *     + If both are dummies, prepend one's children to the other, and
1814      *       remove the now-empty container.
1815      *
1816      *     + If one container is a empty and the other is not, make the
1817      *       non-empty one be a child of the empty, and a sibling of the
1818      *       other ``real'' messages with the
1819      *       same subject (the empty's children.)
1820      *     + If that container is a non-empty, and that message's subject
1821      *       does not begin with ``Re:'', but this message's subject does,
1822      *       then make this be a child of the other.
1823      *     + If that container is a non-empty, and that message's subject
1824      *       begins with ``Re:'', but this message's subject does not,
1825      *       then make that be a child of this one -- they were misordered.
1826      *       (This happens somewhat implicitly, since if there are two
1827      *       messages, one with Re: and one without, the one without
1828      *       will be in the hash table, regardless of the order in which
1829      *       they were seen.)
1830      *
1831      *     + Otherwise, make a new empty container and make both msgs be
1832      *       a child of it. This catches the both-are-replies and
1833      *       neither-are-replies cases, and makes them be siblings instead of
1834      *       asserting a hierarchical relationship which might not be true.
1835      *
1836      *     (People who reply to messages without using ``Re:'' and without
1837      *     using a References line will break this slightly. Those people suck.)
1838      *
1839      *     (It has occurred to me that taking the date or message number into
1840      *     account would be one way of resolving some of the ambiguous cases,
1841      *     but that's not altogether straightforward either.)
1842      */
1843 
1844     subject = lbml_get_subject(node, ti);
1845     if (subject == NULL)
1846 	return;
1847     chopped_subject = lbml_chop_re(subject);
1848     if (chopped_subject == NULL
1849 	|| !strcmp(chopped_subject, _("(No subject)")))
1850 	return;
1851 
1852     old = g_hash_table_lookup(subject_table, chopped_subject);
1853 #ifdef MAKE_EMPTY_CONTAINER_FOR_MISSING_PARENT
1854     if (old == NULL || (node->data == NULL && old->data != NULL)) {
1855 #else				/* MAKE_EMPTY_CONTAINER_FOR_MISSING_PARENT */
1856     if (old == NULL) {
1857 #endif				/* MAKE_EMPTY_CONTAINER_FOR_MISSING_PARENT */
1858 	g_hash_table_insert(subject_table, (char *) chopped_subject,
1859 			    node);
1860 	return;
1861     }
1862 
1863     old_subject = lbml_get_subject(old, ti);
1864 
1865     if (old_subject != lbml_chop_re(old_subject)
1866 	&& subject == chopped_subject)
1867 	g_hash_table_insert(subject_table, (gchar *) chopped_subject,
1868 			    node);
1869 }
1870 
1871 /* Swap data and children. */
1872 static void
1873 lbml_swap(GNode * node1, GNode * node2, ThreadingInfo * ti)
1874 {
1875     GNode *tmp_node = g_node_new(NULL);
1876     gpointer tmp_data;
1877 
1878     lbml_move_children(node1, tmp_node);
1879     lbml_move_children(node2, node1);
1880     lbml_move_children(tmp_node, node2);
1881     g_node_destroy(tmp_node);
1882 
1883     tmp_data = node1->data;
1884     node1->data = node2->data;
1885     node2->data = tmp_data;
1886 }
1887 
1888 static void
1889 lbml_subject_merge(GNode * node, ThreadingInfo * ti)
1890 {
1891     const gchar *subject, *subject2;
1892     const gchar *chopped_subject, *chopped_subject2;
1893     GNode *node2;
1894 
1895     subject = lbml_get_subject(node, ti);
1896     if (subject == NULL)
1897 	return;
1898     chopped_subject = lbml_chop_re(subject);
1899     if (chopped_subject == NULL)
1900 	return;
1901 
1902     node2 = g_hash_table_lookup(ti->subject_table, chopped_subject);
1903     if (node2 == NULL || node2 == node)
1904 	return;
1905 
1906 #ifdef MAKE_EMPTY_CONTAINER_FOR_MISSING_PARENT
1907     if (node->data == NULL && node2->data == NULL) {
1908 	lbml_move_children(node, node2);
1909 	g_node_destroy(node);
1910 	return;
1911     }
1912 
1913     if (node->data == NULL)
1914 	/* node2 should be made a child of node, but unlinking node2
1915 	 * could mess up the foreach, so we'll swap them and fall
1916 	 * through to the next case. */
1917 	lbml_swap(node, node2, ti);
1918 
1919     if (node2->data == NULL) {
1920 	lbml_unlink_and_prepend(node, node2);
1921 	return;
1922     }
1923 #endif				/* MAKE_EMPTY_CONTAINER_FOR_MISSING_PARENT */
1924 
1925     subject2 = lbml_get_subject(node2, ti);
1926     chopped_subject2 = lbml_chop_re(subject2);
1927 
1928     if ((subject2 == chopped_subject2) && subject != chopped_subject)
1929 	/* Make node a child of node2. */
1930 	lbml_unlink_and_prepend(node, node2);
1931     else if ((subject2 != chopped_subject2)
1932 	     && subject == chopped_subject) {
1933 	/* Make node2 a child of node; as above, swap them to avoid
1934 	 * unlinking node2. */
1935 	lbml_swap(node, node2, ti);
1936 	lbml_unlink_and_prepend(node, node2);
1937 #ifdef MAKE_EMPTY_CONTAINER_FOR_MISSING_PARENT
1938     } else {
1939 	/* Make both node and node2 children of a new empty node; as
1940 	 * above, swap node2 and the new node to avoid unlinking node2.
1941 	 */
1942 	GNode *new_node = g_node_new(NULL);
1943 
1944 	lbml_move_children(node2, new_node);
1945 	new_node->data = node2->data;
1946 	node2->data = NULL;
1947 	lbml_unlink_and_prepend(node, node2);
1948 	lbml_unlink_and_prepend(new_node, node2);
1949 #endif				/* MAKE_EMPTY_CONTAINER_FOR_MISSING_PARENT */
1950     }
1951 }
1952 
1953 /* The more heuristics should be added. */
1954 static const gchar *
1955 lbml_chop_re(const gchar * str)
1956 {
1957     const gchar *p = str;
1958     while (*p) {
1959 	while (*p && g_ascii_isspace((int) *p))
1960 	    p++;
1961 	if (!*p)
1962 	    break;
1963 
1964 	if (g_ascii_strncasecmp(p, "re:", 3) == 0
1965 	    || g_ascii_strncasecmp(p, "aw:", 3) == 0) {
1966 	    p += 3;
1967 	    continue;
1968 	} else if (g_ascii_strncasecmp(p, _("Re:"), strlen(_("Re:"))) == 0) {
1969 	    /* should "re" be localized ? */
1970 	    p += strlen(_("Re:"));
1971 	    continue;
1972 	}
1973 	break;
1974     }
1975     return p;
1976 }
1977 
1978 static gboolean
1979 lbml_construct(GNode * node, ThreadingInfo * ti)
1980 {
1981     GNode *msg_node;
1982 
1983     if (node->parent && (msg_node = node->data)) {
1984         GNode *msg_parent = node->parent->data;
1985 
1986         if (msg_parent && msg_node->parent != msg_parent
1987             && !g_node_is_ancestor(msg_node, msg_parent))
1988             libbalsa_mailbox_unlink_and_prepend(ti->mailbox, msg_node,
1989                                                 msg_parent);
1990     }
1991 
1992     return FALSE;
1993 }
1994 
1995 
1996 #ifdef MAKE_EMPTY_CONTAINER_FOR_MISSING_PARENT
1997 static void
1998 lbml_clear_empty(GNode * msg_tree)
1999 {
2000     GNode *node = msg_tree->children;
2001     while (node) {
2002 	GNode *next = node->next;
2003 	if (!node->data && !node->children)
2004 	    g_node_destroy(node);
2005 	node = next;
2006     }
2007 }
2008 #endif				/* MAKE_EMPTY_CONTAINER_FOR_MISSING_PARENT */
2009 
2010 /* yet another message threading function */
2011 
2012 static gboolean lbml_insert_message(GNode * node, ThreadingInfo * ti);
2013 static gboolean lbml_thread_message(GNode * node, ThreadingInfo * ti);
2014 
2015 static void
2016 lbml_threading_simple(LibBalsaMailbox * mailbox,
2017 		      LibBalsaMailboxThreadingType type)
2018 {
2019     GNode *msg_tree = mailbox->msg_tree;
2020     ThreadingInfo ti;
2021 
2022     lbml_info_setup(mailbox, &ti);
2023 
2024     if (type == LB_MAILBOX_THREADING_SIMPLE)
2025 	g_node_traverse(msg_tree, G_POST_ORDER, G_TRAVERSE_ALL,
2026 			-1, (GNodeTraverseFunc) lbml_insert_message, &ti);
2027 
2028     ti.type = type;
2029     g_node_traverse(msg_tree, G_POST_ORDER, G_TRAVERSE_ALL, -1,
2030 		    (GNodeTraverseFunc) lbml_thread_message, &ti);
2031 
2032 #ifdef MAKE_EMPTY_CONTAINER_FOR_MISSING_PARENT
2033     lbml_clear_empty(msg_tree);
2034 #endif				/* MAKE_EMPTY_CONTAINER_FOR_MISSING_PARENT */
2035 
2036     lbml_info_free(&ti);
2037 }
2038 
2039 static gboolean
2040 lbml_insert_message(GNode * node, ThreadingInfo * ti)
2041 {
2042     LibBalsaMailboxLocalInfo *info;
2043 
2044     if (!node->parent)
2045 	return FALSE;
2046 
2047     info = lbml_get_info(node, ti);
2048     if (!info)
2049 	return FALSE;
2050 
2051     if (info->message_id)
2052 	g_hash_table_insert(ti->id_table, info->message_id, node);
2053 
2054     return FALSE;
2055 }
2056 
2057 static gboolean
2058 lbml_thread_message(GNode * node, ThreadingInfo * ti)
2059 {
2060     if (!node->parent)
2061         return FALSE;
2062 
2063     if (ti->type == LB_MAILBOX_THREADING_FLAT) {
2064         if (node->parent != ti->mailbox->msg_tree)
2065             libbalsa_mailbox_unlink_and_prepend(ti->mailbox, node,
2066                                                 ti->mailbox->msg_tree);
2067     } else {
2068         LibBalsaMailboxLocalInfo *info;
2069         GList *refs;
2070         GNode *parent = NULL;
2071 
2072         info = lbml_get_info(node, ti);
2073         if (!info)
2074             return FALSE;
2075 
2076         refs = info->refs_for_threading;
2077         if (refs)
2078             parent = g_hash_table_lookup(ti->id_table,
2079                                          g_list_last(refs)->data);
2080 
2081         if (!parent)
2082             parent = ti->mailbox->msg_tree;
2083         if (parent != node->parent && parent != node
2084             && !g_node_is_ancestor(node, parent))
2085             libbalsa_mailbox_unlink_and_prepend(ti->mailbox, node, parent);
2086     }
2087 
2088     return FALSE;
2089 }
2090 /*------------------------------*/
2091 /*  End of threading functions  */
2092 /*------------------------------*/
2093 
2094 /* Helper for maildir and mh. */
2095 GMimeMessage *
2096 libbalsa_mailbox_local_get_mime_message(LibBalsaMailbox * mailbox,
2097 					 const gchar * name1,
2098 					 const gchar * name2)
2099 {
2100     GMimeStream *mime_stream;
2101     GMimeParser *mime_parser;
2102     GMimeMessage *mime_message;
2103 
2104     mime_stream =
2105 	libbalsa_mailbox_local_get_message_stream(mailbox, name1, name2);
2106     if (!mime_stream)
2107 	return NULL;
2108 
2109     mime_parser = g_mime_parser_new_with_stream(mime_stream);
2110     g_mime_parser_set_scan_from(mime_parser, FALSE);
2111     mime_message = g_mime_parser_construct_message(mime_parser);
2112 
2113     g_object_unref(mime_parser);
2114     g_object_unref(mime_stream);
2115 
2116     return mime_message;
2117 }
2118 
2119 GMimeStream *
2120 libbalsa_mailbox_local_get_message_stream(LibBalsaMailbox * mailbox,
2121 					   const gchar * name1,
2122 					   const gchar * name2)
2123 {
2124     const gchar *path;
2125     gchar *filename;
2126     int fd;
2127     GMimeStream *stream = NULL;
2128 
2129     path = libbalsa_mailbox_local_get_path(mailbox);
2130     filename = g_build_filename(path, name1, name2, NULL);
2131 
2132     fd = open(filename, O_RDONLY);
2133     if (fd != -1) {
2134 	stream = g_mime_stream_fs_new(fd);
2135 	if (!stream)
2136 	    libbalsa_information(LIBBALSA_INFORMATION_ERROR,
2137 				 _("Open of %s failed. Errno = %d, "),
2138 				 filename, errno);
2139     }
2140     g_free(filename);
2141 
2142     return stream;
2143 }
2144 
2145 /* Queued sync. */
2146 
2147 static void
2148 lbm_local_sync_real(LibBalsaMailboxLocal * local)
2149 {
2150     LibBalsaMailbox *mailbox = (LibBalsaMailbox*)local;
2151     time_t tstart;
2152 
2153     time(&tstart);
2154     libbalsa_lock_mailbox(mailbox);
2155     if (local->sync_id &&                       /* request still pending */
2156         MAILBOX_OPEN(mailbox) &&                   /* mailbox still open */
2157         !libbalsa_mailbox_sync_storage(mailbox, FALSE))   /* cannot sync */
2158 	libbalsa_information(LIBBALSA_INFORMATION_WARNING,
2159 			     _("Failed to sync mailbox \"%s\""),
2160 			     mailbox->name);
2161     local->sync_id = 0;
2162     local->sync_time += time(NULL)-tstart;
2163     local->sync_cnt++;
2164     libbalsa_unlock_mailbox(mailbox);
2165     g_object_unref(local);
2166 }
2167 
2168 static gboolean
2169 lbm_local_sync_idle(LibBalsaMailboxLocal * local)
2170 {
2171 #if 0 && defined(BALSA_USE_THREADS)
2172     pthread_t sync_thread;
2173 
2174     pthread_create(&sync_thread, NULL, (void *) lbm_local_sync_real, local);
2175     pthread_detach(sync_thread);
2176 #else                           /*BALSA_USE_THREADS */
2177     lbm_local_sync_real(local);
2178 #endif                          /*BALSA_USE_THREADS */
2179 
2180     return FALSE;
2181 }
2182 
2183 static void
2184 lbm_local_sync_queue(LibBalsaMailboxLocal * local)
2185 {
2186     guint schedule_delay;
2187 
2188     /* The optimal behavior here would be to keep rescheduling
2189     * requests.  But think of following: the idle handler started and
2190     * triggered lbm_local_sync_real thread. While it waits for the lock,
2191     * another queue request is filed but it is too late for removal of
2192     * the sync thread. And we get two sync threads executing one after
2193     * another, etc. So it is better to do sync bit too often... */
2194     if (local->sync_id)
2195         return;
2196     g_object_ref(local);
2197     /* queue sync job so that the delay is at least five times longer
2198      * than the syncing time. Otherwise large mailbox owners will be
2199      * annnoyed. */
2200     schedule_delay = (local->sync_time*5000)/local->sync_cnt;
2201     local->sync_id = g_timeout_add_full(G_PRIORITY_LOW, schedule_delay,
2202                                         (GSourceFunc)lbm_local_sync_idle,
2203                                         local, NULL);
2204 }
2205 
2206 static void
2207 lbm_local_sort(LibBalsaMailbox * mailbox, GArray *sort_array)
2208 {
2209     LIBBALSA_MAILBOX_CLASS(parent_class)->sort(mailbox, sort_array);
2210     lbm_local_queue_save_tree(LIBBALSA_MAILBOX_LOCAL(mailbox));
2211 }
2212 
2213 #define FLAGS_REALLY_DIFFER(flags0, flags1) \
2214         (((flags0 ^ flags1) & LIBBALSA_MESSAGE_FLAGS_REAL) != 0)
2215 
2216 static gboolean
2217 libbalsa_mailbox_local_messages_change_flags(LibBalsaMailbox * mailbox,
2218                                              GArray * msgnos,
2219                                              LibBalsaMessageFlag set,
2220                                              LibBalsaMessageFlag clear)
2221 {
2222     LibBalsaMailboxLocal *local = LIBBALSA_MAILBOX_LOCAL(mailbox);
2223     LibBalsaMailboxLocalMessageInfo *(*get_info) (LibBalsaMailboxLocal *,
2224                                                   guint) =
2225         LIBBALSA_MAILBOX_LOCAL_GET_CLASS(local)->get_info;
2226     guint i;
2227     guint changed = 0;
2228     libbalsa_lock_mailbox(mailbox);
2229     for (i = 0; i < msgnos->len; i++) {
2230         guint msgno = g_array_index(msgnos, guint, i);
2231         LibBalsaMailboxLocalMessageInfo *msg_info;
2232         LibBalsaMessageFlag old_flags;
2233 
2234         if (!(msgno > 0
2235               && msgno <= libbalsa_mailbox_total_messages(mailbox))) {
2236             g_warning("msgno %u out of range", msgno);
2237             continue;
2238         }
2239 
2240         msg_info = get_info(local, msgno);
2241         old_flags = msg_info->flags;
2242         msg_info->flags |= set;
2243         msg_info->flags &= ~clear;
2244         if (!FLAGS_REALLY_DIFFER(msg_info->flags, old_flags))
2245             /* No real flags changed. */
2246             continue;
2247         ++changed;
2248 
2249         if (msg_info->message)
2250             msg_info->message->flags = msg_info->flags;
2251 
2252         libbalsa_mailbox_index_set_flags(mailbox, msgno, msg_info->flags);
2253 
2254         if (msg_info->loaded) {
2255             gboolean was_unread_undeleted, is_unread_undeleted;
2256 
2257             was_unread_undeleted =
2258                 (old_flags & LIBBALSA_MESSAGE_FLAG_NEW)
2259                 && !(old_flags & LIBBALSA_MESSAGE_FLAG_DELETED);
2260             is_unread_undeleted =
2261                 (msg_info->flags & LIBBALSA_MESSAGE_FLAG_NEW)
2262                 && !(msg_info->flags & LIBBALSA_MESSAGE_FLAG_DELETED);
2263             mailbox->unread_messages +=
2264                 is_unread_undeleted - was_unread_undeleted;
2265         }
2266     }
2267     libbalsa_unlock_mailbox(mailbox);
2268 
2269     if (changed > 0) {
2270         libbalsa_mailbox_set_unread_messages_flag(mailbox,
2271                                                   mailbox->unread_messages
2272                                                   > 0);
2273         lbm_local_sync_queue(local);
2274     }
2275 
2276     return TRUE;
2277 }
2278 
2279 static gboolean
2280 libbalsa_mailbox_local_msgno_has_flags(LibBalsaMailbox * mailbox,
2281                                        guint msgno,
2282                                        LibBalsaMessageFlag set,
2283                                        LibBalsaMessageFlag unset)
2284 {
2285     LibBalsaMailboxLocal *local = LIBBALSA_MAILBOX_LOCAL(mailbox);
2286     LibBalsaMailboxLocalMessageInfo *msg_info =
2287         LIBBALSA_MAILBOX_LOCAL_GET_CLASS(local)->get_info(local, msgno);
2288 
2289     return (msg_info->flags & set) == set && (msg_info->flags & unset) == 0;
2290 }
2291 
2292 static GArray *
2293 libbalsa_mailbox_local_duplicate_msgnos(LibBalsaMailbox * mailbox)
2294 {
2295     LibBalsaMailboxLocal *local = (LibBalsaMailboxLocal *) mailbox;
2296     GHashTable *table;
2297     guint i;
2298     GArray *msgnos;
2299 
2300     if (!local->threading_info)
2301         return NULL;
2302 
2303     /* We need all the message-ids. */
2304     if (!libbalsa_mailbox_prepare_threading(mailbox, 0))
2305         return NULL;
2306 
2307     table = g_hash_table_new(g_str_hash, g_str_equal);
2308     msgnos = g_array_new(FALSE, FALSE, sizeof(guint));
2309 
2310     for (i = 0; i < local->threading_info->len; i++) {
2311         LibBalsaMailboxLocalInfo *info;
2312         gpointer tmp;
2313         guint master, msgno = i + 1;
2314 
2315         if (libbalsa_mailbox_msgno_has_flags
2316             (mailbox, msgno, LIBBALSA_MESSAGE_FLAG_DELETED, 0))
2317             continue;
2318 
2319         info = g_ptr_array_index(local->threading_info, i);
2320         if (!info || !info->message_id)
2321             continue;
2322 
2323         tmp = g_hash_table_lookup(table, info->message_id);
2324         master = tmp ? GPOINTER_TO_UINT(tmp) : 0;
2325         if (!master ||
2326             libbalsa_mailbox_msgno_has_flags(mailbox, msgno,
2327                                              LIBBALSA_MESSAGE_FLAG_REPLIED,
2328                                              0)) {
2329             g_hash_table_insert(table, info->message_id,
2330                                 GUINT_TO_POINTER(msgno));
2331             msgno = master;
2332         }
2333 
2334         if (msgno)
2335             g_array_append_val(msgnos, msgno);
2336     }
2337     g_hash_table_destroy(table);
2338 
2339     return msgnos;
2340 }
2341 
2342 /* LibBalsaMailboxLocal class method: */
2343 static void
2344 lbm_local_real_remove_files(LibBalsaMailboxLocal * local)
2345 {
2346     gchar *filename = lbm_local_get_cache_filename(local);
2347     unlink(filename);
2348     g_free(filename);
2349 }
2350