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