1 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
2 /* Balsa E-Mail Client
3 * Copyright (C) 1997-2013 Stuart Parmenter and others,
4 * See the file AUTHORS for a list.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2, or (at your option)
9 * any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19 * 02111-1307, USA.
20 */
21
22 #if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
23 # include "config.h"
24 #endif /* HAVE_CONFIG_H */
25
26 #include <ctype.h>
27 #include <string.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <unistd.h>
31 #include <fcntl.h>
32
33 #include "libbalsa-marshal.h"
34 #include "libbalsa.h"
35 #include "libbalsa-conf.h"
36 #include "mailbox-filter.h"
37 #include "message.h"
38 #include "misc.h"
39 #include "filter-funcs.h"
40 #include "libbalsa_private.h"
41 #include <glib/gi18n.h>
42
43 /* Class functions */
44 static void libbalsa_mailbox_class_init(LibBalsaMailboxClass * klass);
45 static void libbalsa_mailbox_init(LibBalsaMailbox * mailbox);
46 static void libbalsa_mailbox_dispose(GObject * object);
47 static void libbalsa_mailbox_finalize(GObject * object);
48
49 static void libbalsa_mailbox_real_release_message (LibBalsaMailbox * mailbox,
50 LibBalsaMessage * message);
51 static gboolean
52 libbalsa_mailbox_real_messages_copy(LibBalsaMailbox * mailbox,
53 GArray * msgnos,
54 LibBalsaMailbox * dest, GError **err);
55 static gboolean libbalsa_mailbox_real_can_do(LibBalsaMailbox* mbox,
56 enum LibBalsaMailboxCapability c);
57 static void libbalsa_mailbox_real_sort(LibBalsaMailbox* mbox,
58 GArray *sort_array);
59 static gboolean libbalsa_mailbox_real_can_match(LibBalsaMailbox *mailbox,
60 LibBalsaCondition *condition);
61 static void libbalsa_mailbox_real_save_config(LibBalsaMailbox * mailbox,
62 const gchar * group);
63 static void libbalsa_mailbox_real_load_config(LibBalsaMailbox * mailbox,
64 const gchar * group);
65 static gboolean libbalsa_mailbox_real_close_backend (LibBalsaMailbox *
66 mailbox);
67 #ifdef BALSA_USE_THREADS
68 static void libbalsa_mailbox_real_lock_store(LibBalsaMailbox * mailbox,
69 gboolean lock);
70 #endif /* BALSA_USE_THREADS */
71
72 /* SIGNALS MEANINGS :
73 - CHANGED: notification signal sent by the mailbox to allow the
74 frontend to keep in sync. This signal is used when messages are added
75 to or removed from the mailbox. This is used when eg the mailbox
76 loads new messages (check new mails) or the mailbox is expunged.
77 Also when the unread message count might have changed.
78 - MESSAGE_EXPUNGED: sent when a message is expunged. This signal is
79 used to update lists of msgnos when messages are renumbered.
80 */
81
82 enum {
83 CHANGED,
84 MESSAGE_EXPUNGED,
85 PROGRESS_NOTIFY,
86 LAST_SIGNAL
87 };
88
89 enum {
90 ROW_CHANGED,
91 ROW_DELETED,
92 ROW_HAS_CHILD_TOGGLED,
93 ROW_INSERTED,
94 ROWS_REORDERED,
95 LAST_MODEL_SIGNAL
96 };
97
98 static GObjectClass *parent_class = NULL;
99 static guint libbalsa_mailbox_signals[LAST_SIGNAL];
100 static guint libbalsa_mbox_model_signals[LAST_MODEL_SIGNAL];
101
102 /* GtkTreeModel function prototypes */
103 static void mbox_model_init(GtkTreeModelIface *iface);
104
105 /* GtkTreeDragSource function prototypes */
106 static void mbox_drag_source_init(GtkTreeDragSourceIface *iface);
107
108 /* GtkTreeSortable function prototypes */
109 static void mbox_sortable_init(GtkTreeSortableIface *iface);
110
111 GType
libbalsa_mailbox_get_type(void)112 libbalsa_mailbox_get_type(void)
113 {
114 static GType mailbox_type = 0;
115
116 if (!mailbox_type) {
117 static const GTypeInfo mailbox_info = {
118 sizeof(LibBalsaMailboxClass),
119 NULL, /* base_init */
120 NULL, /* base_finalize */
121 (GClassInitFunc) libbalsa_mailbox_class_init,
122 NULL, /* class_finalize */
123 NULL, /* class_data */
124 sizeof(LibBalsaMailbox),
125 0, /* n_preallocs */
126 (GInstanceInitFunc) libbalsa_mailbox_init
127 };
128
129 static const GInterfaceInfo mbox_model_info = {
130 (GInterfaceInitFunc) mbox_model_init,
131 NULL,
132 NULL
133 };
134
135 static const GInterfaceInfo mbox_drag_source_info = {
136 (GInterfaceInitFunc) mbox_drag_source_init,
137 NULL,
138 NULL
139 };
140
141 static const GInterfaceInfo mbox_sortable_info = {
142 (GInterfaceInitFunc) mbox_sortable_init,
143 NULL,
144 NULL
145 };
146
147 mailbox_type =
148 g_type_register_static(G_TYPE_OBJECT, "LibBalsaMailbox",
149 &mailbox_info, 0);
150 g_type_add_interface_static(mailbox_type,
151 GTK_TYPE_TREE_MODEL,
152 &mbox_model_info);
153 g_type_add_interface_static(mailbox_type,
154 GTK_TYPE_TREE_DRAG_SOURCE,
155 &mbox_drag_source_info);
156 g_type_add_interface_static(mailbox_type,
157 GTK_TYPE_TREE_SORTABLE,
158 &mbox_sortable_info);
159 }
160
161 return mailbox_type;
162 }
163
164 static void
libbalsa_mailbox_class_init(LibBalsaMailboxClass * klass)165 libbalsa_mailbox_class_init(LibBalsaMailboxClass * klass)
166 {
167 GObjectClass *object_class = G_OBJECT_CLASS(klass);
168
169 parent_class = g_type_class_peek_parent(klass);
170
171 /* This signal is emitted by the mailbox when new messages are
172 retrieved (check mail or opening of the mailbox). This is used
173 by GUI to sync on the mailbox content (see BalsaIndex)
174 */
175 libbalsa_mailbox_signals[CHANGED] =
176 g_signal_new("changed",
177 G_TYPE_FROM_CLASS(object_class),
178 G_SIGNAL_RUN_FIRST,
179 G_STRUCT_OFFSET(LibBalsaMailboxClass,
180 changed),
181 NULL, NULL,
182 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
183
184 libbalsa_mailbox_signals[MESSAGE_EXPUNGED] =
185 g_signal_new("message-expunged",
186 G_TYPE_FROM_CLASS(object_class),
187 G_SIGNAL_RUN_FIRST,
188 G_STRUCT_OFFSET(LibBalsaMailboxClass,
189 message_expunged),
190 NULL, NULL,
191 g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1,
192 G_TYPE_INT);
193
194 libbalsa_mailbox_signals[PROGRESS_NOTIFY] =
195 g_signal_new("progress-notify",
196 G_TYPE_FROM_CLASS(object_class),
197 G_SIGNAL_RUN_FIRST,
198 G_STRUCT_OFFSET(LibBalsaMailboxClass,
199 progress_notify),
200 NULL, NULL,
201 libbalsa_VOID__INT_INT_INT_STRING, G_TYPE_NONE,
202 4, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING);
203
204 object_class->dispose = libbalsa_mailbox_dispose;
205 object_class->finalize = libbalsa_mailbox_finalize;
206
207 /* Signals */
208 klass->progress_notify = NULL;
209 klass->changed = NULL;
210 klass->message_expunged = NULL;
211
212 /* Virtual functions */
213 klass->open_mailbox = NULL;
214 klass->close_mailbox = NULL;
215 klass->get_message = NULL;
216 klass->prepare_threading = NULL;
217 klass->fetch_message_structure = NULL;
218 klass->fetch_headers = NULL;
219 klass->release_message = libbalsa_mailbox_real_release_message;
220 klass->get_message_part = NULL;
221 klass->get_message_stream = NULL;
222 klass->messages_change_flags = NULL;
223 klass->messages_copy = libbalsa_mailbox_real_messages_copy;
224 klass->can_do = libbalsa_mailbox_real_can_do;
225 klass->set_threading = NULL;
226 klass->update_view_filter = NULL;
227 klass->sort = libbalsa_mailbox_real_sort;
228 klass->check = NULL;
229 klass->message_match = NULL;
230 klass->can_match = libbalsa_mailbox_real_can_match;
231 klass->save_config = libbalsa_mailbox_real_save_config;
232 klass->load_config = libbalsa_mailbox_real_load_config;
233 klass->close_backend = libbalsa_mailbox_real_close_backend;
234 klass->total_messages = NULL;
235 klass->duplicate_msgnos = NULL;
236 #ifdef BALSA_USE_THREADS
237 klass->lock_store = libbalsa_mailbox_real_lock_store;
238 #endif /* BALSA_USE_THREADS */
239 }
240
241 static void
libbalsa_mailbox_init(LibBalsaMailbox * mailbox)242 libbalsa_mailbox_init(LibBalsaMailbox * mailbox)
243 {
244 mailbox->lock = FALSE;
245 mailbox->is_directory = FALSE;
246
247 mailbox->config_prefix = NULL;
248 mailbox->name = NULL;
249 mailbox->url = NULL;
250
251 mailbox->open_ref = 0;
252 mailbox->has_unread_messages = FALSE;
253 mailbox->unread_messages = 0;
254
255 mailbox->readonly = FALSE;
256 mailbox->disconnected = FALSE;
257
258 mailbox->filters=NULL;
259 mailbox->filters_loaded = FALSE;
260 mailbox->view=NULL;
261 /* mailbox->stamp is incremented before we use it, so it won't be
262 * zero for a long, long time... */
263 mailbox->stamp = g_random_int() / 2;
264
265 mailbox->no_reassemble = FALSE;
266 }
267
268 /*
269 * libbalsa_mailbox_dispose:
270 *
271 * called just before finalize, when ref_count is about to be
272 * decremented to 0
273 */
274 static void
libbalsa_mailbox_dispose(GObject * object)275 libbalsa_mailbox_dispose(GObject * object)
276 {
277 LibBalsaMailbox *mailbox = LIBBALSA_MAILBOX(object);
278
279 while (mailbox->open_ref > 0)
280 libbalsa_mailbox_close(mailbox, FALSE);
281
282 G_OBJECT_CLASS(parent_class)->dispose(object);
283 }
284
285
286 static gchar*
get_from_field(LibBalsaMessage * message)287 get_from_field(LibBalsaMessage *message)
288 {
289 InternetAddressList *address_list = NULL;
290 const gchar *name_str = NULL;
291 gboolean append_dots = FALSE;
292 gchar *from;
293
294 g_return_val_if_fail(message->mailbox, NULL);
295
296 if (message->headers) {
297 if (message->mailbox->view &&
298 message->mailbox->view->show == LB_MAILBOX_SHOW_TO)
299 address_list = message->headers->to_list;
300 else
301 address_list = message->headers->from;
302 }
303
304 if (address_list) {
305 gint i, len = internet_address_list_length(address_list);
306
307 for (i = 0; i < len && name_str == NULL; i++) {
308 InternetAddress *ia =
309 internet_address_list_get_address(address_list, i);
310 if (ia->name && *ia->name) {
311 name_str = ia->name;
312 if (i < len - 1)
313 append_dots = TRUE;
314 } else if (INTERNET_ADDRESS_IS_MAILBOX(ia)) {
315 name_str = ((InternetAddressMailbox *) ia)->addr;
316 if (i < len - 1)
317 append_dots = TRUE;
318 } else {
319 InternetAddressGroup *g = (InternetAddressGroup *) ia;
320 gint gi, glen =
321 internet_address_list_length(g->members);
322 for (gi = 0; gi < glen && name_str == NULL; gi++) {
323 InternetAddress *ia2 =
324 internet_address_list_get_address(g->members, gi);
325 if (ia2->name && *ia2->name) {
326 name_str = ia2->name;
327 if (gi < glen - 1)
328 append_dots = TRUE;
329 } else if (INTERNET_ADDRESS_IS_MAILBOX(ia2)) {
330 name_str = ((InternetAddressMailbox *) ia2)->addr;
331 if (gi < glen - 1)
332 append_dots = TRUE;
333 }
334 }
335 }
336 }
337 }
338
339 if (name_str == NULL)
340 name_str = "";
341 from = append_dots ? g_strconcat(name_str, ",...", NULL)
342 : g_strdup(name_str);
343 libbalsa_utf8_sanitize(&from, TRUE, NULL);
344
345 return from;
346 }
347
348 static void
lbm_index_entry_populate_from_msg(LibBalsaMailboxIndexEntry * entry,LibBalsaMessage * msg)349 lbm_index_entry_populate_from_msg(LibBalsaMailboxIndexEntry * entry,
350 LibBalsaMessage * msg)
351 {
352 entry->from = get_from_field(msg);
353 entry->subject = g_strdup(LIBBALSA_MESSAGE_GET_SUBJECT(msg));
354 entry->msg_date = msg->headers->date;
355 entry->internal_date = 0; /* FIXME */
356 entry->status_icon = libbalsa_get_icon_from_flags(msg->flags);
357 entry->attach_icon = libbalsa_message_get_attach_icon(msg);
358 entry->size = msg->length;
359 entry->foreground = NULL;
360 entry->background = NULL;
361 entry->foreground_set = 0;
362 entry->background_set = 0;
363 entry->unseen = LIBBALSA_MESSAGE_IS_UNREAD(msg);
364 #ifdef BALSA_USE_THREADS
365 entry->idle_pending = 0;
366 #endif /*BALSA_USE_THREADS */
367 libbalsa_mailbox_msgno_changed(msg->mailbox, msg->msgno);
368 }
369
370 #ifdef BALSA_USE_THREADS
371 static LibBalsaMailboxIndexEntry*
lbm_index_entry_new_pending(void)372 lbm_index_entry_new_pending(void)
373 {
374 LibBalsaMailboxIndexEntry *entry = g_new(LibBalsaMailboxIndexEntry,1);
375 entry->idle_pending = 1;
376 return entry;
377 }
378 #endif /*BALSA_USE_THREADS */
379
380 static void
lbm_index_entry_free(LibBalsaMailboxIndexEntry * entry)381 lbm_index_entry_free(LibBalsaMailboxIndexEntry *entry)
382 {
383 if(entry) {
384 #ifdef BALSA_USE_THREADS
385 if (!entry->idle_pending)
386 #endif /*BALSA_USE_THREADS */
387 {
388 g_free(entry->from);
389 g_free(entry->subject);
390 }
391 g_free(entry);
392 }
393 }
394
395 void
libbalsa_mailbox_index_entry_clear(LibBalsaMailbox * mailbox,guint msgno)396 libbalsa_mailbox_index_entry_clear(LibBalsaMailbox * mailbox, guint msgno)
397 {
398 g_return_if_fail(LIBBALSA_IS_MAILBOX(mailbox));
399 g_return_if_fail(msgno > 0);
400
401 if (msgno <= mailbox->mindex->len) {
402 LibBalsaMailboxIndexEntry **entry = (LibBalsaMailboxIndexEntry **)
403 & g_ptr_array_index(mailbox->mindex, msgno - 1);
404 lbm_index_entry_free(*entry);
405 *entry = NULL;
406
407 libbalsa_mailbox_msgno_changed(mailbox, msgno);
408 }
409 }
410
411 #ifdef BALSA_USE_THREADS
412 # define VALID_ENTRY(entry) \
413 ((entry) && !((LibBalsaMailboxIndexEntry *) (entry))->idle_pending)
414 #else /*BALSA_USE_THREADS */
415 # define VALID_ENTRY(entry) ((entry) != NULL)
416 #endif /*BALSA_USE_THREADS */
417
418 void
libbalsa_mailbox_index_set_flags(LibBalsaMailbox * mailbox,unsigned msgno,LibBalsaMessageFlag f)419 libbalsa_mailbox_index_set_flags(LibBalsaMailbox *mailbox,
420 unsigned msgno, LibBalsaMessageFlag f)
421 {
422 LibBalsaMailboxIndexEntry *entry;
423
424 if (msgno > mailbox->mindex->len)
425 return;
426
427 entry = g_ptr_array_index(mailbox->mindex, msgno-1);
428 if (VALID_ENTRY(entry)) {
429 entry->status_icon =
430 libbalsa_get_icon_from_flags(f);
431 entry->unseen = f & LIBBALSA_MESSAGE_FLAG_NEW;
432 libbalsa_mailbox_msgno_changed(mailbox, msgno);
433 }
434 }
435
436 /* libbalsa_mailbox_finalize:
437 destroys mailbox. Must leave it in sane state.
438 */
439
440 #ifdef BALSA_USE_THREADS
441 static void lbm_msgno_changed_expunged_cb(LibBalsaMailbox * mailbox,
442 guint seqno);
443 static void lbm_get_index_entry_expunged_cb(LibBalsaMailbox * mailbox,
444 guint seqno);
445 #endif
446
447 static void
libbalsa_mailbox_finalize(GObject * object)448 libbalsa_mailbox_finalize(GObject * object)
449 {
450 LibBalsaMailbox *mailbox;
451
452 g_return_if_fail(object != NULL);
453
454 mailbox = LIBBALSA_MAILBOX(object);
455
456 g_free(mailbox->config_prefix);
457 mailbox->config_prefix = NULL;
458
459 g_free(mailbox->name);
460 mailbox->name = NULL;
461
462 g_free(mailbox->url);
463 mailbox->url = NULL;
464
465 libbalsa_condition_unref(mailbox->view_filter);
466 mailbox->view_filter = NULL;
467
468 libbalsa_condition_unref(mailbox->persistent_view_filter);
469 mailbox->persistent_view_filter = NULL;
470
471 g_slist_foreach(mailbox->filters, (GFunc) g_free, NULL);
472 g_slist_free(mailbox->filters);
473 mailbox->filters = NULL;
474 mailbox->filters_loaded = FALSE;
475
476 #ifdef BALSA_USE_THREADS
477 if (mailbox->msgnos_pending) {
478 g_signal_handlers_disconnect_by_func(mailbox,
479 lbm_get_index_entry_expunged_cb,
480 mailbox->msgnos_pending);
481 g_array_free(mailbox->msgnos_pending, TRUE);
482 mailbox->msgnos_pending = NULL;
483 }
484
485 if (mailbox->msgnos_changed) {
486 g_signal_handlers_disconnect_by_func(mailbox,
487 lbm_msgno_changed_expunged_cb,
488 mailbox->msgnos_changed);
489 g_array_free(mailbox->msgnos_changed, TRUE);
490 mailbox->msgnos_changed = NULL;
491 }
492 #endif /*BALSA_USE_THREADS */
493
494 libbalsa_mailbox_view_free(mailbox->view);
495 mailbox->view = NULL;
496
497 if (mailbox->changed_idle_id) {
498 g_source_remove(mailbox->changed_idle_id);
499 mailbox->changed_idle_id = 0;
500 }
501
502 if (mailbox->queue_check_idle_id) {
503 g_source_remove(mailbox->queue_check_idle_id);
504 mailbox->queue_check_idle_id = 0;
505 }
506
507 G_OBJECT_CLASS(parent_class)->finalize(object);
508 }
509
510 /* Create a new mailbox by loading it from a config entry... */
511 LibBalsaMailbox *
libbalsa_mailbox_new_from_config(const gchar * group)512 libbalsa_mailbox_new_from_config(const gchar * group)
513 {
514 gchar *type_str;
515 GType type;
516 gboolean got_default;
517 LibBalsaMailbox *mailbox;
518
519 libbalsa_conf_push_group(group);
520 type_str = libbalsa_conf_get_string_with_default("Type", &got_default);
521
522 if (got_default) {
523 libbalsa_information(LIBBALSA_INFORMATION_WARNING,
524 _("Cannot load mailbox %s"), group);
525 libbalsa_conf_pop_group();
526 return NULL;
527 }
528 type = g_type_from_name(type_str);
529 if (type == 0) {
530 libbalsa_information(LIBBALSA_INFORMATION_WARNING,
531 _("No such mailbox type: %s"), type_str);
532 g_free(type_str);
533 libbalsa_conf_pop_group();
534 return NULL;
535 }
536
537 /* Handle Local mailboxes.
538 * They are now separate classes for each type
539 * FIXME: This should be removed in som efuture release.
540 */
541 if ( type == LIBBALSA_TYPE_MAILBOX_LOCAL ) {
542 gchar *path = libbalsa_conf_get_string("Path");
543 type = libbalsa_mailbox_type_from_path(path);
544 if (type != G_TYPE_OBJECT)
545 libbalsa_conf_set_string("Type", g_type_name(type));
546 else
547 libbalsa_information(LIBBALSA_INFORMATION_WARNING,
548 _("Bad local mailbox path \"%s\""), path);
549 }
550 mailbox = (type != G_TYPE_OBJECT ? g_object_new(type, NULL) : NULL);
551 if (mailbox == NULL)
552 libbalsa_information(LIBBALSA_INFORMATION_WARNING,
553 _("Could not create a mailbox of type %s"),
554 type_str);
555 else
556 LIBBALSA_MAILBOX_GET_CLASS(mailbox)->load_config(mailbox, group);
557
558 libbalsa_conf_pop_group();
559 g_free(type_str);
560
561 return mailbox;
562 }
563
564 static void
libbalsa_mailbox_free_mindex(LibBalsaMailbox * mailbox)565 libbalsa_mailbox_free_mindex(LibBalsaMailbox *mailbox)
566 {
567 if(mailbox->mindex) {
568 unsigned i;
569 /* we could have used g_ptr_array_foreach but it is >=2.4.0 */
570 for(i=0; i<mailbox->mindex->len; i++)
571 lbm_index_entry_free(g_ptr_array_index(mailbox->mindex, i));
572 g_ptr_array_free(mailbox->mindex, TRUE);
573 mailbox->mindex = NULL;
574 }
575 }
576
577 static gboolean lbm_set_threading(LibBalsaMailbox * mailbox,
578 LibBalsaMailboxThreadingType
579 thread_type);
580 gboolean
libbalsa_mailbox_open(LibBalsaMailbox * mailbox,GError ** err)581 libbalsa_mailbox_open(LibBalsaMailbox * mailbox, GError **err)
582 {
583 gboolean retval;
584
585 g_return_val_if_fail(mailbox != NULL, FALSE);
586 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mailbox), FALSE);
587
588 libbalsa_lock_mailbox(mailbox);
589
590 if (mailbox->open_ref > 0) {
591 mailbox->open_ref++;
592 libbalsa_mailbox_check(mailbox);
593 retval = TRUE;
594 } else {
595 LibBalsaMailboxState saved_state;
596
597 mailbox->stamp++;
598 if(mailbox->mindex) g_warning("mindex set - I leak memory");
599 mailbox->mindex = g_ptr_array_new();
600
601 saved_state = mailbox->state;
602 mailbox->state = LB_MAILBOX_STATE_OPENING;
603 retval =
604 LIBBALSA_MAILBOX_GET_CLASS(mailbox)->open_mailbox(mailbox, err);
605 if(retval) {
606 mailbox->open_ref++;
607 mailbox->state = LB_MAILBOX_STATE_OPEN;
608 } else {
609 mailbox->state = saved_state;
610 libbalsa_mailbox_free_mindex(mailbox);
611 }
612 }
613
614 libbalsa_unlock_mailbox(mailbox);
615
616 return retval;
617 }
618
619 /* libbalsa_mailbox_is_valid:
620 mailbox is valid when:
621 a). it is closed, b). it is open and has proper client context.
622 */
623 gboolean
libbalsa_mailbox_is_valid(LibBalsaMailbox * mailbox)624 libbalsa_mailbox_is_valid(LibBalsaMailbox * mailbox)
625 {
626 if(mailbox->open_ref == 0) return TRUE;
627 if(MAILBOX_CLOSED(mailbox)) return FALSE;
628 return TRUE;
629 }
630
631 gboolean
libbalsa_mailbox_is_open(LibBalsaMailbox * mailbox)632 libbalsa_mailbox_is_open(LibBalsaMailbox *mailbox)
633 {
634 g_return_val_if_fail(mailbox != NULL, FALSE);
635 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mailbox), FALSE);
636
637
638 return mailbox->open_ref>0; /* this will break unlisted mailbox types */
639 }
640
641 void
libbalsa_mailbox_close(LibBalsaMailbox * mailbox,gboolean expunge)642 libbalsa_mailbox_close(LibBalsaMailbox * mailbox, gboolean expunge)
643 {
644 g_return_if_fail(mailbox != NULL);
645 g_return_if_fail(LIBBALSA_IS_MAILBOX(mailbox));
646 g_return_if_fail(MAILBOX_OPEN(mailbox));
647
648 g_object_ref(mailbox);
649 libbalsa_lock_mailbox(mailbox);
650
651 if (--mailbox->open_ref == 0) {
652 mailbox->state = LB_MAILBOX_STATE_CLOSING;
653 /* do not try expunging read-only mailboxes, it's a waste of time */
654 expunge = expunge && !mailbox->readonly;
655 LIBBALSA_MAILBOX_GET_CLASS(mailbox)->close_mailbox(mailbox, expunge);
656 gdk_threads_enter();
657 if(mailbox->msg_tree) {
658 g_node_destroy(mailbox->msg_tree);
659 mailbox->msg_tree = NULL;
660 }
661 gdk_threads_leave();
662 libbalsa_mailbox_free_mindex(mailbox);
663 mailbox->stamp++;
664 mailbox->state = LB_MAILBOX_STATE_CLOSED;
665 }
666
667 libbalsa_unlock_mailbox(mailbox);
668 g_object_unref(mailbox);
669 }
670
671 void
libbalsa_mailbox_set_unread_messages_flag(LibBalsaMailbox * mailbox,gboolean has_unread)672 libbalsa_mailbox_set_unread_messages_flag(LibBalsaMailbox * mailbox,
673 gboolean has_unread)
674 {
675 g_return_if_fail(mailbox != NULL);
676 g_return_if_fail(LIBBALSA_IS_MAILBOX(mailbox));
677
678 mailbox->has_unread_messages = (has_unread != FALSE);
679 libbalsa_mailbox_changed(mailbox);
680 }
681
682 /* libbalsa_mailbox_progress_notify:
683 there has been a progress in current operation.
684 */
685 void
libbalsa_mailbox_progress_notify(LibBalsaMailbox * mailbox,int type,int prog,int tot,const gchar * msg)686 libbalsa_mailbox_progress_notify(LibBalsaMailbox * mailbox,
687 int type, int prog, int tot, const gchar* msg)
688 {
689 g_return_if_fail(mailbox != NULL);
690 g_return_if_fail(LIBBALSA_IS_MAILBOX(mailbox));
691
692 /* OK to emit in a subthread, because the handler expects it. */
693 g_signal_emit(G_OBJECT(mailbox),
694 libbalsa_mailbox_signals[PROGRESS_NOTIFY],
695 0, type, prog, tot, msg);
696 }
697
698 void
libbalsa_mailbox_check(LibBalsaMailbox * mailbox)699 libbalsa_mailbox_check(LibBalsaMailbox * mailbox)
700 {
701 GSList *unthreaded;
702
703 g_return_if_fail(mailbox != NULL);
704 g_return_if_fail(LIBBALSA_IS_MAILBOX(mailbox));
705
706 libbalsa_lock_mailbox(mailbox);
707
708 if (mailbox->queue_check_idle_id) {
709 /* Remove scheduled idle callback. */
710 g_source_remove(mailbox->queue_check_idle_id);
711 mailbox->queue_check_idle_id = 0;
712 }
713
714 unthreaded = NULL;
715 if (MAILBOX_OPEN(mailbox))
716 g_object_set_data(G_OBJECT(mailbox), LIBBALSA_MAILBOX_UNTHREADED,
717 &unthreaded);
718 LIBBALSA_MAILBOX_GET_CLASS(mailbox)->check(mailbox);
719 g_object_set_data(G_OBJECT(mailbox), LIBBALSA_MAILBOX_UNTHREADED,
720 unthreaded);
721 if (unthreaded) {
722 lbm_set_threading(mailbox, mailbox->view->threading_type);
723 g_slist_free(unthreaded);
724 g_object_set_data(G_OBJECT(mailbox), LIBBALSA_MAILBOX_UNTHREADED,
725 NULL);
726 }
727
728 libbalsa_unlock_mailbox(mailbox);
729
730 #ifdef BALSA_USE_THREADS
731 pthread_testcancel();
732 #endif
733 }
734
735 static gboolean
lbm_changed_idle_cb(LibBalsaMailbox * mailbox)736 lbm_changed_idle_cb(LibBalsaMailbox * mailbox)
737 {
738 libbalsa_lock_mailbox(mailbox);
739 g_signal_emit(mailbox, libbalsa_mailbox_signals[CHANGED], 0);
740 mailbox->changed_idle_id = 0;
741 libbalsa_unlock_mailbox(mailbox);
742 return FALSE;
743 }
744
745 static void
lbm_changed_schedule_idle(LibBalsaMailbox * mailbox)746 lbm_changed_schedule_idle(LibBalsaMailbox * mailbox)
747 {
748 libbalsa_lock_mailbox(mailbox);
749 if (!mailbox->changed_idle_id)
750 mailbox->changed_idle_id =
751 g_idle_add((GSourceFunc) lbm_changed_idle_cb, mailbox);
752 libbalsa_unlock_mailbox(mailbox);
753 }
754
755 void
libbalsa_mailbox_changed(LibBalsaMailbox * mailbox)756 libbalsa_mailbox_changed(LibBalsaMailbox * mailbox)
757 {
758 libbalsa_lock_mailbox(mailbox);
759 if (!g_signal_has_handler_pending
760 (mailbox, libbalsa_mailbox_signals[CHANGED], 0, TRUE)) {
761 /* No one cares, so don't set any message counts--that might
762 * cause mailbox->view to be created. */
763 libbalsa_unlock_mailbox(mailbox);
764 return;
765 }
766
767 if (MAILBOX_OPEN(mailbox)) {
768 /* Both counts are valid. */
769 libbalsa_mailbox_set_total(mailbox,
770 libbalsa_mailbox_total_messages
771 (mailbox));
772 libbalsa_mailbox_set_unread(mailbox, mailbox->unread_messages);
773 } else if (mailbox->has_unread_messages
774 && libbalsa_mailbox_get_unread(mailbox) <= 0) {
775 /* Mail has arrived in a closed mailbox since our last check;
776 * total is unknown, but mailbox->has_unread_messages is valid. */
777 libbalsa_mailbox_set_total(mailbox, -1);
778 libbalsa_mailbox_set_unread(mailbox, 1);
779 }
780
781 lbm_changed_schedule_idle(mailbox);
782 libbalsa_unlock_mailbox(mailbox);
783 }
784
785 /* libbalsa_mailbox_message_match:
786 * Tests if message with msgno matches the conditions cached in the
787 * search_iter: this is used
788 by the search code. It is a "virtual method", indeed IMAP has a
789 special way to implement it for speed/bandwidth reasons
790 */
791 gboolean
libbalsa_mailbox_message_match(LibBalsaMailbox * mailbox,guint msgno,LibBalsaMailboxSearchIter * search_iter)792 libbalsa_mailbox_message_match(LibBalsaMailbox * mailbox,
793 guint msgno,
794 LibBalsaMailboxSearchIter * search_iter)
795 {
796 gboolean match;
797
798 g_return_val_if_fail(mailbox != NULL, FALSE);
799 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mailbox), FALSE);
800 g_return_val_if_fail(msgno <= libbalsa_mailbox_total_messages(mailbox),
801 FALSE);
802
803 if (libbalsa_condition_is_flag_only(search_iter->condition,
804 mailbox, msgno, &match))
805 return match;
806
807 return LIBBALSA_MAILBOX_GET_CLASS(mailbox)->message_match(mailbox,
808 msgno,
809 search_iter);
810 }
811
libbalsa_mailbox_real_can_match(LibBalsaMailbox * mailbox,LibBalsaCondition * condition)812 gboolean libbalsa_mailbox_real_can_match(LibBalsaMailbox *mailbox,
813 LibBalsaCondition *condition)
814 {
815 /* By default : all filters is OK */
816 return TRUE;
817 }
818
819 gboolean
libbalsa_mailbox_can_match(LibBalsaMailbox * mailbox,LibBalsaCondition * condition)820 libbalsa_mailbox_can_match(LibBalsaMailbox * mailbox,
821 LibBalsaCondition *condition)
822 {
823 g_return_val_if_fail(mailbox != NULL, FALSE);
824 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mailbox), FALSE);
825
826 return LIBBALSA_MAILBOX_GET_CLASS(mailbox)->can_match(mailbox,
827 condition);
828 }
829
830 /* Helper function to run the "on reception" filters on a mailbox */
831
832 static gboolean
lbm_run_filters_on_reception_idle_cb(LibBalsaMailbox * mailbox)833 lbm_run_filters_on_reception_idle_cb(LibBalsaMailbox * mailbox)
834 {
835 GSList *filters;
836 guint progress_count;
837 GSList *lst;
838 static LibBalsaCondition *recent_undeleted;
839 gchar *text;
840 guint total;
841 guint progress_total;
842 LibBalsaProgress progress;
843
844 g_object_add_weak_pointer(G_OBJECT(mailbox), (gpointer) &mailbox);
845 g_object_unref(mailbox);
846 if (!mailbox)
847 return FALSE;
848 g_object_remove_weak_pointer(G_OBJECT(mailbox), (gpointer) &mailbox);
849
850 if (!mailbox->filters_loaded) {
851 config_mailbox_filters_load(mailbox);
852 mailbox->filters_loaded = TRUE;
853 }
854
855 filters = libbalsa_mailbox_filters_when(mailbox->filters,
856 FILTER_WHEN_INCOMING);
857
858 if (!filters)
859 return FALSE;
860 if (!filters_prepare_to_run(filters)) {
861 g_slist_free(filters);
862 return FALSE;
863 }
864
865 progress_count = 0;
866 for (lst = filters; lst; lst = lst->next) {
867 LibBalsaFilter *filter = lst->data;
868
869 if (filter->condition
870 && !libbalsa_condition_is_flag_only(filter->condition, NULL, 0,
871 NULL))
872 ++progress_count;
873 }
874
875 libbalsa_lock_mailbox(mailbox);
876 if (!recent_undeleted)
877 recent_undeleted =
878 libbalsa_condition_new_bool_ptr(FALSE, CONDITION_AND,
879 libbalsa_condition_new_flag_enum
880 (FALSE,
881 LIBBALSA_MESSAGE_FLAG_RECENT),
882 libbalsa_condition_new_flag_enum
883 (TRUE,
884 LIBBALSA_MESSAGE_FLAG_DELETED));
885
886 text = g_strdup_printf(_("Applying filter rules to %s"), mailbox->name);
887 total = libbalsa_mailbox_total_messages(mailbox);
888 progress_total = progress_count * total;
889 libbalsa_progress_set_text(&progress, text, progress_total);
890 g_free(text);
891
892 progress_count = 0;
893 for (lst = filters; lst; lst = lst->next) {
894 LibBalsaFilter *filter = lst->data;
895 gboolean use_progress;
896 LibBalsaCondition *cond;
897 LibBalsaMailboxSearchIter *search_iter;
898 guint msgno;
899 GArray *msgnos;
900
901 if (!filter->condition)
902 continue;
903
904 use_progress = !libbalsa_condition_is_flag_only(filter->condition,
905 NULL, 0, NULL);
906
907 cond = libbalsa_condition_new_bool_ptr(FALSE, CONDITION_AND,
908 recent_undeleted,
909 filter->condition);
910 search_iter = libbalsa_mailbox_search_iter_new(cond);
911 libbalsa_condition_unref(cond);
912
913 msgnos = g_array_new(FALSE, FALSE, sizeof(guint));
914 for (msgno = 1; msgno <= total; msgno++) {
915 if (libbalsa_mailbox_message_match(mailbox, msgno, search_iter))
916 g_array_append_val(msgnos, msgno);
917 if (use_progress)
918 libbalsa_progress_set_fraction(&progress,
919 ((gdouble) ++progress_count)
920 /
921 ((gdouble) progress_total));
922 }
923 libbalsa_mailbox_search_iter_unref(search_iter);
924
925 libbalsa_mailbox_register_msgnos(mailbox, msgnos);
926 libbalsa_filter_mailbox_messages(filter, mailbox, msgnos);
927 libbalsa_mailbox_unregister_msgnos(mailbox, msgnos);
928 g_array_free(msgnos, TRUE);
929 }
930 libbalsa_progress_set_text(&progress, NULL, 0);
931 libbalsa_unlock_mailbox(mailbox);
932
933 g_slist_free(filters);
934 return FALSE;
935 }
936
937 void
libbalsa_mailbox_run_filters_on_reception(LibBalsaMailbox * mailbox)938 libbalsa_mailbox_run_filters_on_reception(LibBalsaMailbox * mailbox)
939 {
940 g_return_if_fail(LIBBALSA_IS_MAILBOX(mailbox));
941
942 g_idle_add((GSourceFunc) lbm_run_filters_on_reception_idle_cb,
943 g_object_ref(mailbox));
944 }
945
946 void
libbalsa_mailbox_save_config(LibBalsaMailbox * mailbox,const gchar * group)947 libbalsa_mailbox_save_config(LibBalsaMailbox * mailbox,
948 const gchar * group)
949 {
950 g_return_if_fail(mailbox != NULL);
951 g_return_if_fail(LIBBALSA_IS_MAILBOX(mailbox));
952
953 /* These are incase this section was used for another
954 * type of mailbox that has now been deleted...
955 */
956 g_free(mailbox->config_prefix);
957 mailbox->config_prefix = g_strdup(group);
958 libbalsa_conf_private_remove_group(group);
959 libbalsa_conf_remove_group(group);
960
961 libbalsa_conf_push_group(group);
962 LIBBALSA_MAILBOX_GET_CLASS(mailbox)->save_config(mailbox, group);
963 libbalsa_conf_pop_group();
964 }
965
966 static void
libbalsa_mailbox_real_release_message(LibBalsaMailbox * mailbox,LibBalsaMessage * message)967 libbalsa_mailbox_real_release_message(LibBalsaMailbox * mailbox,
968 LibBalsaMessage * message)
969 {
970 if (message->mime_msg) {
971 g_object_unref(message->mime_msg);
972 message->mime_msg = NULL;
973 }
974 }
975
976 struct MsgCopyData {
977 LibBalsaMailbox *src_mailbox;
978 GArray *msgnos;
979 GMimeStream *stream;
980 guint current_idx;
981 guint copied_cnt;
982 LibBalsaProgress progress;
983 };
984
985 static gboolean
copy_iterator(LibBalsaMessageFlag * flags,GMimeStream ** stream,void * arg)986 copy_iterator(LibBalsaMessageFlag *flags, GMimeStream **stream, void * arg)
987 {
988 struct MsgCopyData *mcd = (struct MsgCopyData*)arg;
989 guint msgno;
990 gboolean (*msgno_has_flags)(LibBalsaMailbox *, guint,
991 LibBalsaMessageFlag, LibBalsaMessageFlag);
992 LibBalsaMailbox *mailbox = mcd->src_mailbox;
993
994 if(mcd->current_idx >= mcd->msgnos->len)
995 return FALSE; /* no more messages */
996
997 if(mcd->stream) {
998 g_object_unref(mcd->stream);
999 mcd->stream = NULL;
1000 }
1001 msgno_has_flags = LIBBALSA_MAILBOX_GET_CLASS(mailbox)->msgno_has_flags;
1002 msgno = g_array_index(mcd->msgnos, guint, mcd->current_idx);
1003
1004 libbalsa_progress_set_fraction(&mcd->progress,
1005 ((gdouble) (mcd->current_idx + 1)) /
1006 ((gdouble) mcd->msgnos->len));
1007 mcd->current_idx++;
1008
1009 *flags = 0;
1010 /* Copy flags. */
1011 if (msgno_has_flags(mailbox, msgno, LIBBALSA_MESSAGE_FLAG_NEW, 0))
1012 *flags |= LIBBALSA_MESSAGE_FLAG_NEW;
1013 if (msgno_has_flags
1014 (mailbox, msgno, LIBBALSA_MESSAGE_FLAG_REPLIED, 0))
1015 *flags |= LIBBALSA_MESSAGE_FLAG_REPLIED;
1016 if (msgno_has_flags
1017 (mailbox, msgno, LIBBALSA_MESSAGE_FLAG_FLAGGED, 0))
1018 *flags |= LIBBALSA_MESSAGE_FLAG_FLAGGED;
1019 if (msgno_has_flags
1020 (mailbox, msgno, LIBBALSA_MESSAGE_FLAG_DELETED, 0))
1021 *flags |= LIBBALSA_MESSAGE_FLAG_DELETED;
1022
1023 /* Copy stream */
1024 *stream = libbalsa_mailbox_get_message_stream(mailbox, msgno, TRUE);
1025 if(!*stream) {
1026 printf("Connection broken for message %u\n",
1027 (unsigned)msgno);
1028 return FALSE;
1029 }
1030
1031 return TRUE;
1032 }
1033
1034 /* Default method; imap backend replaces with its own method, optimized
1035 * for server-side copy, but falls back to this one if it's not a
1036 * server-side copy. */
1037 static void lbm_queue_check(LibBalsaMailbox * mailbox);
1038 static gboolean
libbalsa_mailbox_real_messages_copy(LibBalsaMailbox * mailbox,GArray * msgnos,LibBalsaMailbox * dest,GError ** err)1039 libbalsa_mailbox_real_messages_copy(LibBalsaMailbox * mailbox,
1040 GArray * msgnos,
1041 LibBalsaMailbox * dest, GError ** err)
1042 {
1043 gchar *text;
1044 guint successfully_copied;
1045 struct MsgCopyData mcd;
1046
1047 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mailbox), FALSE);
1048 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(dest), FALSE);
1049 g_return_val_if_fail(dest != mailbox, FALSE);
1050
1051 text = g_strdup_printf(_("Copying from %s to %s"), mailbox->name,
1052 dest->name);
1053 mcd.progress = LIBBALSA_PROGRESS_INIT;
1054 libbalsa_progress_set_text(&mcd.progress, text, msgnos->len);
1055 g_free(text);
1056
1057 mcd.src_mailbox = mailbox;
1058 mcd.msgnos = msgnos;
1059 mcd.stream = NULL;
1060 mcd.current_idx = 0;
1061 mcd.copied_cnt = 0;
1062 successfully_copied =
1063 LIBBALSA_MAILBOX_GET_CLASS(dest)->add_messages(dest,
1064 copy_iterator,
1065 &mcd,
1066 err);
1067 if(mcd.stream)
1068 g_object_unref(mcd.stream);
1069
1070 libbalsa_progress_set_text(&mcd.progress, NULL, 0);
1071
1072 if (successfully_copied)
1073 /* Some messages copied. */
1074 lbm_queue_check(dest);
1075
1076 return successfully_copied == msgnos->len;
1077 }
1078
1079 static gint mbox_compare_func(const SortTuple * a,
1080 const SortTuple * b,
1081 LibBalsaMailbox * mbox);
1082
1083 static void
libbalsa_mailbox_real_sort(LibBalsaMailbox * mbox,GArray * sort_array)1084 libbalsa_mailbox_real_sort(LibBalsaMailbox* mbox, GArray *sort_array)
1085 {
1086 /* Sort the array */
1087 g_array_sort_with_data(sort_array,
1088 (GCompareDataFunc) mbox_compare_func, mbox);
1089 }
1090
1091 static void
libbalsa_mailbox_real_save_config(LibBalsaMailbox * mailbox,const gchar * group)1092 libbalsa_mailbox_real_save_config(LibBalsaMailbox * mailbox,
1093 const gchar * group)
1094 {
1095 g_return_if_fail(LIBBALSA_IS_MAILBOX(mailbox));
1096
1097 libbalsa_conf_set_string("Type",
1098 g_type_name(G_OBJECT_TYPE(mailbox)));
1099 libbalsa_conf_set_string("Name", mailbox->name);
1100 }
1101
1102 static void
libbalsa_mailbox_real_load_config(LibBalsaMailbox * mailbox,const gchar * group)1103 libbalsa_mailbox_real_load_config(LibBalsaMailbox * mailbox,
1104 const gchar * group)
1105 {
1106 g_return_if_fail(LIBBALSA_IS_MAILBOX(mailbox));
1107
1108 g_free(mailbox->config_prefix);
1109 mailbox->config_prefix = g_strdup(group);
1110
1111 g_free(mailbox->name);
1112 mailbox->name = libbalsa_conf_get_string("Name=Mailbox");
1113 }
1114
1115 static gboolean
libbalsa_mailbox_real_close_backend(LibBalsaMailbox * mailbox)1116 libbalsa_mailbox_real_close_backend(LibBalsaMailbox * mailbox)
1117 {
1118 return TRUE; /* Default is noop. */
1119 }
1120
1121 #if BALSA_USE_THREADS
1122 static void
libbalsa_mailbox_real_lock_store(LibBalsaMailbox * mailbox,gboolean lock)1123 libbalsa_mailbox_real_lock_store(LibBalsaMailbox * mailbox, gboolean lock)
1124 {
1125 /* Default is noop. */
1126 }
1127 #endif /* BALSA_USE_THREADS */
1128
1129 GType
libbalsa_mailbox_type_from_path(const gchar * path)1130 libbalsa_mailbox_type_from_path(const gchar * path)
1131 /* libbalsa_get_mailbox_storage_type:
1132 returns one of LIBBALSA_TYPE_MAILBOX_IMAP,
1133 LIBBALSA_TYPE_MAILBOX_MAILDIR, LIBBALSA_TYPE_MAILBOX_MH,
1134 LIBBALSA_TYPE_MAILBOX_MBOX. G_TYPE_OBJECT on error or a directory.
1135 */
1136 {
1137 struct stat st;
1138
1139 if(strncmp(path, "imap://", 7) == 0)
1140 return LIBBALSA_TYPE_MAILBOX_IMAP;
1141
1142 if (stat (path, &st) == -1)
1143 return G_TYPE_OBJECT;
1144
1145 if (S_ISDIR (st.st_mode)) {
1146 char tmp[_POSIX_PATH_MAX];
1147
1148 /* check for maildir-style mailbox */
1149 snprintf (tmp, sizeof (tmp), "%s/cur", path);
1150 if (stat (tmp, &st) == 0 && S_ISDIR (st.st_mode))
1151 return LIBBALSA_TYPE_MAILBOX_MAILDIR;
1152
1153 /* check for mh-style mailbox */
1154 snprintf (tmp, sizeof (tmp), "%s/.mh_sequences", path);
1155 if (access (tmp, F_OK) == 0)
1156 return LIBBALSA_TYPE_MAILBOX_MH;
1157
1158 snprintf (tmp, sizeof (tmp), "%s/.xmhcache", path);
1159 if (access (tmp, F_OK) == 0)
1160 return LIBBALSA_TYPE_MAILBOX_MH;
1161
1162 snprintf (tmp, sizeof (tmp), "%s/.mew_cache", path);
1163 if (access (tmp, F_OK) == 0)
1164 return LIBBALSA_TYPE_MAILBOX_MH;
1165
1166 snprintf (tmp, sizeof (tmp), "%s/.mew-cache", path);
1167 if (access (tmp, F_OK) == 0)
1168 return LIBBALSA_TYPE_MAILBOX_MH;
1169
1170 /*
1171 * ok, this isn't an mh folder, but mh mode can be used to read
1172 * Usenet news from the spool. ;-)
1173 */
1174
1175 snprintf (tmp, sizeof (tmp), "%s/.overview", path);
1176 if (access (tmp, F_OK) == 0)
1177 return LIBBALSA_TYPE_MAILBOX_MH;
1178
1179 } else {
1180 /* Minimal check for an mbox */
1181 gint fd;
1182
1183 if ((fd = open(path, O_RDONLY)) >= 0) {
1184 gchar buf[5];
1185 guint len = read(fd, buf, sizeof buf);
1186 close(fd);
1187 if (len == 0
1188 || (len == sizeof buf
1189 && strncmp(buf, "From ", sizeof buf) == 0))
1190 return LIBBALSA_TYPE_MAILBOX_MBOX;
1191 }
1192 }
1193
1194 /* This is not a mailbox */
1195 return G_TYPE_OBJECT;
1196 }
1197
1198 /* Each of the next three methods emits a signal that will be caught by
1199 * a GtkTreeView, so the emission must be made holding the gdk lock.
1200 */
1201
1202 static LibBalsaMailboxIndexEntry *lbm_get_index_entry(LibBalsaMailbox *
1203 lmm, guint msgno);
1204 /* Does the node (non-NULL) have unseen children? */
1205 static gboolean
lbm_node_has_unseen_child(LibBalsaMailbox * lmm,GNode * node)1206 lbm_node_has_unseen_child(LibBalsaMailbox * lmm, GNode * node)
1207 {
1208 for (node = node->children; node; node = node->next) {
1209 LibBalsaMailboxIndexEntry *entry =
1210 /* g_ptr_array_index(lmm->mindex, msgno - 1); ?? */
1211 lbm_get_index_entry(lmm, GPOINTER_TO_UINT(node->data));
1212 if ((entry && entry->unseen) || lbm_node_has_unseen_child(lmm, node))
1213 return TRUE;
1214 }
1215 return FALSE;
1216 }
1217
1218 #ifdef BALSA_USE_THREADS
1219 /* Protects access to mailbox->msgnos_changed; may be locked
1220 * with or without the gdk lock, so WE MUST NOT GRAB THE GDK LOCK WHILE
1221 * HOLDING IT. */
1222
1223 static pthread_mutex_t msgnos_changed_lock = PTHREAD_MUTEX_INITIALIZER;
1224
1225 static void lbm_update_msgnos(LibBalsaMailbox * mailbox, guint seqno,
1226 GArray * msgnos);
1227
1228 static void
lbm_msgno_changed_expunged_cb(LibBalsaMailbox * mailbox,guint seqno)1229 lbm_msgno_changed_expunged_cb(LibBalsaMailbox * mailbox, guint seqno)
1230 {
1231 pthread_mutex_lock(&msgnos_changed_lock);
1232 lbm_update_msgnos(mailbox, seqno, mailbox->msgnos_changed);
1233 pthread_mutex_unlock(&msgnos_changed_lock);
1234 }
1235 #endif /* BALSA_USE_THREADS */
1236
1237 static void
lbm_msgno_row_changed(LibBalsaMailbox * mailbox,guint msgno,GtkTreeIter * iter)1238 lbm_msgno_row_changed(LibBalsaMailbox * mailbox, guint msgno,
1239 GtkTreeIter * iter)
1240 {
1241 if (!iter->user_data)
1242 iter->user_data =
1243 g_node_find(mailbox->msg_tree, G_PRE_ORDER, G_TRAVERSE_ALL,
1244 GUINT_TO_POINTER(msgno));
1245
1246 if (iter->user_data) {
1247 GtkTreePath *path;
1248
1249 iter->stamp = mailbox->stamp;
1250 path = gtk_tree_model_get_path(GTK_TREE_MODEL(mailbox), iter);
1251 g_signal_emit(mailbox, libbalsa_mbox_model_signals[ROW_CHANGED], 0,
1252 path, iter);
1253 gtk_tree_path_free(path);
1254 }
1255 }
1256
1257 #ifdef BALSA_USE_THREADS
1258 static gboolean
lbm_msgnos_changed_idle_cb(LibBalsaMailbox * mailbox)1259 lbm_msgnos_changed_idle_cb(LibBalsaMailbox * mailbox)
1260 {
1261 guint i;
1262
1263 if (!mailbox->msgnos_changed)
1264 return FALSE;
1265
1266 #define DEBUG FALSE
1267 #if DEBUG
1268 g_print("%s %s %d requested\n", __func__, mailbox->name,
1269 mailbox->msgnos_changed->len);
1270 #endif
1271
1272 pthread_mutex_lock(&msgnos_changed_lock);
1273 for (i = 0; i < mailbox->msgnos_changed->len; i++) {
1274 guint msgno = g_array_index(mailbox->msgnos_changed, guint, i);
1275 GtkTreeIter iter;
1276
1277 if (!MAILBOX_OPEN(mailbox))
1278 break;
1279
1280 #if DEBUG
1281 g_print("%s %s msgno %d\n", __func__, mailbox->name, msgno);
1282 #endif
1283 pthread_mutex_unlock(&msgnos_changed_lock);
1284 iter.user_data = NULL;
1285 lbm_msgno_row_changed(mailbox, msgno, &iter);
1286 pthread_mutex_lock(&msgnos_changed_lock);
1287 }
1288
1289 #if DEBUG
1290 g_print("%s %s %d processed\n", __func__, mailbox->name,
1291 mailbox->msgnos_changed->len);
1292 #endif
1293 mailbox->msgnos_changed->len = 0;
1294 pthread_mutex_unlock(&msgnos_changed_lock);
1295
1296 g_object_unref(mailbox);
1297 return FALSE;
1298 }
1299 #endif /* BALSA_USE_THREADS */
1300
1301 static void
lbm_msgno_changed(LibBalsaMailbox * mailbox,guint seqno,GtkTreeIter * iter)1302 lbm_msgno_changed(LibBalsaMailbox * mailbox, guint seqno,
1303 GtkTreeIter * iter)
1304 {
1305 #ifdef BALSA_USE_THREADS
1306 if (libbalsa_am_i_subthread()) {
1307 pthread_mutex_lock(&msgnos_changed_lock);
1308 if (!mailbox->msgnos_changed) {
1309 mailbox->msgnos_changed =
1310 g_array_new(FALSE, FALSE, sizeof(guint));
1311 g_signal_connect(mailbox, "message-expunged",
1312 G_CALLBACK(lbm_msgno_changed_expunged_cb),
1313 NULL);
1314 }
1315 if (mailbox->msgnos_changed->len == 0)
1316 g_idle_add((GSourceFunc) lbm_msgnos_changed_idle_cb,
1317 g_object_ref(mailbox));
1318
1319 g_array_append_val(mailbox->msgnos_changed, seqno);
1320 pthread_mutex_unlock(&msgnos_changed_lock);
1321
1322 /* Not calling lbm_msgno_row_changed, so we must make sure
1323 * iter->user_data is set: */
1324 if (!iter->user_data)
1325 iter->user_data =
1326 g_node_find(mailbox->msg_tree, G_PRE_ORDER, G_TRAVERSE_ALL,
1327 GUINT_TO_POINTER(seqno));
1328 return;
1329 }
1330 #endif
1331
1332 lbm_msgno_row_changed(mailbox, seqno, iter);
1333 }
1334
1335 void
libbalsa_mailbox_msgno_changed(LibBalsaMailbox * mailbox,guint seqno)1336 libbalsa_mailbox_msgno_changed(LibBalsaMailbox * mailbox, guint seqno)
1337 {
1338 GtkTreeIter iter;
1339
1340 if (!mailbox->msg_tree) {
1341 return;
1342 }
1343
1344 iter.user_data = NULL;
1345 lbm_msgno_changed(mailbox, seqno, &iter);
1346
1347 /* Parents' style may need to be changed also. */
1348 while (iter.user_data) {
1349 GNode *parent = ((GNode *) iter.user_data)->parent;
1350
1351 iter.user_data = parent;
1352 if (parent && (seqno = GPOINTER_TO_UINT(parent->data)) > 0)
1353 lbm_msgno_changed(mailbox, seqno, &iter);
1354 }
1355 }
1356
1357 void
libbalsa_mailbox_msgno_inserted(LibBalsaMailbox * mailbox,guint seqno,GNode * parent,GNode ** sibling)1358 libbalsa_mailbox_msgno_inserted(LibBalsaMailbox *mailbox, guint seqno,
1359 GNode * parent, GNode ** sibling)
1360 {
1361 GtkTreeIter iter;
1362 GtkTreePath *path;
1363 GSList **unthreaded;
1364
1365 if (!mailbox->msg_tree)
1366 return;
1367 #undef SANITY_CHECK
1368 #ifdef SANITY_CHECK
1369 g_return_if_fail(!g_node_find(mailbox->msg_tree,
1370 G_PRE_ORDER, G_TRAVERSE_ALL,
1371 GUINT_TO_POINTER(seqno)));
1372 #endif
1373
1374 gdk_threads_enter();
1375 /* Insert node into the message tree before getting path. */
1376 iter.user_data = g_node_new(GUINT_TO_POINTER(seqno));
1377 iter.stamp = mailbox->stamp;
1378 *sibling = g_node_insert_after(parent, *sibling, iter.user_data);
1379
1380 if (g_signal_has_handler_pending(mailbox,
1381 libbalsa_mbox_model_signals
1382 [ROW_INSERTED], 0, FALSE)) {
1383 path = gtk_tree_model_get_path(GTK_TREE_MODEL(mailbox), &iter);
1384 g_signal_emit(mailbox, libbalsa_mbox_model_signals[ROW_INSERTED],
1385 0, path, &iter);
1386 gtk_tree_path_free(path);
1387 }
1388
1389 unthreaded =
1390 g_object_get_data(G_OBJECT(mailbox), LIBBALSA_MAILBOX_UNTHREADED);
1391 if (unthreaded)
1392 *unthreaded =
1393 g_slist_prepend(*unthreaded, GUINT_TO_POINTER(seqno));
1394
1395 mailbox->msg_tree_changed = TRUE;
1396 gdk_threads_leave();
1397 }
1398
1399 static void
libbalsa_mailbox_msgno_filt_in(LibBalsaMailbox * mailbox,guint seqno)1400 libbalsa_mailbox_msgno_filt_in(LibBalsaMailbox *mailbox, guint seqno)
1401 {
1402 GtkTreeIter iter;
1403 GtkTreePath *path;
1404
1405 gdk_threads_enter();
1406 if (!mailbox->msg_tree) {
1407 gdk_threads_leave();
1408 return;
1409 }
1410
1411 /* Insert node into the message tree before getting path. */
1412 iter.user_data = g_node_new(GUINT_TO_POINTER(seqno));
1413 iter.stamp = mailbox->stamp;
1414 g_node_prepend(mailbox->msg_tree, iter.user_data);
1415
1416 path = gtk_tree_model_get_path(GTK_TREE_MODEL(mailbox), &iter);
1417 g_signal_emit(mailbox, libbalsa_mbox_model_signals[ROW_INSERTED], 0,
1418 path, &iter);
1419 gtk_tree_path_free(path);
1420
1421 mailbox->msg_tree_changed = TRUE;
1422 lbm_changed_schedule_idle(mailbox);
1423
1424 gdk_threads_leave();
1425 }
1426
1427 /*
1428 * libbalsa_mailbox_msgno_removed and helpers
1429 */
1430 struct remove_data {LibBalsaMailbox *mailbox; unsigned seqno; GNode *node; };
1431 static gboolean
decrease_post(GNode * node,gpointer data)1432 decrease_post(GNode *node, gpointer data)
1433 {
1434 struct remove_data *dt = (struct remove_data*)data;
1435 unsigned seqno = GPOINTER_TO_UINT(node->data);
1436 if(seqno == dt->seqno)
1437 dt->node = node;
1438 else if(seqno>dt->seqno) {
1439 GtkTreeIter iter;
1440 node->data = GUINT_TO_POINTER(seqno-1);
1441 iter.user_data = node;
1442 lbm_msgno_changed(dt->mailbox, seqno, &iter);
1443 }
1444 return FALSE;
1445 }
1446
1447 void
libbalsa_mailbox_msgno_removed(LibBalsaMailbox * mailbox,guint seqno)1448 libbalsa_mailbox_msgno_removed(LibBalsaMailbox * mailbox, guint seqno)
1449 {
1450 GtkTreeIter iter;
1451 GtkTreePath *path;
1452 struct remove_data dt;
1453 GNode *child;
1454 GNode *parent;
1455
1456 gdk_threads_enter();
1457 g_signal_emit(mailbox, libbalsa_mailbox_signals[MESSAGE_EXPUNGED],
1458 0, seqno);
1459
1460 if (!mailbox->msg_tree) {
1461 gdk_threads_leave();
1462 return;
1463 }
1464
1465 dt.mailbox = mailbox;
1466 dt.seqno = seqno;
1467 dt.node = NULL;
1468
1469 g_node_traverse(mailbox->msg_tree, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1470 decrease_post, &dt);
1471
1472 if (seqno <= mailbox->mindex->len) {
1473 lbm_index_entry_free(g_ptr_array_index(mailbox->mindex,
1474 seqno - 1));
1475 g_ptr_array_remove_index(mailbox->mindex, seqno - 1);
1476 }
1477
1478 mailbox->msg_tree_changed = TRUE;
1479
1480 if (!dt.node) {
1481 /* It's ok, apparently the view did not include this message */
1482 gdk_threads_leave();
1483 return;
1484 }
1485
1486 iter.user_data = dt.node;
1487 iter.stamp = mailbox->stamp;
1488 path = gtk_tree_model_get_path(GTK_TREE_MODEL(mailbox), &iter);
1489
1490 /* First promote any children to the node's parent; we'll insert
1491 * them all before the current node, to keep the path calculation
1492 * simple. */
1493 parent = dt.node->parent;
1494 while ((child = dt.node->children)) {
1495 GSList **unthreaded;
1496 /* No need to notify the tree-view about unlinking the child--it
1497 * will assume we already did that when we notify it about
1498 * destroying the parent. */
1499 g_node_unlink(child);
1500 g_node_insert_before(parent, dt.node, child);
1501
1502 /* Notify the tree-view about the new location of the child. */
1503 iter.user_data = child;
1504 g_signal_emit(mailbox, libbalsa_mbox_model_signals[ROW_INSERTED], 0,
1505 path, &iter);
1506 if (child->children)
1507 g_signal_emit(mailbox,
1508 libbalsa_mbox_model_signals[ROW_HAS_CHILD_TOGGLED],
1509 0, path, &iter);
1510 gtk_tree_path_next(path);
1511
1512 unthreaded = g_object_get_data(G_OBJECT(mailbox),
1513 LIBBALSA_MAILBOX_UNTHREADED);
1514 if (unthreaded)
1515 *unthreaded = g_slist_prepend(*unthreaded, child->data);
1516 }
1517
1518 /* Now it's safe to destroy the node. */
1519 g_node_destroy(dt.node);
1520 g_signal_emit(mailbox, libbalsa_mbox_model_signals[ROW_DELETED], 0, path);
1521
1522 if (parent->parent && !parent->children) {
1523 gtk_tree_path_up(path);
1524 iter.user_data = parent;
1525 g_signal_emit(mailbox,
1526 libbalsa_mbox_model_signals[ROW_HAS_CHILD_TOGGLED], 0,
1527 path, &iter);
1528 }
1529
1530 gtk_tree_path_free(path);
1531 mailbox->stamp++;
1532
1533 gdk_threads_leave();
1534 }
1535
1536 static void
libbalsa_mailbox_msgno_filt_out(LibBalsaMailbox * mailbox,GNode * node)1537 libbalsa_mailbox_msgno_filt_out(LibBalsaMailbox * mailbox, GNode * node)
1538 {
1539 GtkTreeIter iter;
1540 GtkTreePath *path;
1541 GNode *child, *parent;
1542
1543 gdk_threads_enter();
1544 if (!mailbox->msg_tree) {
1545 gdk_threads_leave();
1546 return;
1547 }
1548
1549 iter.user_data = node;
1550 iter.stamp = mailbox->stamp;
1551 path = gtk_tree_model_get_path(GTK_TREE_MODEL(mailbox), &iter);
1552
1553 /* First promote any children to the node's parent; we'll insert
1554 * them all before the current node, to keep the path calculation
1555 * simple. */
1556 parent = node->parent;
1557 while ((child = node->children)) {
1558 /* No need to notify the tree-view about unlinking the child--it
1559 * will assume we already did that when we notify it about
1560 * destroying the parent. */
1561 g_node_unlink(child);
1562 g_node_insert_before(parent, node, child);
1563
1564 /* Notify the tree-view about the new location of the child. */
1565 iter.user_data = child;
1566 g_signal_emit(mailbox, libbalsa_mbox_model_signals[ROW_INSERTED], 0,
1567 path, &iter);
1568 if (child->children)
1569 g_signal_emit(mailbox,
1570 libbalsa_mbox_model_signals[ROW_HAS_CHILD_TOGGLED],
1571 0, path, &iter);
1572 gtk_tree_path_next(path);
1573 }
1574
1575 /* Now it's safe to destroy the node. */
1576 g_node_destroy(node);
1577 g_signal_emit(mailbox, libbalsa_mbox_model_signals[ROW_DELETED], 0, path);
1578
1579 if (parent->parent && !parent->children) {
1580 gtk_tree_path_up(path);
1581 iter.user_data = parent;
1582 g_signal_emit(mailbox,
1583 libbalsa_mbox_model_signals[ROW_HAS_CHILD_TOGGLED], 0,
1584 path, &iter);
1585 }
1586
1587 gtk_tree_path_free(path);
1588 mailbox->stamp++;
1589
1590 mailbox->msg_tree_changed = TRUE;
1591 lbm_changed_schedule_idle(mailbox);
1592
1593 gdk_threads_leave();
1594 }
1595
1596 /*
1597 * Check whether to filter the message in or out of the view:
1598 * - if it's in the view and doesn't match the condition, filter it out,
1599 * unless it's selected and we don't want to filter out selected
1600 * messages;
1601 * - if it isn't in the view and it matches the condition, filter it in.
1602 */
1603
1604 static void
lbm_msgno_filt_check(LibBalsaMailbox * mailbox,guint seqno,LibBalsaMailboxSearchIter * search_iter,gboolean hold_selected)1605 lbm_msgno_filt_check(LibBalsaMailbox * mailbox, guint seqno,
1606 LibBalsaMailboxSearchIter * search_iter,
1607 gboolean hold_selected)
1608 {
1609 gboolean match;
1610 GNode *node;
1611
1612 match = search_iter ?
1613 libbalsa_mailbox_message_match(mailbox, seqno, search_iter) : TRUE;
1614 node = g_node_find(mailbox->msg_tree, G_PRE_ORDER, G_TRAVERSE_ALL,
1615 GUINT_TO_POINTER(seqno));
1616 if (node) {
1617 if (!match) {
1618 gboolean filt_out = hold_selected ?
1619 libbalsa_mailbox_msgno_has_flags(mailbox, seqno, 0,
1620 LIBBALSA_MESSAGE_FLAG_SELECTED)
1621 : TRUE;
1622 #if 1
1623 /* a hack. The whole filtering idea is bit silly since we
1624 keep checking flags (or maybe more!) on all messages so
1625 that the time spent on changing the selection grows
1626 linearly with the mailbox size! */
1627 if (LIBBALSA_IS_MAILBOX_IMAP(mailbox) &&
1628 !libbalsa_mailbox_imap_is_connected
1629 (LIBBALSA_MAILBOX_IMAP(mailbox)))
1630 filt_out = FALSE;
1631 #endif
1632 if (filt_out)
1633 libbalsa_mailbox_msgno_filt_out(mailbox, node);
1634 }
1635 } else {
1636 if (match)
1637 libbalsa_mailbox_msgno_filt_in(mailbox, seqno);
1638 }
1639 }
1640
1641 #ifdef BALSA_USE_THREADS
1642 typedef struct {
1643 LibBalsaMailbox *mailbox;
1644 guint seqno;
1645 LibBalsaMailboxSearchIter *search_iter;
1646 gboolean hold_selected;
1647 } LibBalsaMailboxMsgnoFiltCheckInfo;
1648
1649 static gboolean
lbm_msgno_filt_check_idle_cb(LibBalsaMailboxMsgnoFiltCheckInfo * info)1650 lbm_msgno_filt_check_idle_cb(LibBalsaMailboxMsgnoFiltCheckInfo * info)
1651 {
1652 if (MAILBOX_OPEN(info->mailbox))
1653 lbm_msgno_filt_check(info->mailbox, info->seqno, info->search_iter,
1654 info->hold_selected);
1655
1656 g_object_unref(info->mailbox);
1657 libbalsa_mailbox_search_iter_unref(info->search_iter);
1658 g_free(info);
1659
1660 return FALSE;
1661 }
1662 #endif /* BALSA_USE_THREADS */
1663
1664 void
libbalsa_mailbox_msgno_filt_check(LibBalsaMailbox * mailbox,guint seqno,LibBalsaMailboxSearchIter * search_iter,gboolean hold_selected)1665 libbalsa_mailbox_msgno_filt_check(LibBalsaMailbox * mailbox, guint seqno,
1666 LibBalsaMailboxSearchIter * search_iter,
1667 gboolean hold_selected)
1668 {
1669 g_return_if_fail(LIBBALSA_IS_MAILBOX(mailbox));
1670
1671 if (!mailbox->msg_tree) {
1672 return;
1673 }
1674
1675 #ifdef BALSA_USE_THREADS
1676 if (!libbalsa_am_i_subthread()) {
1677 lbm_msgno_filt_check(mailbox, seqno, search_iter, hold_selected);
1678 } else {
1679 LibBalsaMailboxMsgnoFiltCheckInfo *info;
1680
1681 info = g_new(LibBalsaMailboxMsgnoFiltCheckInfo, 1);
1682 info->mailbox = g_object_ref(mailbox);
1683 info->seqno = seqno;
1684 info->search_iter = libbalsa_mailbox_search_iter_ref(search_iter);
1685 info->hold_selected = hold_selected;
1686 g_idle_add((GSourceFunc) lbm_msgno_filt_check_idle_cb, info);
1687 }
1688 #else /* BALSA_USE_THREADS */
1689 lbm_msgno_filt_check(mailbox, seqno, search_iter, hold_selected);
1690 #endif /* BALSA_USE_THREADS */
1691 }
1692
1693 /* Search iters */
1694 LibBalsaMailboxSearchIter *
libbalsa_mailbox_search_iter_new(LibBalsaCondition * condition)1695 libbalsa_mailbox_search_iter_new(LibBalsaCondition * condition)
1696 {
1697 LibBalsaMailboxSearchIter *iter;
1698
1699 if (!condition)
1700 return NULL;
1701
1702 iter = g_slice_new(LibBalsaMailboxSearchIter);
1703 iter->mailbox = NULL;
1704 iter->stamp = 0;
1705 iter->condition = libbalsa_condition_ref(condition);
1706 iter->user_data = NULL;
1707 iter->ref_count = 1;
1708
1709 return iter;
1710 }
1711
1712 /* Create a LibBalsaMailboxSearchIter for a mailbox's view_filter. */
1713 LibBalsaMailboxSearchIter *
libbalsa_mailbox_search_iter_view(LibBalsaMailbox * mailbox)1714 libbalsa_mailbox_search_iter_view(LibBalsaMailbox * mailbox)
1715 {
1716 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mailbox), NULL);
1717
1718 return libbalsa_mailbox_search_iter_new(mailbox->view_filter);
1719 }
1720
1721 /* Increment reference count of a LibBalsaMailboxSearchIter, if it is
1722 * valid */
1723 LibBalsaMailboxSearchIter *
libbalsa_mailbox_search_iter_ref(LibBalsaMailboxSearchIter * search_iter)1724 libbalsa_mailbox_search_iter_ref(LibBalsaMailboxSearchIter * search_iter)
1725 {
1726 if (search_iter)
1727 ++search_iter->ref_count;
1728
1729 return search_iter;
1730 }
1731
1732 /* Decrement reference count of a LibBalsaMailboxSearchIter, if it is
1733 * non-NULL and valid, and free it if it goes to zero */
1734 void
libbalsa_mailbox_search_iter_unref(LibBalsaMailboxSearchIter * search_iter)1735 libbalsa_mailbox_search_iter_unref(LibBalsaMailboxSearchIter * search_iter)
1736 {
1737 LibBalsaMailbox *mailbox;
1738
1739 if (!search_iter || --search_iter->ref_count > 0)
1740 return;
1741
1742 mailbox = search_iter->mailbox;
1743 if (mailbox && LIBBALSA_MAILBOX_GET_CLASS(mailbox)->search_iter_free)
1744 LIBBALSA_MAILBOX_GET_CLASS(mailbox)->search_iter_free(search_iter);
1745
1746 libbalsa_condition_unref(search_iter->condition);
1747 g_slice_free(LibBalsaMailboxSearchIter, search_iter);
1748 }
1749
1750 /* GNode iterators; they return the root node when they run out of nodes,
1751 * and find the appropriate starting node when called with the root. */
1752 static GNode *
lbm_next(GNode * node)1753 lbm_next(GNode * node)
1754 {
1755 /* next is: our first child, if we have one;
1756 * else our sibling, if we have one;
1757 * else the sibling of our first ancestor who has
1758 * one. */
1759 if (node->children)
1760 return node->children;
1761
1762 do {
1763 if (node->next)
1764 return node->next;
1765 node = node->parent;
1766 } while (!G_NODE_IS_ROOT(node));
1767
1768 return node;
1769 }
1770
1771 static GNode *
lbm_last_descendant(GNode * node)1772 lbm_last_descendant(GNode * node)
1773 {
1774 if (node->children) {
1775 GNode *tmp;
1776
1777 node = node->children;
1778 while ((tmp = node->next) || (tmp = node->children))
1779 node = tmp;
1780 }
1781 return node;
1782 }
1783
1784 static GNode *
lbm_prev(GNode * node)1785 lbm_prev(GNode * node)
1786 {
1787 if (G_NODE_IS_ROOT(node))
1788 return lbm_last_descendant(node);
1789
1790 /* previous is: if we have a sibling,
1791 * if it has children, its last descendant;
1792 * else the sibling;
1793 * else our parent. */
1794 if (node->prev)
1795 return lbm_last_descendant(node->prev);
1796
1797 return node->parent;
1798 }
1799
1800 /* Find a message in the tree-model, by its message number. */
1801 gboolean
libbalsa_mailbox_msgno_find(LibBalsaMailbox * mailbox,guint seqno,GtkTreePath ** path,GtkTreeIter * iter)1802 libbalsa_mailbox_msgno_find(LibBalsaMailbox * mailbox, guint seqno,
1803 GtkTreePath ** path, GtkTreeIter * iter)
1804 {
1805 GtkTreeIter tmp_iter;
1806
1807 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mailbox), FALSE);
1808 g_return_val_if_fail(seqno > 0, FALSE);
1809
1810 if (!mailbox->msg_tree || !(tmp_iter.user_data =
1811 g_node_find(mailbox->msg_tree, G_PRE_ORDER, G_TRAVERSE_ALL,
1812 GINT_TO_POINTER(seqno))))
1813 return FALSE;
1814
1815 tmp_iter.stamp = mailbox->stamp;
1816
1817 if (path)
1818 *path =
1819 gtk_tree_model_get_path(GTK_TREE_MODEL(mailbox), &tmp_iter);
1820 if (iter)
1821 *iter = tmp_iter;
1822
1823 return TRUE;
1824 }
1825
1826 struct AddMessageData {
1827 GMimeStream *stream;
1828 LibBalsaMessageFlag flags;
1829 gboolean processed;
1830 };
1831
1832 static gboolean
msg_iterator(LibBalsaMessageFlag * flg,GMimeStream ** stream,void * arg)1833 msg_iterator(LibBalsaMessageFlag *flg, GMimeStream **stream, void *arg)
1834 {
1835 struct AddMessageData * amd = (struct AddMessageData*)arg;
1836 if (amd->processed)
1837 return FALSE;
1838 amd->processed = TRUE;
1839 *flg = amd->flags;
1840 /* Make sure ::add_messages does not destroy the stream. */
1841 *stream = g_object_ref(amd->stream);
1842 return TRUE;
1843 }
1844
1845 gboolean
libbalsa_mailbox_add_message(LibBalsaMailbox * mailbox,GMimeStream * stream,LibBalsaMessageFlag flags,GError ** err)1846 libbalsa_mailbox_add_message(LibBalsaMailbox * mailbox,
1847 GMimeStream * stream,
1848 LibBalsaMessageFlag flags, GError ** err)
1849 {
1850 guint retval;
1851 struct AddMessageData amd;
1852 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mailbox), FALSE);
1853
1854 libbalsa_lock_mailbox(mailbox);
1855
1856 amd.stream = stream;
1857 amd.flags = flags;
1858 amd.processed = FALSE;
1859 retval =
1860 LIBBALSA_MAILBOX_GET_CLASS(mailbox)->add_messages(mailbox,
1861 msg_iterator, &amd,
1862 err);
1863 if (retval) {
1864 if (!(flags & LIBBALSA_MESSAGE_FLAG_DELETED)
1865 && (flags & LIBBALSA_MESSAGE_FLAG_NEW))
1866 libbalsa_mailbox_set_unread_messages_flag(mailbox, TRUE);
1867 lbm_queue_check(mailbox);
1868 }
1869
1870 libbalsa_unlock_mailbox(mailbox);
1871
1872 return retval;
1873 }
1874
1875 guint
libbalsa_mailbox_add_messages(LibBalsaMailbox * mailbox,LibBalsaAddMessageIterator msg_iterator,void * arg,GError ** err)1876 libbalsa_mailbox_add_messages(LibBalsaMailbox * mailbox,
1877 LibBalsaAddMessageIterator msg_iterator,
1878 void *arg,
1879 GError ** err)
1880 {
1881 guint retval;
1882
1883 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mailbox), FALSE);
1884
1885 libbalsa_lock_mailbox(mailbox);
1886
1887 retval =
1888 LIBBALSA_MAILBOX_GET_CLASS(mailbox)->add_messages(mailbox,
1889 msg_iterator, arg,
1890 err);
1891
1892 if (retval) {
1893 #ifdef FIXED
1894 /* this is something that should be returned/taken care of by
1895 add_messages? */
1896 if (!(flags & LIBBALSA_MESSAGE_FLAG_DELETED)
1897 && (flags & LIBBALSA_MESSAGE_FLAG_NEW))
1898 libbalsa_mailbox_set_unread_messages_flag(mailbox, TRUE);
1899 #endif
1900 lbm_queue_check(mailbox);
1901 }
1902
1903 libbalsa_unlock_mailbox(mailbox);
1904
1905 return retval;
1906 }
1907
1908 gboolean
libbalsa_mailbox_close_backend(LibBalsaMailbox * mailbox)1909 libbalsa_mailbox_close_backend(LibBalsaMailbox * mailbox)
1910 {
1911 g_return_val_if_fail(mailbox != NULL, FALSE);
1912 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mailbox), FALSE);
1913
1914 return LIBBALSA_MAILBOX_GET_CLASS(mailbox)->close_backend(mailbox);
1915 }
1916
1917 guint
libbalsa_mailbox_total_messages(LibBalsaMailbox * mailbox)1918 libbalsa_mailbox_total_messages(LibBalsaMailbox * mailbox)
1919 {
1920 g_return_val_if_fail(mailbox != NULL, 0);
1921 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mailbox), 0);
1922
1923 return LIBBALSA_MAILBOX_GET_CLASS(mailbox)->total_messages(mailbox);
1924 }
1925
1926 gboolean
libbalsa_mailbox_sync_storage(LibBalsaMailbox * mailbox,gboolean expunge)1927 libbalsa_mailbox_sync_storage(LibBalsaMailbox * mailbox, gboolean expunge)
1928 {
1929 gboolean retval = TRUE;
1930
1931 g_return_val_if_fail(mailbox != NULL, FALSE);
1932 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mailbox), FALSE);
1933 g_return_val_if_fail(!mailbox->readonly, TRUE);
1934
1935 libbalsa_lock_mailbox(mailbox);
1936
1937 /* When called in an idle handler, the mailbox might have been
1938 * closed, so we must check (with the mailbox locked). */
1939 if (MAILBOX_OPEN(mailbox)) {
1940 GSList *unthreaded = NULL;
1941
1942 g_object_set_data(G_OBJECT(mailbox), LIBBALSA_MAILBOX_UNTHREADED,
1943 &unthreaded);
1944 retval =
1945 LIBBALSA_MAILBOX_GET_CLASS(mailbox)->sync(mailbox, expunge);
1946 g_object_set_data(G_OBJECT(mailbox), LIBBALSA_MAILBOX_UNTHREADED,
1947 unthreaded);
1948 if (unthreaded) {
1949 lbm_set_threading(mailbox, mailbox->view->threading_type);
1950 g_slist_free(unthreaded);
1951 g_object_set_data(G_OBJECT(mailbox),
1952 LIBBALSA_MAILBOX_UNTHREADED, NULL);
1953 } else
1954 libbalsa_mailbox_changed(mailbox);
1955 }
1956
1957 libbalsa_unlock_mailbox(mailbox);
1958
1959 return retval;
1960 }
1961
1962 static void
lbm_cache_message(LibBalsaMailbox * mailbox,guint msgno,LibBalsaMessage * message)1963 lbm_cache_message(LibBalsaMailbox * mailbox, guint msgno,
1964 LibBalsaMessage * message)
1965 {
1966 LibBalsaMailboxIndexEntry *entry;
1967
1968 if (mailbox->mindex->len < msgno)
1969 g_ptr_array_set_size(mailbox->mindex, msgno);
1970
1971 entry = g_ptr_array_index(mailbox->mindex, msgno - 1);
1972
1973 if (!entry) {
1974 g_ptr_array_index(mailbox->mindex, msgno - 1) =
1975 entry = g_new(LibBalsaMailboxIndexEntry, 1);
1976 lbm_index_entry_populate_from_msg(entry, message);
1977 }
1978 #if BALSA_USE_THREADS
1979 else if (entry->idle_pending)
1980 lbm_index_entry_populate_from_msg(entry, message);
1981 #endif /* BALSA_USE_THREADS */
1982 }
1983
1984 LibBalsaMessage *
libbalsa_mailbox_get_message(LibBalsaMailbox * mailbox,guint msgno)1985 libbalsa_mailbox_get_message(LibBalsaMailbox * mailbox, guint msgno)
1986 {
1987 LibBalsaMessage *message;
1988
1989 g_return_val_if_fail(mailbox != NULL, NULL);
1990 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mailbox), NULL);
1991
1992 libbalsa_lock_mailbox(mailbox);
1993
1994 if (!MAILBOX_OPEN(mailbox)) {
1995 g_message(_("libbalsa_mailbox_get_message: mailbox %s is closed"),
1996 mailbox->name);
1997 libbalsa_unlock_mailbox(mailbox);
1998 return NULL;
1999 }
2000
2001 #ifdef BALSA_USE_THREADS
2002 if( !(msgno > 0 && msgno <= libbalsa_mailbox_total_messages(mailbox)) ) {
2003 libbalsa_unlock_mailbox(mailbox);
2004 g_warning("get_message: msgno %d out of range", msgno);
2005 return NULL;
2006 }
2007 #else /* BALSA_USE_THREADS */
2008 g_return_val_if_fail(msgno > 0 && msgno <=
2009 libbalsa_mailbox_total_messages(mailbox), NULL);
2010 #endif /* BALSA_USE_THREADS */
2011
2012 message = LIBBALSA_MAILBOX_GET_CLASS(mailbox)->get_message(mailbox,
2013 msgno);
2014 if (message && mailbox->mindex)
2015 /* Cache the message info, if we do not already have it. */
2016 lbm_cache_message(mailbox, msgno, message);
2017
2018 libbalsa_unlock_mailbox(mailbox);
2019
2020 return message;
2021 }
2022
2023 gboolean
libbalsa_mailbox_prepare_threading(LibBalsaMailbox * mailbox,guint start)2024 libbalsa_mailbox_prepare_threading(LibBalsaMailbox * mailbox, guint start)
2025 {
2026 g_return_val_if_fail(mailbox != NULL, FALSE);
2027 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mailbox), FALSE);
2028
2029 return LIBBALSA_MAILBOX_GET_CLASS(mailbox)->prepare_threading(mailbox,
2030 start);
2031 }
2032
2033 gboolean
libbalsa_mailbox_fetch_message_structure(LibBalsaMailbox * mailbox,LibBalsaMessage * message,LibBalsaFetchFlag flags)2034 libbalsa_mailbox_fetch_message_structure(LibBalsaMailbox *mailbox,
2035 LibBalsaMessage *message,
2036 LibBalsaFetchFlag flags)
2037 {
2038 g_return_val_if_fail(mailbox != NULL, FALSE);
2039 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mailbox), FALSE);
2040 g_return_val_if_fail(message != NULL, FALSE);
2041
2042 return LIBBALSA_MAILBOX_GET_CLASS(mailbox)
2043 ->fetch_message_structure(mailbox, message, flags);
2044 }
2045
2046 void
libbalsa_mailbox_release_message(LibBalsaMailbox * mailbox,LibBalsaMessage * message)2047 libbalsa_mailbox_release_message(LibBalsaMailbox * mailbox,
2048 LibBalsaMessage * message)
2049 {
2050 g_return_if_fail(mailbox != NULL);
2051 g_return_if_fail(LIBBALSA_IS_MAILBOX(mailbox));
2052 g_return_if_fail(message != NULL);
2053 g_return_if_fail(LIBBALSA_IS_MESSAGE(message));
2054 g_return_if_fail(mailbox == message->mailbox);
2055
2056 LIBBALSA_MAILBOX_GET_CLASS(mailbox)
2057 ->release_message(mailbox, message);
2058 }
2059
2060 void
libbalsa_mailbox_set_msg_headers(LibBalsaMailbox * mailbox,LibBalsaMessage * message)2061 libbalsa_mailbox_set_msg_headers(LibBalsaMailbox *mailbox,
2062 LibBalsaMessage *message)
2063 {
2064 g_return_if_fail(mailbox != NULL);
2065 g_return_if_fail(LIBBALSA_IS_MAILBOX(mailbox));
2066 g_return_if_fail(message != NULL);
2067
2068 if(!message->has_all_headers) {
2069 LIBBALSA_MAILBOX_GET_CLASS(mailbox)->fetch_headers(mailbox, message);
2070 message->has_all_headers = 1;
2071 }
2072 }
2073
2074 gboolean
libbalsa_mailbox_get_message_part(LibBalsaMessage * message,LibBalsaMessageBody * part,GError ** err)2075 libbalsa_mailbox_get_message_part(LibBalsaMessage *message,
2076 LibBalsaMessageBody *part,
2077 GError **err)
2078 {
2079 g_return_val_if_fail(message != NULL, FALSE);
2080 g_return_val_if_fail(message->mailbox != NULL, FALSE);
2081 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(message->mailbox), FALSE);
2082 g_return_val_if_fail(part != NULL, FALSE);
2083
2084 return LIBBALSA_MAILBOX_GET_CLASS(message->mailbox)
2085 ->get_message_part(message, part, err);
2086 }
2087
2088 GMimeStream *
libbalsa_mailbox_get_message_stream(LibBalsaMailbox * mailbox,guint msgno,gboolean peek)2089 libbalsa_mailbox_get_message_stream(LibBalsaMailbox * mailbox, guint msgno,
2090 gboolean peek)
2091 {
2092 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mailbox), NULL);
2093 g_return_val_if_fail(msgno <= libbalsa_mailbox_total_messages(mailbox),
2094 NULL);
2095
2096 return LIBBALSA_MAILBOX_GET_CLASS(mailbox)->get_message_stream(mailbox,
2097 msgno,
2098 peek);
2099 }
2100
2101 /* libbalsa_mailbox_change_msgs_flags() changes stored message flags
2102 and is to be used only internally by libbalsa.
2103 */
2104 gboolean
libbalsa_mailbox_messages_change_flags(LibBalsaMailbox * mailbox,GArray * msgnos,LibBalsaMessageFlag set,LibBalsaMessageFlag clear)2105 libbalsa_mailbox_messages_change_flags(LibBalsaMailbox * mailbox,
2106 GArray * msgnos,
2107 LibBalsaMessageFlag set,
2108 LibBalsaMessageFlag clear)
2109 {
2110 gboolean retval;
2111 guint i;
2112 gboolean real_flag;
2113
2114 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mailbox), FALSE);
2115
2116 real_flag = (set | clear) & LIBBALSA_MESSAGE_FLAGS_REAL;
2117 g_return_val_if_fail(!mailbox->readonly || !real_flag, FALSE);
2118
2119 if (msgnos->len == 0)
2120 return TRUE;
2121
2122 if (real_flag)
2123 libbalsa_lock_mailbox(mailbox);
2124
2125 retval = LIBBALSA_MAILBOX_GET_CLASS(mailbox)->
2126 messages_change_flags(mailbox, msgnos, set, clear);
2127
2128 if (retval && mailbox->mindex && mailbox->view_filter) {
2129 LibBalsaMailboxSearchIter *iter_view =
2130 libbalsa_mailbox_search_iter_view(mailbox);
2131 for (i = 0; i < msgnos->len; i++) {
2132 guint msgno = g_array_index(msgnos, guint, i);
2133 libbalsa_mailbox_msgno_filt_check(mailbox, msgno, iter_view,
2134 TRUE);
2135 }
2136 libbalsa_mailbox_search_iter_unref(iter_view);
2137 }
2138
2139 if (real_flag)
2140 libbalsa_unlock_mailbox(mailbox);
2141
2142 if (set & LIBBALSA_MESSAGE_FLAG_DELETED && retval)
2143 libbalsa_mailbox_changed(mailbox);
2144
2145 return retval;
2146 }
2147
2148 gboolean
libbalsa_mailbox_msgno_change_flags(LibBalsaMailbox * mailbox,guint msgno,LibBalsaMessageFlag set,LibBalsaMessageFlag clear)2149 libbalsa_mailbox_msgno_change_flags(LibBalsaMailbox * mailbox,
2150 guint msgno,
2151 LibBalsaMessageFlag set,
2152 LibBalsaMessageFlag clear)
2153 {
2154 gboolean retval;
2155 GArray *msgnos = g_array_sized_new(FALSE, FALSE, sizeof(guint), 1);
2156
2157 g_array_append_val(msgnos, msgno);
2158 libbalsa_mailbox_register_msgnos(mailbox, msgnos);
2159 retval =
2160 libbalsa_mailbox_messages_change_flags(mailbox, msgnos, set,
2161 clear);
2162 libbalsa_mailbox_unregister_msgnos(mailbox, msgnos);
2163 g_array_free(msgnos, TRUE);
2164
2165 return retval;
2166 }
2167
2168 /* Copy messages with msgnos in the list from mailbox to dest. */
2169 gboolean
libbalsa_mailbox_messages_copy(LibBalsaMailbox * mailbox,GArray * msgnos,LibBalsaMailbox * dest,GError ** err)2170 libbalsa_mailbox_messages_copy(LibBalsaMailbox * mailbox, GArray * msgnos,
2171 LibBalsaMailbox * dest, GError **err)
2172 {
2173 gboolean retval;
2174
2175 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mailbox), FALSE);
2176 g_return_val_if_fail(msgnos->len > 0, TRUE);
2177
2178 libbalsa_lock_mailbox(mailbox);
2179 retval = LIBBALSA_MAILBOX_GET_CLASS(mailbox)->
2180 messages_copy(mailbox, msgnos, dest, err);
2181 libbalsa_unlock_mailbox(mailbox);
2182
2183 return retval;
2184 }
2185
2186 /* Move messages with msgnos in the list from mailbox to dest. */
2187 gboolean
libbalsa_mailbox_messages_move(LibBalsaMailbox * mailbox,GArray * msgnos,LibBalsaMailbox * dest,GError ** err)2188 libbalsa_mailbox_messages_move(LibBalsaMailbox * mailbox,
2189 GArray * msgnos,
2190 LibBalsaMailbox * dest, GError **err)
2191 {
2192 gboolean retval;
2193
2194 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mailbox), FALSE);
2195 g_return_val_if_fail(msgnos->len > 0, TRUE);
2196
2197 libbalsa_lock_mailbox(mailbox);
2198 if (libbalsa_mailbox_messages_copy(mailbox, msgnos, dest, err)) {
2199 retval = libbalsa_mailbox_messages_change_flags
2200 (mailbox, msgnos, LIBBALSA_MESSAGE_FLAG_DELETED,
2201 (LibBalsaMessageFlag) 0);
2202 if(!retval)
2203 g_set_error(err,LIBBALSA_MAILBOX_ERROR,
2204 LIBBALSA_MAILBOX_COPY_ERROR,
2205 _("Removing messages from source mailbox failed"));
2206 } else
2207 retval = FALSE;
2208 libbalsa_unlock_mailbox(mailbox);
2209
2210 return retval;
2211 }
2212
2213 /*
2214 * Mailbox views.
2215 *
2216 * NOTE: call to update_view_filter MUST be followed by a call to
2217 * lbm_set_threading that will actually create the
2218 * message tree.
2219 *
2220 * Returns TRUE if the message tree was updated.
2221 */
2222 gboolean
libbalsa_mailbox_set_view_filter(LibBalsaMailbox * mailbox,LibBalsaCondition * cond,gboolean update_immediately)2223 libbalsa_mailbox_set_view_filter(LibBalsaMailbox *mailbox,
2224 LibBalsaCondition *cond,
2225 gboolean update_immediately)
2226 {
2227 gboolean retval = FALSE;
2228
2229 libbalsa_lock_mailbox(mailbox);
2230
2231 if (!libbalsa_condition_compare(mailbox->view_filter, cond))
2232 mailbox->view_filter_pending = TRUE;
2233
2234 libbalsa_condition_unref(mailbox->view_filter);
2235 mailbox->view_filter = libbalsa_condition_ref(cond);
2236
2237 if (update_immediately && mailbox->view_filter_pending) {
2238 LIBBALSA_MAILBOX_GET_CLASS(mailbox)->update_view_filter(mailbox,
2239 cond);
2240 retval = lbm_set_threading(mailbox, mailbox->view->threading_type);
2241 mailbox->view_filter_pending = FALSE;
2242 }
2243
2244 libbalsa_unlock_mailbox(mailbox);
2245
2246 return retval;
2247 }
2248
2249 void
libbalsa_mailbox_make_view_filter_persistent(LibBalsaMailbox * mailbox)2250 libbalsa_mailbox_make_view_filter_persistent(LibBalsaMailbox * mailbox)
2251 {
2252 libbalsa_condition_unref(mailbox->persistent_view_filter);
2253 mailbox->persistent_view_filter =
2254 libbalsa_condition_ref(mailbox->view_filter);
2255 }
2256
2257 /* Test message flags. */
2258 gboolean
libbalsa_mailbox_msgno_has_flags(LibBalsaMailbox * mailbox,guint msgno,LibBalsaMessageFlag set,LibBalsaMessageFlag unset)2259 libbalsa_mailbox_msgno_has_flags(LibBalsaMailbox * mailbox, guint msgno,
2260 LibBalsaMessageFlag set,
2261 LibBalsaMessageFlag unset)
2262 {
2263 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mailbox), FALSE);
2264 g_return_val_if_fail(msgno > 0, FALSE);
2265
2266 return LIBBALSA_MAILBOX_GET_CLASS(mailbox)->msgno_has_flags(mailbox,
2267 msgno, set,
2268 unset);
2269 }
2270
2271 /* Inquire method: check whether mailbox driver can perform operation
2272 in question. In principle, all operations should be supported but
2273 some of them may be expensive under certain circumstances and are
2274 best avoided. */
2275 static gboolean
libbalsa_mailbox_real_can_do(LibBalsaMailbox * mbox,enum LibBalsaMailboxCapability cap)2276 libbalsa_mailbox_real_can_do(LibBalsaMailbox* mbox,
2277 enum LibBalsaMailboxCapability cap)
2278 {
2279 return TRUE;
2280 }
2281
2282 gboolean
libbalsa_mailbox_can_do(LibBalsaMailbox * mailbox,enum LibBalsaMailboxCapability cap)2283 libbalsa_mailbox_can_do(LibBalsaMailbox *mailbox,
2284 enum LibBalsaMailboxCapability cap)
2285 {
2286 return LIBBALSA_MAILBOX_GET_CLASS(mailbox)->can_do(mailbox, cap);
2287 }
2288
2289
2290 static void lbm_sort(LibBalsaMailbox * mbox, GNode * parent);
2291
2292 static void
lbm_check_and_sort(LibBalsaMailbox * mailbox)2293 lbm_check_and_sort(LibBalsaMailbox * mailbox)
2294 {
2295 if (mailbox->msg_tree)
2296 lbm_sort(mailbox, mailbox->msg_tree);
2297
2298 libbalsa_mailbox_changed(mailbox);
2299 }
2300
2301 #ifdef BALSA_USE_THREADS
2302 static gboolean
lbm_set_threading_idle_cb(LibBalsaMailbox * mailbox)2303 lbm_set_threading_idle_cb(LibBalsaMailbox * mailbox)
2304 {
2305 lbm_check_and_sort(mailbox);
2306 g_object_unref(mailbox);
2307 return FALSE;
2308 }
2309 #endif /* BALSA_USE_THREADS */
2310
2311 static gboolean
lbm_set_threading(LibBalsaMailbox * mailbox,LibBalsaMailboxThreadingType thread_type)2312 lbm_set_threading(LibBalsaMailbox * mailbox,
2313 LibBalsaMailboxThreadingType thread_type)
2314 {
2315 if (!MAILBOX_OPEN(mailbox))
2316 return FALSE;
2317
2318 LIBBALSA_MAILBOX_GET_CLASS(mailbox)->set_threading(mailbox,
2319 thread_type);
2320 #ifdef BALSA_USE_THREADS
2321 if (libbalsa_am_i_subthread()) {
2322 gdk_threads_add_idle((GSourceFunc) lbm_set_threading_idle_cb,
2323 g_object_ref(mailbox));
2324 } else {
2325 gdk_threads_enter();
2326 lbm_check_and_sort(mailbox);
2327 gdk_threads_leave();
2328 }
2329 #else /* BALSA_USE_THREADS */
2330 lbm_check_and_sort(mailbox);
2331 #endif /* BALSA_USE_THREADS */
2332
2333 return TRUE;
2334 }
2335
2336 void
libbalsa_mailbox_set_threading(LibBalsaMailbox * mailbox,LibBalsaMailboxThreadingType thread_type)2337 libbalsa_mailbox_set_threading(LibBalsaMailbox *mailbox,
2338 LibBalsaMailboxThreadingType thread_type)
2339 {
2340 g_return_if_fail(mailbox != NULL);
2341 g_return_if_fail(LIBBALSA_IS_MAILBOX(mailbox));
2342
2343 libbalsa_lock_mailbox(mailbox);
2344 lbm_set_threading(mailbox, thread_type);
2345 libbalsa_unlock_mailbox(mailbox);
2346 }
2347
2348 /* =================================================================== *
2349 * Mailbox view methods *
2350 * =================================================================== */
2351
2352 static LibBalsaMailboxView libbalsa_mailbox_view_default = {
2353 NULL, /* mailing_list_address */
2354 NULL, /* identity_name */
2355 LB_MAILBOX_THREADING_FLAT, /* threading_type */
2356 0, /* filter */
2357 LB_MAILBOX_SORT_TYPE_ASC, /* sort_type */
2358 LB_MAILBOX_SORT_NO, /* sort_field */
2359 LB_MAILBOX_SORT_NO, /* sort_field_prev */
2360 LB_MAILBOX_SHOW_UNSET, /* show */
2361 LB_MAILBOX_SUBSCRIBE_UNSET, /* subscribe */
2362 0, /* exposed */
2363 0, /* open */
2364 1, /* in_sync */
2365 0, /* used */
2366 #ifdef HAVE_GPGME
2367 LB_MAILBOX_CHK_CRYPT_MAYBE, /* gpg_chk_mode */
2368 #endif
2369 -1, /* total messages */
2370 -1, /* unread messages */
2371 0 /* mod time */
2372 };
2373
2374 LibBalsaMailboxView *
libbalsa_mailbox_view_new(void)2375 libbalsa_mailbox_view_new(void)
2376 {
2377 LibBalsaMailboxView *view;
2378
2379 view = g_memdup(&libbalsa_mailbox_view_default,
2380 sizeof libbalsa_mailbox_view_default);
2381
2382 return view;
2383 }
2384
2385 void
libbalsa_mailbox_view_free(LibBalsaMailboxView * view)2386 libbalsa_mailbox_view_free(LibBalsaMailboxView * view)
2387 {
2388 if (!view)
2389 return;
2390
2391 if (view->mailing_list_address)
2392 g_object_unref(view->mailing_list_address);
2393 g_free(view->identity_name);
2394 g_free(view);
2395 }
2396
2397 /* helper */
2398 static LibBalsaMailboxView *
lbm_get_view(LibBalsaMailbox * mailbox)2399 lbm_get_view(LibBalsaMailbox * mailbox)
2400 {
2401 if (!mailbox)
2402 return &libbalsa_mailbox_view_default;
2403
2404 if (!mailbox->view)
2405 mailbox->view = libbalsa_mailbox_view_new();
2406
2407 return mailbox->view;
2408 }
2409
2410 /* Set methods; NULL mailbox is valid, and changes the default value. */
2411
2412 gboolean
libbalsa_mailbox_set_identity_name(LibBalsaMailbox * mailbox,const gchar * identity_name)2413 libbalsa_mailbox_set_identity_name(LibBalsaMailbox * mailbox,
2414 const gchar * identity_name)
2415 {
2416 LibBalsaMailboxView *view = lbm_get_view(mailbox);
2417
2418 if (!view->identity_name || strcmp(view->identity_name, identity_name)) {
2419 g_free(view->identity_name);
2420 view->identity_name = g_strdup(identity_name);
2421 if (mailbox)
2422 view->in_sync = 0;
2423 return TRUE;
2424 } else
2425 return FALSE;
2426 }
2427
2428 void
libbalsa_mailbox_set_threading_type(LibBalsaMailbox * mailbox,LibBalsaMailboxThreadingType threading_type)2429 libbalsa_mailbox_set_threading_type(LibBalsaMailbox * mailbox,
2430 LibBalsaMailboxThreadingType
2431 threading_type)
2432 {
2433 LibBalsaMailboxView *view = lbm_get_view(mailbox);
2434
2435 if (view->threading_type != threading_type) {
2436 view->threading_type = threading_type;
2437 if (mailbox)
2438 view->in_sync = 0;
2439 }
2440 }
2441
2442 void
libbalsa_mailbox_set_sort_type(LibBalsaMailbox * mailbox,LibBalsaMailboxSortType sort_type)2443 libbalsa_mailbox_set_sort_type(LibBalsaMailbox * mailbox,
2444 LibBalsaMailboxSortType sort_type)
2445 {
2446 LibBalsaMailboxView *view = lbm_get_view(mailbox);
2447
2448 if (view->sort_type != sort_type) {
2449 view->sort_type = sort_type;
2450 if (mailbox)
2451 view->in_sync = 0;
2452 }
2453 }
2454
2455 void
libbalsa_mailbox_set_sort_field(LibBalsaMailbox * mailbox,LibBalsaMailboxSortFields sort_field)2456 libbalsa_mailbox_set_sort_field(LibBalsaMailbox * mailbox,
2457 LibBalsaMailboxSortFields sort_field)
2458 {
2459 LibBalsaMailboxView *view = lbm_get_view(mailbox);
2460
2461 if (view->sort_field != sort_field) {
2462 view->sort_field_prev = view->sort_field;
2463 view->sort_field = sort_field;
2464 if (mailbox)
2465 view->in_sync = 0;
2466 }
2467 }
2468
2469 gboolean
libbalsa_mailbox_set_show(LibBalsaMailbox * mailbox,LibBalsaMailboxShow show)2470 libbalsa_mailbox_set_show(LibBalsaMailbox * mailbox, LibBalsaMailboxShow show)
2471 {
2472 LibBalsaMailboxView *view = lbm_get_view(mailbox);
2473
2474 if (view->show != show) {
2475 /* Don't set not in sync if we're just replacing UNSET with the
2476 * default. */
2477 if (mailbox && view->show != LB_MAILBOX_SHOW_UNSET)
2478 view->in_sync = 0;
2479 view->show = show;
2480 return TRUE;
2481 } else
2482 return FALSE;
2483 }
2484
2485 gboolean
libbalsa_mailbox_set_subscribe(LibBalsaMailbox * mailbox,LibBalsaMailboxSubscribe subscribe)2486 libbalsa_mailbox_set_subscribe(LibBalsaMailbox * mailbox,
2487 LibBalsaMailboxSubscribe subscribe)
2488 {
2489 LibBalsaMailboxView *view = lbm_get_view(mailbox);
2490
2491 if (view->subscribe != subscribe) {
2492 /* Don't set not in sync if we're just replacing UNSET with the
2493 * default. */
2494 if (mailbox && view->subscribe != LB_MAILBOX_SUBSCRIBE_UNSET)
2495 view->in_sync = 0;
2496 view->subscribe = subscribe;
2497 return TRUE;
2498 } else
2499 return FALSE;
2500 }
2501
2502 void
libbalsa_mailbox_set_exposed(LibBalsaMailbox * mailbox,gboolean exposed)2503 libbalsa_mailbox_set_exposed(LibBalsaMailbox * mailbox, gboolean exposed)
2504 {
2505 LibBalsaMailboxView *view = lbm_get_view(mailbox);
2506
2507 if (view->exposed != exposed) {
2508 view->exposed = exposed ? 1 : 0;
2509 if (mailbox)
2510 view->in_sync = 0;
2511 }
2512 }
2513
2514 void
libbalsa_mailbox_set_open(LibBalsaMailbox * mailbox,gboolean open)2515 libbalsa_mailbox_set_open(LibBalsaMailbox * mailbox, gboolean open)
2516 {
2517 LibBalsaMailboxView *view = lbm_get_view(mailbox);
2518
2519 if (view->open != open) {
2520 view->open = open ? 1 : 0;
2521 if (mailbox)
2522 view->in_sync = 0;
2523 }
2524 }
2525
2526 void
libbalsa_mailbox_set_filter(LibBalsaMailbox * mailbox,gint filter)2527 libbalsa_mailbox_set_filter(LibBalsaMailbox * mailbox, gint filter)
2528 {
2529 LibBalsaMailboxView *view = lbm_get_view(mailbox);
2530
2531 if (view->filter != filter) {
2532 view->filter = filter;
2533 if (mailbox)
2534 view->in_sync = 0;
2535 }
2536 }
2537
2538 #ifdef HAVE_GPGME
2539 gboolean
libbalsa_mailbox_set_crypto_mode(LibBalsaMailbox * mailbox,LibBalsaChkCryptoMode gpg_chk_mode)2540 libbalsa_mailbox_set_crypto_mode(LibBalsaMailbox * mailbox,
2541 LibBalsaChkCryptoMode gpg_chk_mode)
2542 {
2543 LibBalsaMailboxView *view;
2544
2545 g_return_val_if_fail(mailbox != NULL && mailbox->view != NULL, FALSE);
2546
2547 view = mailbox->view;
2548 if (view->gpg_chk_mode != gpg_chk_mode) {
2549 view->gpg_chk_mode = gpg_chk_mode;
2550 return TRUE;
2551 } else
2552 return FALSE;
2553 }
2554 #endif
2555
2556 void
libbalsa_mailbox_set_unread(LibBalsaMailbox * mailbox,gint unread)2557 libbalsa_mailbox_set_unread(LibBalsaMailbox * mailbox, gint unread)
2558 {
2559 LibBalsaMailboxView *view;
2560
2561 /* Changing the default is not allowed. */
2562 g_return_if_fail(mailbox != NULL);
2563
2564 view = lbm_get_view(mailbox);
2565 view->used = 1;
2566
2567 if (view->unread != unread) {
2568 view->unread = unread;
2569 view->in_sync = 0;
2570 }
2571 }
2572
2573 void
libbalsa_mailbox_set_total(LibBalsaMailbox * mailbox,gint total)2574 libbalsa_mailbox_set_total(LibBalsaMailbox * mailbox, gint total)
2575 {
2576 LibBalsaMailboxView *view;
2577
2578 /* Changing the default is not allowed. */
2579 g_return_if_fail(mailbox != NULL);
2580
2581 view = lbm_get_view(mailbox);
2582
2583 if (view->total != total) {
2584 view->total = total;
2585 view->in_sync = 0;
2586 }
2587 }
2588
2589 void
libbalsa_mailbox_set_mtime(LibBalsaMailbox * mailbox,time_t mtime)2590 libbalsa_mailbox_set_mtime(LibBalsaMailbox * mailbox, time_t mtime)
2591 {
2592 LibBalsaMailboxView *view;
2593
2594 /* Changing the default is not allowed. */
2595 g_return_if_fail(mailbox != NULL);
2596
2597 view = lbm_get_view(mailbox);
2598
2599 if (view->mtime != mtime) {
2600 view->mtime = mtime;
2601 view->in_sync = 0;
2602 }
2603 }
2604
2605 /* End of set methods. */
2606
2607 /* Get methods; NULL mailbox is valid, and returns the default value. */
2608
2609 InternetAddressList *
libbalsa_mailbox_get_mailing_list_address(LibBalsaMailbox * mailbox)2610 libbalsa_mailbox_get_mailing_list_address(LibBalsaMailbox * mailbox)
2611 {
2612 return (mailbox && mailbox->view) ?
2613 mailbox->view->mailing_list_address :
2614 libbalsa_mailbox_view_default.mailing_list_address;
2615 }
2616
2617 const gchar *
libbalsa_mailbox_get_identity_name(LibBalsaMailbox * mailbox)2618 libbalsa_mailbox_get_identity_name(LibBalsaMailbox * mailbox)
2619 {
2620 return (mailbox && mailbox->view) ?
2621 mailbox->view->identity_name :
2622 libbalsa_mailbox_view_default.identity_name;
2623 }
2624
2625
2626 LibBalsaMailboxThreadingType
libbalsa_mailbox_get_threading_type(LibBalsaMailbox * mailbox)2627 libbalsa_mailbox_get_threading_type(LibBalsaMailbox * mailbox)
2628 {
2629 return (mailbox && mailbox->view) ?
2630 mailbox->view->threading_type :
2631 libbalsa_mailbox_view_default.threading_type;
2632 }
2633
2634 LibBalsaMailboxSortType
libbalsa_mailbox_get_sort_type(LibBalsaMailbox * mailbox)2635 libbalsa_mailbox_get_sort_type(LibBalsaMailbox * mailbox)
2636 {
2637 return (mailbox && mailbox->view) ?
2638 mailbox->view->sort_type : libbalsa_mailbox_view_default.sort_type;
2639 }
2640
2641 LibBalsaMailboxSortFields
libbalsa_mailbox_get_sort_field(LibBalsaMailbox * mailbox)2642 libbalsa_mailbox_get_sort_field(LibBalsaMailbox * mailbox)
2643 {
2644 return (mailbox && mailbox->view) ?
2645 mailbox->view->sort_field :
2646 libbalsa_mailbox_view_default.sort_field;
2647 }
2648
2649 LibBalsaMailboxShow
libbalsa_mailbox_get_show(LibBalsaMailbox * mailbox)2650 libbalsa_mailbox_get_show(LibBalsaMailbox * mailbox)
2651 {
2652 return (mailbox && mailbox->view) ?
2653 mailbox->view->show : libbalsa_mailbox_view_default.show;
2654 }
2655
2656 LibBalsaMailboxSubscribe
libbalsa_mailbox_get_subscribe(LibBalsaMailbox * mailbox)2657 libbalsa_mailbox_get_subscribe(LibBalsaMailbox * mailbox)
2658 {
2659 return (mailbox && mailbox->view) ?
2660 mailbox->view->subscribe : libbalsa_mailbox_view_default.subscribe;
2661 }
2662
2663 gboolean
libbalsa_mailbox_get_exposed(LibBalsaMailbox * mailbox)2664 libbalsa_mailbox_get_exposed(LibBalsaMailbox * mailbox)
2665 {
2666 return (mailbox && mailbox->view) ?
2667 mailbox->view->exposed : libbalsa_mailbox_view_default.exposed;
2668 }
2669
2670 gboolean
libbalsa_mailbox_get_open(LibBalsaMailbox * mailbox)2671 libbalsa_mailbox_get_open(LibBalsaMailbox * mailbox)
2672 {
2673 return (mailbox && mailbox->view) ?
2674 mailbox->view->open : libbalsa_mailbox_view_default.open;
2675 }
2676
2677 gint
libbalsa_mailbox_get_filter(LibBalsaMailbox * mailbox)2678 libbalsa_mailbox_get_filter(LibBalsaMailbox * mailbox)
2679 {
2680 return (mailbox && mailbox->view) ?
2681 mailbox->view->filter : libbalsa_mailbox_view_default.filter;
2682 }
2683
2684 #ifdef HAVE_GPGME
2685 LibBalsaChkCryptoMode
libbalsa_mailbox_get_crypto_mode(LibBalsaMailbox * mailbox)2686 libbalsa_mailbox_get_crypto_mode(LibBalsaMailbox * mailbox)
2687 {
2688 return (mailbox && mailbox->view) ?
2689 mailbox->view->gpg_chk_mode :
2690 libbalsa_mailbox_view_default.gpg_chk_mode;
2691 }
2692 #endif
2693
2694 gint
libbalsa_mailbox_get_unread(LibBalsaMailbox * mailbox)2695 libbalsa_mailbox_get_unread(LibBalsaMailbox * mailbox)
2696 {
2697 if (mailbox && mailbox->view) {
2698 mailbox->view->used = 1;
2699 return mailbox->view->unread;
2700 } else
2701 return libbalsa_mailbox_view_default.unread;
2702 }
2703
2704 gint
libbalsa_mailbox_get_total(LibBalsaMailbox * mailbox)2705 libbalsa_mailbox_get_total(LibBalsaMailbox * mailbox)
2706 {
2707 return (mailbox && mailbox->view) ?
2708 mailbox->view->total : libbalsa_mailbox_view_default.total;
2709 }
2710
2711 time_t
libbalsa_mailbox_get_mtime(LibBalsaMailbox * mailbox)2712 libbalsa_mailbox_get_mtime(LibBalsaMailbox * mailbox)
2713 {
2714 return (mailbox && mailbox->view) ?
2715 mailbox->view->mtime : libbalsa_mailbox_view_default.mtime;
2716 }
2717
2718 /* End of get methods. */
2719
2720 /* =================================================================== *
2721 * GtkTreeModel implementation functions. *
2722 * Important:
2723 * do not forget to modify LibBalsaMailbox::stamp on each modification
2724 * of the message list.
2725 * =================================================================== */
2726
2727 /* Iterator invalidation macros. */
2728 #define VALID_ITER(iter, tree_model) \
2729 ((iter)!= NULL && \
2730 (iter)->user_data != NULL && \
2731 LIBBALSA_IS_MAILBOX(tree_model) && \
2732 ((LibBalsaMailbox *) tree_model)->stamp == (iter)->stamp)
2733 #define VALIDATE_ITER(iter, tree_model) \
2734 ((iter)->stamp = ((LibBalsaMailbox *) tree_model)->stamp)
2735 #define INVALIDATE_ITER(iter) ((iter)->stamp = 0)
2736
2737 static GtkTreeModelFlags mbox_model_get_flags (GtkTreeModel *tree_model);
2738 static gint mbox_model_get_n_columns (GtkTreeModel *tree_model);
2739 static GType mbox_model_get_column_type (GtkTreeModel *tree_model,
2740 gint index);
2741 static gboolean mbox_model_get_iter (GtkTreeModel *tree_model,
2742 GtkTreeIter *iter,
2743 GtkTreePath *path);
2744 static GtkTreePath *mbox_model_get_path (GtkTreeModel *tree_model,
2745 GtkTreeIter *iter);
2746 static void mbox_model_get_value (GtkTreeModel *tree_model,
2747 GtkTreeIter *iter,
2748 gint column,
2749 GValue *value);
2750 static gboolean mbox_model_iter_next (GtkTreeModel *tree_model,
2751 GtkTreeIter *iter);
2752 static gboolean mbox_model_iter_children (GtkTreeModel *tree_model,
2753 GtkTreeIter *iter,
2754 GtkTreeIter *parent);
2755 static gboolean mbox_model_iter_has_child (GtkTreeModel *tree_model,
2756 GtkTreeIter *iter);
2757 static gint mbox_model_iter_n_children (GtkTreeModel *tree_model,
2758 GtkTreeIter *iter);
2759 static gboolean mbox_model_iter_nth_child (GtkTreeModel *tree_model,
2760 GtkTreeIter *iter,
2761 GtkTreeIter *parent,
2762 gint n);
2763 static gboolean mbox_model_iter_parent (GtkTreeModel *tree_model,
2764 GtkTreeIter *iter,
2765 GtkTreeIter *child);
2766
2767
2768 static GType mbox_model_col_type[LB_MBOX_N_COLS];
2769
2770 static void
mbox_model_init(GtkTreeModelIface * iface)2771 mbox_model_init(GtkTreeModelIface *iface)
2772 {
2773 iface->get_flags = mbox_model_get_flags;
2774 iface->get_n_columns = mbox_model_get_n_columns;
2775 iface->get_column_type = mbox_model_get_column_type;
2776 iface->get_iter = mbox_model_get_iter;
2777 iface->get_path = mbox_model_get_path;
2778 iface->get_value = mbox_model_get_value;
2779 iface->iter_next = mbox_model_iter_next;
2780 iface->iter_children = mbox_model_iter_children;
2781 iface->iter_has_child = mbox_model_iter_has_child;
2782 iface->iter_n_children = mbox_model_iter_n_children;
2783 iface->iter_nth_child = mbox_model_iter_nth_child;
2784 iface->iter_parent = mbox_model_iter_parent;
2785
2786 mbox_model_col_type[LB_MBOX_MSGNO_COL] = G_TYPE_UINT;
2787 mbox_model_col_type[LB_MBOX_MARKED_COL] = GDK_TYPE_PIXBUF;
2788 mbox_model_col_type[LB_MBOX_ATTACH_COL] = GDK_TYPE_PIXBUF;
2789 mbox_model_col_type[LB_MBOX_FROM_COL] = G_TYPE_STRING;
2790 mbox_model_col_type[LB_MBOX_SUBJECT_COL] = G_TYPE_STRING;
2791 mbox_model_col_type[LB_MBOX_DATE_COL] = G_TYPE_STRING;
2792 mbox_model_col_type[LB_MBOX_SIZE_COL] = G_TYPE_STRING;
2793 mbox_model_col_type[LB_MBOX_WEIGHT_COL] = G_TYPE_UINT;
2794 mbox_model_col_type[LB_MBOX_STYLE_COL] = G_TYPE_UINT;
2795 mbox_model_col_type[LB_MBOX_FOREGROUND_COL] = G_TYPE_STRING;
2796 mbox_model_col_type[LB_MBOX_FOREGROUND_SET_COL] = G_TYPE_UINT;
2797 mbox_model_col_type[LB_MBOX_BACKGROUND_COL] = G_TYPE_STRING;
2798 mbox_model_col_type[LB_MBOX_BACKGROUND_SET_COL] = G_TYPE_UINT;
2799
2800
2801 libbalsa_mbox_model_signals[ROW_CHANGED] =
2802 g_signal_lookup("row-changed", GTK_TYPE_TREE_MODEL);
2803 libbalsa_mbox_model_signals[ROW_DELETED] =
2804 g_signal_lookup("row-deleted", GTK_TYPE_TREE_MODEL);
2805 libbalsa_mbox_model_signals[ROW_HAS_CHILD_TOGGLED] =
2806 g_signal_lookup("row-has-child-toggled", GTK_TYPE_TREE_MODEL);
2807 libbalsa_mbox_model_signals[ROW_INSERTED] =
2808 g_signal_lookup("row-inserted", GTK_TYPE_TREE_MODEL);
2809 libbalsa_mbox_model_signals[ROWS_REORDERED] =
2810 g_signal_lookup("rows-reordered", GTK_TYPE_TREE_MODEL);
2811 }
2812
2813 static GtkTreeModelFlags
mbox_model_get_flags(GtkTreeModel * tree_model)2814 mbox_model_get_flags(GtkTreeModel *tree_model)
2815 {
2816 return 0;
2817 }
2818
2819 static gint
mbox_model_get_n_columns(GtkTreeModel * tree_model)2820 mbox_model_get_n_columns(GtkTreeModel *tree_model)
2821 {
2822 return LB_MBOX_N_COLS;
2823 }
2824
2825 static GType
mbox_model_get_column_type(GtkTreeModel * tree_model,gint index)2826 mbox_model_get_column_type(GtkTreeModel *tree_model, gint index)
2827 {
2828 g_return_val_if_fail(index>=0 && index <LB_MBOX_N_COLS, G_TYPE_BOOLEAN);
2829 return mbox_model_col_type[index];
2830 }
2831
2832 static gboolean
mbox_model_get_iter(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreePath * path)2833 mbox_model_get_iter(GtkTreeModel *tree_model,
2834 GtkTreeIter *iter,
2835 GtkTreePath *path)
2836 {
2837 GtkTreeIter parent;
2838 const gint *indices;
2839 gint depth, i;
2840
2841 INVALIDATE_ITER(iter);
2842 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(tree_model), FALSE);
2843
2844 indices = gtk_tree_path_get_indices(path);
2845 depth = gtk_tree_path_get_depth(path);
2846
2847 g_return_val_if_fail(depth > 0, FALSE);
2848
2849 if (!mbox_model_iter_nth_child(tree_model, iter, NULL, indices[0]))
2850 return FALSE;
2851
2852 for (i = 1; i < depth; i++) {
2853 parent = *iter;
2854 if (!mbox_model_iter_nth_child(tree_model, iter, &parent,
2855 indices[i]))
2856 return FALSE;
2857 }
2858
2859 return TRUE;
2860 }
2861
2862 static GtkTreePath *
mbox_model_get_path_helper(GNode * node,GNode * msg_tree)2863 mbox_model_get_path_helper(GNode * node, GNode * msg_tree)
2864 {
2865 GtkTreePath *path = gtk_tree_path_new();
2866
2867 while (node->parent) {
2868 gint i = g_node_child_position(node->parent, node);
2869 if (i < 0) {
2870 gtk_tree_path_free(path);
2871 return NULL;
2872 }
2873 gtk_tree_path_prepend_index(path, i);
2874 node = node->parent;
2875 }
2876
2877 if (node == msg_tree)
2878 return path;
2879 gtk_tree_path_free(path);
2880 return NULL;
2881 }
2882
2883 static GtkTreePath *
mbox_model_get_path(GtkTreeModel * tree_model,GtkTreeIter * iter)2884 mbox_model_get_path(GtkTreeModel * tree_model, GtkTreeIter * iter)
2885 {
2886 GNode *node;
2887 #ifdef SANITY_CHECK
2888 GNode *parent_node;
2889 #endif
2890
2891 g_return_val_if_fail(VALID_ITER(iter, tree_model), NULL);
2892
2893 node = iter->user_data;
2894 #ifdef SANITY_CHECK
2895 for (parent_node = node->parent; parent_node;
2896 parent_node = parent_node->parent)
2897 g_return_val_if_fail(parent_node != node, NULL);
2898 #endif
2899
2900 g_return_val_if_fail(node->parent != NULL, NULL);
2901
2902 return mbox_model_get_path_helper(node,
2903 LIBBALSA_MAILBOX(tree_model)->
2904 msg_tree);
2905 }
2906
2907 /* mbox_model_get_value:
2908 FIXME: still includes some debugging code in case fetching the
2909 message failed.
2910 */
2911
2912 static GdkPixbuf *status_icons[LIBBALSA_MESSAGE_STATUS_ICONS_NUM];
2913 static GdkPixbuf *attach_icons[LIBBALSA_MESSAGE_ATTACH_ICONS_NUM];
2914
2915 #ifdef BALSA_USE_THREADS
2916 /* Protects access to mailbox->msgnos_pending; */
2917 static pthread_mutex_t get_index_entry_lock = PTHREAD_MUTEX_INITIALIZER;
2918
2919 static void
lbm_get_index_entry_expunged_cb(LibBalsaMailbox * mailbox,guint seqno)2920 lbm_get_index_entry_expunged_cb(LibBalsaMailbox * mailbox, guint seqno)
2921 {
2922 pthread_mutex_lock(&get_index_entry_lock);
2923 lbm_update_msgnos(mailbox, seqno, mailbox->msgnos_pending);
2924 pthread_mutex_unlock(&get_index_entry_lock);
2925 }
2926
2927 static void
lbm_get_index_entry_real(LibBalsaMailbox * mailbox)2928 lbm_get_index_entry_real(LibBalsaMailbox * mailbox)
2929 {
2930 guint i;
2931
2932 #if DEBUG
2933 g_print("%s %s %d requested\n", __func__, mailbox->name,
2934 mailbox->msgnos_pending->len);
2935 #endif
2936 pthread_mutex_lock(&get_index_entry_lock);
2937 for (i = 0; i < mailbox->msgnos_pending->len; i++) {
2938 guint msgno = g_array_index(mailbox->msgnos_pending, guint, i);
2939 LibBalsaMessage *message;
2940
2941 if (!MAILBOX_OPEN(mailbox))
2942 break;
2943
2944 #if DEBUG
2945 g_print("%s %s msgno %d\n", __func__, mailbox->name, msgno);
2946 #endif
2947 pthread_mutex_unlock(&get_index_entry_lock);
2948 if ((message = libbalsa_mailbox_get_message(mailbox, msgno)))
2949 /* get-message has cached the message info, so we just unref
2950 * message. */
2951 g_object_unref(message);
2952 pthread_mutex_lock(&get_index_entry_lock);
2953 }
2954
2955 #if DEBUG
2956 g_print("%s %s %d processed\n", __func__, mailbox->name,
2957 mailbox->msgnos_pending->len);
2958 #endif
2959 mailbox->msgnos_pending->len = 0;
2960 pthread_mutex_unlock(&get_index_entry_lock);
2961
2962 g_object_unref(mailbox);
2963 }
2964 #endif /*BALSA_USE_THREADS */
2965
2966 static LibBalsaMailboxIndexEntry *
lbm_get_index_entry(LibBalsaMailbox * lmm,guint msgno)2967 lbm_get_index_entry(LibBalsaMailbox * lmm, guint msgno)
2968 {
2969 LibBalsaMailboxIndexEntry *entry;
2970
2971 if (!lmm->mindex)
2972 return NULL;
2973
2974 if (lmm->mindex->len < msgno )
2975 g_ptr_array_set_size(lmm->mindex, msgno);
2976
2977 entry = g_ptr_array_index(lmm->mindex, msgno - 1);
2978 #ifdef BALSA_USE_THREADS
2979 if (entry)
2980 return entry->idle_pending ? NULL : entry;
2981
2982 pthread_mutex_lock(&get_index_entry_lock);
2983 if (!lmm->msgnos_pending) {
2984 lmm->msgnos_pending = g_array_new(FALSE, FALSE, sizeof(guint));
2985 g_signal_connect(lmm, "message-expunged",
2986 G_CALLBACK(lbm_get_index_entry_expunged_cb), NULL);
2987 }
2988
2989 if (!lmm->msgnos_pending->len) {
2990 pthread_t get_index_entry_thread;
2991
2992 g_object_ref(lmm);
2993 pthread_create(&get_index_entry_thread, NULL,
2994 (void *) lbm_get_index_entry_real, lmm);
2995 pthread_detach(get_index_entry_thread);
2996 }
2997
2998 g_array_append_val(lmm->msgnos_pending, msgno);
2999 /* Make sure we have a "pending" index entry before releasing the
3000 * lock. */
3001 g_ptr_array_index(lmm->mindex, msgno - 1) =
3002 lbm_index_entry_new_pending();
3003 pthread_mutex_unlock(&get_index_entry_lock);
3004 #else /*BALSA_USE_THREADS */
3005 if (!entry) {
3006 LibBalsaMessage *message =
3007 libbalsa_mailbox_get_message(lmm, msgno);
3008 if (message) {
3009 /* get-message has cached the message info, so we just unref
3010 * message. */
3011 g_object_unref(message);
3012 entry = g_ptr_array_index(lmm->mindex, msgno - 1);
3013 }
3014 }
3015 #endif /*BALSA_USE_THREADS */
3016
3017 return entry;
3018 }
3019
3020 gchar *libbalsa_mailbox_date_format;
3021 static void
mbox_model_get_value(GtkTreeModel * tree_model,GtkTreeIter * iter,gint column,GValue * value)3022 mbox_model_get_value(GtkTreeModel *tree_model,
3023 GtkTreeIter *iter,
3024 gint column,
3025 GValue *value)
3026 {
3027 LibBalsaMailbox* lmm = LIBBALSA_MAILBOX(tree_model);
3028 LibBalsaMailboxIndexEntry* msg = NULL;
3029 guint msgno;
3030 gchar *tmp;
3031
3032 g_return_if_fail(VALID_ITER(iter, tree_model));
3033 g_return_if_fail(column >= 0 &&
3034 column < (int) ELEMENTS(mbox_model_col_type));
3035
3036 g_value_init (value, mbox_model_col_type[column]);
3037 msgno = GPOINTER_TO_UINT( ((GNode*)iter->user_data)->data );
3038
3039 if(column == LB_MBOX_MSGNO_COL) {
3040 g_value_set_uint(value, msgno);
3041 return;
3042 }
3043 g_return_if_fail(msgno<=libbalsa_mailbox_total_messages(lmm));
3044 msg = lbm_get_index_entry(lmm, msgno);
3045 switch(column) {
3046 /* case LB_MBOX_MSGNO_COL: handled above */
3047 case LB_MBOX_MARKED_COL:
3048 if (msg && msg->status_icon < LIBBALSA_MESSAGE_STATUS_ICONS_NUM)
3049 g_value_set_object(value, status_icons[msg->status_icon]);
3050 break;
3051 case LB_MBOX_ATTACH_COL:
3052 if (msg && msg->attach_icon < LIBBALSA_MESSAGE_ATTACH_ICONS_NUM)
3053 g_value_set_object(value, attach_icons[msg->attach_icon]);
3054 break;
3055 case LB_MBOX_FROM_COL:
3056 if(msg) {
3057 if (msg->from)
3058 g_value_set_string(value, msg->from);
3059 else
3060 g_value_set_static_string(value, _("from unknown"));
3061 } else
3062 g_value_set_static_string(value, _("Loading..."));
3063 break;
3064 case LB_MBOX_SUBJECT_COL:
3065 if(msg) g_value_set_string(value, msg->subject);
3066 break;
3067 case LB_MBOX_DATE_COL:
3068 if(msg) {
3069 tmp = libbalsa_date_to_utf8(&msg->msg_date,
3070 libbalsa_mailbox_date_format);
3071 g_value_take_string(value, tmp);
3072 }
3073 break;
3074 case LB_MBOX_SIZE_COL:
3075 if(msg) {
3076 tmp = libbalsa_size_to_gchar(msg->size);
3077 g_value_take_string(value, tmp);
3078 }
3079 else g_value_set_static_string(value, " ");
3080 break;
3081 case LB_MBOX_WEIGHT_COL:
3082 g_value_set_uint(value, msg && msg->unseen
3083 ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL);
3084 break;
3085 case LB_MBOX_STYLE_COL:
3086 g_value_set_uint(value, msg &&
3087 lbm_node_has_unseen_child(lmm,
3088 (GNode *) iter->user_data)
3089 ? PANGO_STYLE_OBLIQUE : PANGO_STYLE_NORMAL);
3090 break;
3091 case LB_MBOX_FOREGROUND_COL:
3092 if(msg) {
3093 tmp = g_strdup(msg->foreground);
3094 g_value_take_string(value, tmp);
3095 }
3096 break;
3097 case LB_MBOX_FOREGROUND_SET_COL:
3098 g_value_set_uint(value, msg && msg->foreground_set);
3099 break;
3100 case LB_MBOX_BACKGROUND_COL:
3101 if(msg) {
3102 tmp = g_strdup(msg->background);
3103 g_value_take_string(value, tmp);
3104 }
3105 break;
3106 case LB_MBOX_BACKGROUND_SET_COL:
3107 g_value_set_uint(value, msg && msg->background_set);
3108 break;
3109 }
3110 }
3111
3112 static gboolean
mbox_model_iter_next(GtkTreeModel * tree_model,GtkTreeIter * iter)3113 mbox_model_iter_next(GtkTreeModel *tree_model,
3114 GtkTreeIter *iter)
3115 {
3116 GNode *node;
3117 g_return_val_if_fail(VALID_ITER(iter, tree_model), FALSE);
3118
3119 node = iter->user_data;
3120 if(node && (node = node->next)) {
3121 iter->user_data = node;
3122 VALIDATE_ITER(iter, tree_model);
3123 return TRUE;
3124 } else {
3125 INVALIDATE_ITER(iter);
3126 return FALSE;
3127 }
3128 }
3129
3130 static gboolean
mbox_model_iter_children(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreeIter * parent)3131 mbox_model_iter_children(GtkTreeModel *tree_model,
3132 GtkTreeIter *iter,
3133 GtkTreeIter *parent)
3134 {
3135 GNode *node;
3136
3137 INVALIDATE_ITER(iter);
3138 g_return_val_if_fail(parent == NULL ||
3139 VALID_ITER(parent, tree_model), FALSE);
3140
3141 node = parent ? parent->user_data
3142 : LIBBALSA_MAILBOX(tree_model)->msg_tree;
3143 node = node->children;
3144 if (node) {
3145 iter->user_data = node;
3146 VALIDATE_ITER(iter, tree_model);
3147 return TRUE;
3148 } else
3149 return FALSE;
3150 }
3151
3152 static gboolean
mbox_model_iter_has_child(GtkTreeModel * tree_model,GtkTreeIter * iter)3153 mbox_model_iter_has_child(GtkTreeModel * tree_model,
3154 GtkTreeIter * iter)
3155 {
3156 GNode *node;
3157
3158 g_return_val_if_fail(VALID_ITER(iter, LIBBALSA_MAILBOX(tree_model)),
3159 FALSE);
3160
3161 node = iter->user_data;
3162
3163 return (node->children != NULL);
3164 }
3165
3166 static gint
mbox_model_iter_n_children(GtkTreeModel * tree_model,GtkTreeIter * iter)3167 mbox_model_iter_n_children(GtkTreeModel *tree_model,
3168 GtkTreeIter *iter)
3169 {
3170 GNode *node;
3171
3172 g_return_val_if_fail(iter == NULL || VALID_ITER(iter, tree_model), 0);
3173
3174 node = iter ? iter->user_data
3175 : LIBBALSA_MAILBOX(tree_model)->msg_tree;
3176
3177 return node ? g_node_n_children(node) : 0;
3178 }
3179
3180 static gboolean
mbox_model_iter_nth_child(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreeIter * parent,gint n)3181 mbox_model_iter_nth_child(GtkTreeModel * tree_model,
3182 GtkTreeIter * iter,
3183 GtkTreeIter * parent,
3184 gint n)
3185 {
3186 GNode *node;
3187
3188 INVALIDATE_ITER(iter);
3189 if(!LIBBALSA_MAILBOX(tree_model)->msg_tree)
3190 return FALSE; /* really, this should be never called when
3191 * msg_tree == NULL but the FALSE response is
3192 * fair as well and only a bit dirtier.
3193 * I have more critical problems to debug now. */
3194 g_return_val_if_fail(parent == NULL
3195 ||VALID_ITER(parent, tree_model), FALSE);
3196
3197 node = parent ? parent->user_data
3198 : LIBBALSA_MAILBOX(tree_model)->msg_tree;
3199 if(!node) /* the tree has been destroyed already (mailbox has been
3200 * closed), there is nothing to iterate over. This happens
3201 * only if mailbox is closed but a view is still active.
3202 */
3203 return FALSE;
3204 node = g_node_nth_child(node, n);
3205
3206 if (node) {
3207 iter->user_data = node;
3208 VALIDATE_ITER(iter, tree_model);
3209 return TRUE;
3210 } else
3211 return FALSE;
3212 }
3213
3214 static gboolean
mbox_model_iter_parent(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreeIter * child)3215 mbox_model_iter_parent(GtkTreeModel * tree_model,
3216 GtkTreeIter * iter,
3217 GtkTreeIter * child)
3218 {
3219 GNode *node;
3220
3221 INVALIDATE_ITER(iter);
3222 g_return_val_if_fail(iter != NULL, FALSE);
3223 g_return_val_if_fail(VALID_ITER(child, tree_model), FALSE);
3224
3225 node = child->user_data;
3226 node = node->parent;
3227 if (node && node != LIBBALSA_MAILBOX(tree_model)->msg_tree) {
3228 iter->user_data = node;
3229 VALIDATE_ITER(iter, tree_model);
3230 return TRUE;
3231 } else
3232 return FALSE;
3233 }
3234
3235 /* Set icons used in tree view. */
3236 static void
libbalsa_mailbox_set_icon(GdkPixbuf * pixbuf,GdkPixbuf ** pixbuf_store)3237 libbalsa_mailbox_set_icon(GdkPixbuf * pixbuf, GdkPixbuf ** pixbuf_store)
3238 {
3239 if (*pixbuf_store)
3240 g_object_unref(*pixbuf_store);
3241 *pixbuf_store = pixbuf;
3242 }
3243
3244 /* Icons for status column. */
3245 void
libbalsa_mailbox_set_unread_icon(GdkPixbuf * pixbuf)3246 libbalsa_mailbox_set_unread_icon(GdkPixbuf * pixbuf)
3247 {
3248 libbalsa_mailbox_set_icon(pixbuf,
3249 &status_icons
3250 [LIBBALSA_MESSAGE_STATUS_UNREAD]);
3251 }
3252
libbalsa_mailbox_set_trash_icon(GdkPixbuf * pixbuf)3253 void libbalsa_mailbox_set_trash_icon(GdkPixbuf * pixbuf)
3254 {
3255 libbalsa_mailbox_set_icon(pixbuf,
3256 &status_icons
3257 [LIBBALSA_MESSAGE_STATUS_DELETED]);
3258 }
3259
libbalsa_mailbox_set_flagged_icon(GdkPixbuf * pixbuf)3260 void libbalsa_mailbox_set_flagged_icon(GdkPixbuf * pixbuf)
3261 {
3262 libbalsa_mailbox_set_icon(pixbuf,
3263 &status_icons
3264 [LIBBALSA_MESSAGE_STATUS_FLAGGED]);
3265 }
3266
libbalsa_mailbox_set_replied_icon(GdkPixbuf * pixbuf)3267 void libbalsa_mailbox_set_replied_icon(GdkPixbuf * pixbuf)
3268 {
3269 libbalsa_mailbox_set_icon(pixbuf,
3270 &status_icons
3271 [LIBBALSA_MESSAGE_STATUS_REPLIED]);
3272 }
3273
3274 /* Icons for attachment column. */
libbalsa_mailbox_set_attach_icon(GdkPixbuf * pixbuf)3275 void libbalsa_mailbox_set_attach_icon(GdkPixbuf * pixbuf)
3276 {
3277 libbalsa_mailbox_set_icon(pixbuf,
3278 &attach_icons
3279 [LIBBALSA_MESSAGE_ATTACH_ATTACH]);
3280 }
3281
3282 #ifdef HAVE_GPGME
libbalsa_mailbox_set_good_icon(GdkPixbuf * pixbuf)3283 void libbalsa_mailbox_set_good_icon(GdkPixbuf * pixbuf)
3284 {
3285 libbalsa_mailbox_set_icon(pixbuf,
3286 &attach_icons
3287 [LIBBALSA_MESSAGE_ATTACH_GOOD]);
3288 }
3289
libbalsa_mailbox_set_notrust_icon(GdkPixbuf * pixbuf)3290 void libbalsa_mailbox_set_notrust_icon(GdkPixbuf * pixbuf)
3291 {
3292 libbalsa_mailbox_set_icon(pixbuf,
3293 &attach_icons
3294 [LIBBALSA_MESSAGE_ATTACH_NOTRUST]);
3295 }
3296
libbalsa_mailbox_set_bad_icon(GdkPixbuf * pixbuf)3297 void libbalsa_mailbox_set_bad_icon(GdkPixbuf * pixbuf)
3298 {
3299 libbalsa_mailbox_set_icon(pixbuf,
3300 &attach_icons
3301 [LIBBALSA_MESSAGE_ATTACH_BAD]);
3302 }
3303
libbalsa_mailbox_set_sign_icon(GdkPixbuf * pixbuf)3304 void libbalsa_mailbox_set_sign_icon(GdkPixbuf * pixbuf)
3305 {
3306 libbalsa_mailbox_set_icon(pixbuf,
3307 &attach_icons
3308 [LIBBALSA_MESSAGE_ATTACH_SIGN]);
3309 }
3310
libbalsa_mailbox_set_encr_icon(GdkPixbuf * pixbuf)3311 void libbalsa_mailbox_set_encr_icon(GdkPixbuf * pixbuf)
3312 {
3313 libbalsa_mailbox_set_icon(pixbuf,
3314 &attach_icons
3315 [LIBBALSA_MESSAGE_ATTACH_ENCR]);
3316 }
3317 #endif /* HAVE_GPGME */
3318
3319 /* =================================================================== *
3320 * GtkTreeDragSource implementation functions. *
3321 * =================================================================== */
3322
3323 static gboolean mbox_row_draggable(GtkTreeDragSource * drag_source,
3324 GtkTreePath * path);
3325 static gboolean mbox_drag_data_delete(GtkTreeDragSource * drag_source,
3326 GtkTreePath * path);
3327 static gboolean mbox_drag_data_get(GtkTreeDragSource * drag_source,
3328 GtkTreePath * path,
3329 GtkSelectionData * selection_data);
3330
3331 static void
mbox_drag_source_init(GtkTreeDragSourceIface * iface)3332 mbox_drag_source_init(GtkTreeDragSourceIface * iface)
3333 {
3334 iface->row_draggable = mbox_row_draggable;
3335 iface->drag_data_delete = mbox_drag_data_delete;
3336 iface->drag_data_get = mbox_drag_data_get;
3337 }
3338
3339 /* These three methods are apparently never called, so what they return
3340 * is irrelevant. The code reflects guesses about what they should
3341 * return if they were ever called.
3342 */
3343 static gboolean
mbox_row_draggable(GtkTreeDragSource * drag_source,GtkTreePath * path)3344 mbox_row_draggable(GtkTreeDragSource * drag_source, GtkTreePath * path)
3345 {
3346 /* All rows are valid sources. */
3347 return TRUE;
3348 }
3349
3350 static gboolean
mbox_drag_data_delete(GtkTreeDragSource * drag_source,GtkTreePath * path)3351 mbox_drag_data_delete(GtkTreeDragSource * drag_source, GtkTreePath * path)
3352 {
3353 /* The "drag-data-received" callback handles deleting messages that
3354 * are dragged out of the mailbox, so we don't. */
3355 return FALSE;
3356 }
3357
3358 static gboolean
mbox_drag_data_get(GtkTreeDragSource * drag_source,GtkTreePath * path,GtkSelectionData * selection_data)3359 mbox_drag_data_get(GtkTreeDragSource * drag_source, GtkTreePath * path,
3360 GtkSelectionData * selection_data)
3361 {
3362 /* The "drag-data-get" callback passes the list of selected messages
3363 * to the GtkSelectionData, so we don't. */
3364 return FALSE;
3365 }
3366
3367 /* =================================================================== *
3368 * GtkTreeSortable implementation functions. *
3369 * =================================================================== */
3370
3371 static gboolean mbox_get_sort_column_id(GtkTreeSortable * sortable,
3372 gint * sort_column_id,
3373 GtkSortType * order);
3374 static void mbox_set_sort_column_id(GtkTreeSortable * sortable,
3375 gint sort_column_id,
3376 GtkSortType order);
3377 static void mbox_set_sort_func(GtkTreeSortable * sortable,
3378 gint sort_column_id,
3379 GtkTreeIterCompareFunc func, gpointer data,
3380 GDestroyNotify destroy);
3381 static void mbox_set_default_sort_func(GtkTreeSortable * sortable,
3382 GtkTreeIterCompareFunc func,
3383 gpointer data,
3384 GDestroyNotify destroy);
3385 static gboolean mbox_has_default_sort_func(GtkTreeSortable * sortable);
3386
3387 static void
mbox_sortable_init(GtkTreeSortableIface * iface)3388 mbox_sortable_init(GtkTreeSortableIface * iface)
3389 {
3390 iface->get_sort_column_id = mbox_get_sort_column_id;
3391 iface->set_sort_column_id = mbox_set_sort_column_id;
3392 iface->set_sort_func = mbox_set_sort_func;
3393 iface->set_default_sort_func = mbox_set_default_sort_func;
3394 iface->has_default_sort_func = mbox_has_default_sort_func;
3395 }
3396
3397 static gint
mbox_compare_from(LibBalsaMailboxIndexEntry * message_a,LibBalsaMailboxIndexEntry * message_b)3398 mbox_compare_from(LibBalsaMailboxIndexEntry * message_a,
3399 LibBalsaMailboxIndexEntry * message_b)
3400 {
3401 return g_ascii_strcasecmp(message_a->from, message_b->from);
3402 }
3403
3404 static gint
mbox_compare_subject(LibBalsaMailboxIndexEntry * message_a,LibBalsaMailboxIndexEntry * message_b)3405 mbox_compare_subject(LibBalsaMailboxIndexEntry * message_a,
3406 LibBalsaMailboxIndexEntry * message_b)
3407 {
3408 return g_ascii_strcasecmp(message_a->subject, message_b->subject);
3409 }
3410
3411 static gint
mbox_compare_date(LibBalsaMailboxIndexEntry * message_a,LibBalsaMailboxIndexEntry * message_b)3412 mbox_compare_date(LibBalsaMailboxIndexEntry * message_a,
3413 LibBalsaMailboxIndexEntry * message_b)
3414 {
3415 return message_a->msg_date - message_b->msg_date;
3416 }
3417
3418 static gint
mbox_compare_size(LibBalsaMailboxIndexEntry * message_a,LibBalsaMailboxIndexEntry * message_b)3419 mbox_compare_size(LibBalsaMailboxIndexEntry * message_a,
3420 LibBalsaMailboxIndexEntry * message_b)
3421 {
3422 return message_a->size - message_b->size;
3423 }
3424
3425 static gint
mbox_compare_func(const SortTuple * a,const SortTuple * b,LibBalsaMailbox * mbox)3426 mbox_compare_func(const SortTuple * a,
3427 const SortTuple * b,
3428 LibBalsaMailbox * mbox)
3429 {
3430 guint msgno_a;
3431 guint msgno_b;
3432 gint retval;
3433
3434 msgno_a = GPOINTER_TO_UINT(a->node->data);
3435 msgno_b = GPOINTER_TO_UINT(b->node->data);
3436 if (mbox->view->sort_field == LB_MAILBOX_SORT_NO)
3437 retval = msgno_a - msgno_b;
3438 else {
3439 LibBalsaMailboxIndexEntry *message_a;
3440 LibBalsaMailboxIndexEntry *message_b;
3441
3442 message_a = g_ptr_array_index(mbox->mindex, msgno_a - 1);
3443 message_b = g_ptr_array_index(mbox->mindex, msgno_b - 1);
3444
3445 if (!(VALID_ENTRY(message_a) && VALID_ENTRY(message_b)))
3446 return 0;
3447
3448 switch (mbox->view->sort_field) {
3449 case LB_MAILBOX_SORT_SENDER:
3450 retval = mbox_compare_from(message_a, message_b);
3451 break;
3452 case LB_MAILBOX_SORT_SUBJECT:
3453 retval = mbox_compare_subject(message_a, message_b);
3454 break;
3455 case LB_MAILBOX_SORT_DATE:
3456 retval = mbox_compare_date(message_a, message_b);
3457 break;
3458 case LB_MAILBOX_SORT_SIZE:
3459 retval = mbox_compare_size(message_a, message_b);
3460 break;
3461 default:
3462 retval = 0;
3463 break;
3464 }
3465
3466 if (retval == 0) {
3467 /* resolve ties using previous sort column */
3468 switch (mbox->view->sort_field_prev) {
3469 case LB_MAILBOX_SORT_SENDER:
3470 retval = mbox_compare_from(message_a, message_b);
3471 break;
3472 case LB_MAILBOX_SORT_SUBJECT:
3473 retval = mbox_compare_subject(message_a, message_b);
3474 break;
3475 case LB_MAILBOX_SORT_DATE:
3476 retval = mbox_compare_date(message_a, message_b);
3477 break;
3478 case LB_MAILBOX_SORT_SIZE:
3479 retval = mbox_compare_size(message_a, message_b);
3480 break;
3481 default:
3482 retval = 0;
3483 break;
3484 }
3485 }
3486 }
3487
3488 if (mbox->view->sort_type == LB_MAILBOX_SORT_TYPE_DESC) {
3489 retval = -retval;
3490 }
3491
3492 return retval;
3493 }
3494
3495 /*
3496 * Sort part of the mailbox tree.
3497 *
3498 * We may not have the sort fields for all nodes, so we build an array
3499 * for only the nodes where we do have them.
3500 */
3501 static gboolean
lbm_has_valid_index_entry(LibBalsaMailbox * mailbox,guint msgno)3502 lbm_has_valid_index_entry(LibBalsaMailbox * mailbox, guint msgno)
3503 {
3504 LibBalsaMailboxIndexEntry *entry;
3505
3506 if (msgno > mailbox->mindex->len)
3507 return FALSE;
3508
3509 entry = g_ptr_array_index(mailbox->mindex, msgno - 1);
3510
3511 return VALID_ENTRY(entry);
3512 }
3513
3514 static void
lbm_sort(LibBalsaMailbox * mbox,GNode * parent)3515 lbm_sort(LibBalsaMailbox * mbox, GNode * parent)
3516 {
3517 GtkTreeIter iter;
3518 GArray *sort_array;
3519 GPtrArray *node_array;
3520 GNode *node, *tmp_node, *prev;
3521 guint i, j;
3522 gint *new_order;
3523 GtkTreePath *path;
3524 gboolean sort_no = mbox->view->sort_field == LB_MAILBOX_SORT_NO;
3525 #if !defined(LOCAL_MAILBOX_SORTED_JUST_ONCE_ON_OPENING)
3526 gboolean can_sort_all = sort_no || LIBBALSA_IS_MAILBOX_IMAP(mbox);
3527 #else
3528 gboolean can_sort_all = 1;
3529 #endif
3530 node = parent->children;
3531 if (!node)
3532 return;
3533
3534 if (node->next == NULL) {
3535 lbm_sort(mbox, node);
3536 return;
3537 }
3538
3539 sort_array = g_array_new(FALSE, FALSE, sizeof(SortTuple));
3540 node_array = g_ptr_array_new();
3541 for (tmp_node = node; tmp_node; tmp_node = tmp_node->next) {
3542 SortTuple sort_tuple;
3543 guint msgno = GPOINTER_TO_UINT(tmp_node->data);
3544
3545 if (can_sort_all || lbm_has_valid_index_entry(mbox, msgno)) {
3546 /* We have the sort fields. */
3547 sort_tuple.offset = node_array->len;
3548 sort_tuple.node = tmp_node;
3549 g_array_append_val(sort_array, sort_tuple);
3550 }
3551 g_ptr_array_add(node_array, tmp_node);
3552 }
3553
3554 if (sort_array->len <= 1) {
3555 g_array_free(sort_array, TRUE);
3556 g_ptr_array_free(node_array, TRUE);
3557 lbm_sort(mbox, node);
3558 return;
3559 }
3560 LIBBALSA_MAILBOX_GET_CLASS(mbox)->sort(mbox, sort_array);
3561
3562 /* Step through the nodes in original order. */
3563 prev = NULL;
3564 for (i = j = 0; i < node_array->len; i++) {
3565 guint msgno;
3566
3567 tmp_node = g_ptr_array_index(node_array, i);
3568 msgno = GPOINTER_TO_UINT(tmp_node->data);
3569 if (can_sort_all || lbm_has_valid_index_entry(mbox, msgno)) {
3570 /* This is one of the nodes we sorted: find out which one
3571 * goes here. */
3572 g_assert(j < sort_array->len);
3573 tmp_node = g_array_index(sort_array, SortTuple, j++).node;
3574 }
3575 if (tmp_node->prev != prev) {
3576 /* Change the links. */
3577 if (prev)
3578 prev->next = tmp_node;
3579 else
3580 node = parent->children = tmp_node;
3581 tmp_node->prev = prev;
3582 mbox->msg_tree_changed = TRUE;
3583 } else
3584 g_assert(prev == NULL || prev->next == tmp_node);
3585 prev = tmp_node;
3586 }
3587 prev->next = NULL;
3588
3589 /* Let the world know about our new order */
3590 new_order = g_new(gint, node_array->len);
3591 i = j = 0;
3592 for (tmp_node = node; tmp_node; tmp_node = tmp_node->next) {
3593 guint msgno = GPOINTER_TO_UINT(tmp_node->data);
3594 new_order[j] = can_sort_all || lbm_has_valid_index_entry(mbox, msgno)
3595 ? g_array_index(sort_array, SortTuple, i++).offset
3596 : j;
3597 j++;
3598 }
3599
3600 iter.stamp = mbox->stamp;
3601 iter.user_data = parent;
3602 path = parent->parent ? mbox_model_get_path(GTK_TREE_MODEL(mbox), &iter)
3603 : gtk_tree_path_new();
3604 gtk_tree_model_rows_reordered(GTK_TREE_MODEL(mbox),
3605 path, &iter, new_order);
3606 gtk_tree_path_free(path);
3607 g_free(new_order);
3608 g_array_free(sort_array, TRUE);
3609 g_ptr_array_free(node_array, TRUE);
3610
3611 for (tmp_node = node; tmp_node; tmp_node = tmp_node->next)
3612 lbm_sort(mbox, tmp_node);
3613 }
3614
3615 /* called from gtk-tree-view-column */
3616 static gboolean
mbox_get_sort_column_id(GtkTreeSortable * sortable,gint * sort_column_id,GtkSortType * order)3617 mbox_get_sort_column_id(GtkTreeSortable * sortable,
3618 gint * sort_column_id,
3619 GtkSortType * order)
3620 {
3621 LibBalsaMailbox *mbox = (LibBalsaMailbox *) sortable;
3622
3623 g_return_val_if_fail(LIBBALSA_IS_MAILBOX(sortable), FALSE);
3624
3625 if (sort_column_id) {
3626 switch (mbox->view->sort_field) {
3627 default:
3628 case LB_MAILBOX_SORT_NO:
3629 *sort_column_id = LB_MBOX_MSGNO_COL;
3630 break;
3631 case LB_MAILBOX_SORT_SENDER:
3632 *sort_column_id = LB_MBOX_FROM_COL;
3633 break;
3634 case LB_MAILBOX_SORT_SUBJECT:
3635 *sort_column_id = LB_MBOX_SUBJECT_COL;
3636 break;
3637 case LB_MAILBOX_SORT_DATE:
3638 *sort_column_id = LB_MBOX_DATE_COL;
3639 break;
3640 case LB_MAILBOX_SORT_SIZE:
3641 *sort_column_id = LB_MBOX_SIZE_COL;
3642 break;
3643 }
3644 }
3645
3646 if (order)
3647 *order = (mbox->view->sort_type ==
3648 LB_MAILBOX_SORT_TYPE_DESC ? GTK_SORT_DESCENDING :
3649 GTK_SORT_ASCENDING);
3650
3651 return TRUE;
3652 }
3653
3654 /* called from gtk-tree-view-column */
3655 static void
mbox_set_sort_column_id(GtkTreeSortable * sortable,gint sort_column_id,GtkSortType order)3656 mbox_set_sort_column_id(GtkTreeSortable * sortable,
3657 gint sort_column_id,
3658 GtkSortType order)
3659 {
3660 LibBalsaMailbox *mbox = (LibBalsaMailbox *) sortable;
3661 LibBalsaMailboxView *view;
3662 LibBalsaMailboxSortFields new_field;
3663 LibBalsaMailboxSortType new_type;
3664
3665 g_return_if_fail(LIBBALSA_IS_MAILBOX(sortable));
3666
3667 view = mbox->view;
3668
3669 switch (sort_column_id) {
3670 default:
3671 case LB_MBOX_MSGNO_COL:
3672 new_field = LB_MAILBOX_SORT_NO;
3673 break;
3674 case LB_MBOX_FROM_COL:
3675 new_field = LB_MAILBOX_SORT_SENDER;
3676 break;
3677 case LB_MBOX_SUBJECT_COL:
3678 new_field = LB_MAILBOX_SORT_SUBJECT;
3679 break;
3680 case LB_MBOX_DATE_COL:
3681 new_field = LB_MAILBOX_SORT_DATE;
3682 break;
3683 case LB_MBOX_SIZE_COL:
3684 new_field = LB_MAILBOX_SORT_SIZE;
3685 break;
3686 }
3687
3688 new_type = (order == GTK_SORT_DESCENDING ? LB_MAILBOX_SORT_TYPE_DESC :
3689 LB_MAILBOX_SORT_TYPE_ASC);
3690
3691 if (view->sort_field == new_field && view->sort_type == new_type)
3692 return;
3693
3694 if (view->sort_field != new_field) {
3695 view->sort_field_prev = view->sort_field;
3696 view->sort_field = new_field;
3697 }
3698 view->sort_type = new_type;
3699 view->in_sync = 0;
3700
3701 gtk_tree_sortable_sort_column_changed(sortable);
3702
3703 if (new_field != LB_MAILBOX_SORT_NO) {
3704 gboolean rc;
3705
3706 gdk_threads_leave();
3707 rc = libbalsa_mailbox_prepare_threading(mbox, 0);
3708 gdk_threads_enter();
3709
3710 if (!rc)
3711 /* Prepare-threading failed--perhaps mailbox was closed. */
3712 return;
3713 }
3714 libbalsa_lock_mailbox(mbox);
3715 lbm_sort(mbox, mbox->msg_tree);
3716 libbalsa_unlock_mailbox(mbox);
3717
3718 libbalsa_mailbox_changed(mbox);
3719 }
3720
3721 static void
mbox_set_sort_func(GtkTreeSortable * sortable,gint sort_column_id,GtkTreeIterCompareFunc func,gpointer data,GDestroyNotify destroy)3722 mbox_set_sort_func(GtkTreeSortable * sortable,
3723 gint sort_column_id,
3724 GtkTreeIterCompareFunc func,
3725 gpointer data, GDestroyNotify destroy)
3726 {
3727 g_warning("%s called but not implemented.\n", __func__);
3728 }
3729
3730 static void
mbox_set_default_sort_func(GtkTreeSortable * sortable,GtkTreeIterCompareFunc func,gpointer data,GDestroyNotify destroy)3731 mbox_set_default_sort_func(GtkTreeSortable * sortable,
3732 GtkTreeIterCompareFunc func,
3733 gpointer data, GDestroyNotify destroy)
3734 {
3735 g_warning("%s called but not implemented.\n", __func__);
3736 }
3737
3738 /* called from gtk-tree-view-column */
3739 static gboolean
mbox_has_default_sort_func(GtkTreeSortable * sortable)3740 mbox_has_default_sort_func(GtkTreeSortable * sortable)
3741 {
3742 return FALSE;
3743 }
3744
3745 /* Helpers for set-threading-type. */
3746 void
libbalsa_mailbox_unlink_and_prepend(LibBalsaMailbox * mailbox,GNode * node,GNode * parent)3747 libbalsa_mailbox_unlink_and_prepend(LibBalsaMailbox * mailbox,
3748 GNode * node, GNode * parent)
3749 {
3750 GtkTreeIter iter;
3751 GtkTreePath *path;
3752 GNode *current_parent;
3753
3754 g_return_if_fail(node != NULL);
3755 g_return_if_fail(parent != node);
3756 #ifdef SANITY_CHECK
3757 g_return_if_fail(!parent || !g_node_is_ancestor(node, parent));
3758 #endif
3759
3760 gdk_threads_enter();
3761 iter.stamp = mailbox->stamp;
3762
3763 path = mbox_model_get_path_helper(node, mailbox->msg_tree);
3764 current_parent = node->parent;
3765 g_node_unlink(node);
3766 if (path) {
3767 /* The node was in mailbox->msg_tree. */
3768 g_signal_emit(mailbox,
3769 libbalsa_mbox_model_signals[ROW_DELETED], 0, path);
3770 if (!current_parent->children) {
3771 /* It was the last child. */
3772 gtk_tree_path_up(path);
3773 iter.user_data = current_parent;
3774 g_signal_emit(mailbox,
3775 libbalsa_mbox_model_signals[ROW_HAS_CHILD_TOGGLED],
3776 0, path, &iter);
3777 }
3778 gtk_tree_path_free(path);
3779 }
3780
3781 if (!parent) {
3782 g_node_destroy(node);
3783 return;
3784 }
3785
3786 g_node_prepend(parent, node);
3787 path = mbox_model_get_path_helper(parent, mailbox->msg_tree);
3788 if (path) {
3789 /* The parent is in mailbox->msg_tree. */
3790 if (!node->next) {
3791 /* It is the first child. */
3792 iter.user_data = parent;
3793 g_signal_emit(mailbox,
3794 libbalsa_mbox_model_signals[ROW_HAS_CHILD_TOGGLED],
3795 0, path, &iter);
3796 }
3797 gtk_tree_path_down(path);
3798 iter.user_data = node;
3799 g_signal_emit(mailbox,
3800 libbalsa_mbox_model_signals[ROW_INSERTED], 0,
3801 path, &iter);
3802 if (node->children)
3803 g_signal_emit(mailbox,
3804 libbalsa_mbox_model_signals[ROW_HAS_CHILD_TOGGLED],
3805 0, path, &iter);
3806 gtk_tree_path_free(path);
3807
3808 mailbox->msg_tree_changed = TRUE;
3809 }
3810 gdk_threads_leave();
3811 }
3812
3813 struct lbm_update_msg_tree_info {
3814 LibBalsaMailbox *mailbox;
3815 GNode *new_tree;
3816 GNode **nodes;
3817 guint total;
3818 };
3819
3820 /* GNodeTraverseFunc for making a reverse lookup array into a tree. */
3821 static gboolean
lbm_update_msg_tree_populate(GNode * node,struct lbm_update_msg_tree_info * mti)3822 lbm_update_msg_tree_populate(GNode * node,
3823 struct lbm_update_msg_tree_info *mti)
3824 {
3825 guint msgno;
3826
3827 msgno = GPOINTER_TO_UINT(node->data);
3828
3829 if (msgno < mti->total)
3830 mti->nodes[msgno] = node;
3831
3832 return FALSE;
3833 }
3834
3835 /* GNodeTraverseFunc for pruning the current tree; mti->nodes is a
3836 * reverse lookup array into the new tree, so a NULL value is a node
3837 * that doesn't appear in the new tree. */
3838 static gboolean
lbm_update_msg_tree_prune(GNode * node,struct lbm_update_msg_tree_info * mti)3839 lbm_update_msg_tree_prune(GNode * node,
3840 struct lbm_update_msg_tree_info *mti)
3841 {
3842 guint msgno;
3843
3844 msgno = GPOINTER_TO_UINT(node->data);
3845 if (msgno >= mti->total || !mti->nodes[msgno]) {
3846 /* It's a bottom-up traverse, so the node's remaining children
3847 * are all in the new tree; we'll promote them to be children of
3848 * the node's parent, which might even be where they finish up. */
3849 while (node->children)
3850 libbalsa_mailbox_unlink_and_prepend(mti->mailbox,
3851 node->children,
3852 node->parent);
3853 /* Now we can destroy the node. */
3854 libbalsa_mailbox_unlink_and_prepend(mti->mailbox, node, NULL);
3855 }
3856
3857 return FALSE;
3858 }
3859
3860 /* GNodeTraverseFunc for checking parent-child relationships; mti->nodes
3861 * is a reverse lookup array into the old tree, so a NULL value means a
3862 * node that isn't in the current tree, and we insert one; because the
3863 * traverse is top-down, a missing parent will have been inserted before
3864 * we get to the child. */
3865 static gboolean
lbm_update_msg_tree_move(GNode * new_node,struct lbm_update_msg_tree_info * mti)3866 lbm_update_msg_tree_move(GNode * new_node,
3867 struct lbm_update_msg_tree_info *mti)
3868 {
3869 guint msgno;
3870 GNode *node;
3871 GNode *parent;
3872
3873 if (!new_node->parent)
3874 return FALSE;
3875
3876 msgno = GPOINTER_TO_UINT(new_node->data);
3877 if (msgno >= mti->total)
3878 return FALSE;
3879
3880 node = mti->nodes[msgno];
3881 if (!node)
3882 mti->nodes[msgno] = node = g_node_new(new_node->data);
3883
3884 msgno = GPOINTER_TO_UINT(new_node->parent->data);
3885 if (msgno >= mti->total)
3886 return FALSE;
3887
3888 parent = mti->nodes[msgno];
3889
3890 if (parent && parent != node->parent)
3891 libbalsa_mailbox_unlink_and_prepend(mti->mailbox, node, parent);
3892
3893 return FALSE;
3894 }
3895
3896 static void
lbm_update_msg_tree(LibBalsaMailbox * mailbox,GNode * new_tree)3897 lbm_update_msg_tree(LibBalsaMailbox * mailbox, GNode * new_tree)
3898 {
3899 struct lbm_update_msg_tree_info mti;
3900
3901 mti.mailbox = mailbox;
3902 mti.new_tree = new_tree;
3903 mti.total = 1 + libbalsa_mailbox_total_messages(mailbox);
3904 mti.nodes = g_new0(GNode *, mti.total);
3905
3906 /* Populate the nodes array with nodes in the new tree. */
3907 g_node_traverse(new_tree, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
3908 (GNodeTraverseFunc) lbm_update_msg_tree_populate, &mti);
3909 /* Remove deadwood. */
3910 g_node_traverse(mailbox->msg_tree, G_POST_ORDER, G_TRAVERSE_ALL, -1,
3911 (GNodeTraverseFunc) lbm_update_msg_tree_prune, &mti);
3912
3913 /* Clear the nodes array and repopulate it with nodes in the current
3914 * tree. */
3915 memset(mti.nodes, 0, sizeof(GNode *) * mti.total);
3916 g_node_traverse(mailbox->msg_tree, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
3917 (GNodeTraverseFunc) lbm_update_msg_tree_populate, &mti);
3918 /* Check parent-child relationships. */
3919 g_node_traverse(new_tree, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
3920 (GNodeTraverseFunc) lbm_update_msg_tree_move, &mti);
3921
3922 g_free(mti.nodes);
3923 }
3924
3925 static void
lbm_set_msg_tree(LibBalsaMailbox * mailbox)3926 lbm_set_msg_tree(LibBalsaMailbox * mailbox)
3927 {
3928 GtkTreeIter iter;
3929 GNode *node;
3930 GtkTreePath *path;
3931
3932 iter.stamp = ++mailbox->stamp;
3933
3934 if (!mailbox->msg_tree)
3935 return;
3936
3937 gdk_threads_enter();
3938 path = gtk_tree_path_new();
3939 gtk_tree_path_down(path);
3940
3941 for (node = mailbox->msg_tree->children; node; node = node->next) {
3942 iter.user_data = node;
3943 g_signal_emit(mailbox,
3944 libbalsa_mbox_model_signals[ROW_INSERTED], 0, path,
3945 &iter);
3946 if (node->children)
3947 g_signal_emit(mailbox,
3948 libbalsa_mbox_model_signals
3949 [ROW_HAS_CHILD_TOGGLED], 0, path, &iter);
3950 gtk_tree_path_next(path);
3951 }
3952
3953 gtk_tree_path_free(path);
3954 gdk_threads_leave();
3955 }
3956
3957 void
libbalsa_mailbox_set_msg_tree(LibBalsaMailbox * mailbox,GNode * new_tree)3958 libbalsa_mailbox_set_msg_tree(LibBalsaMailbox * mailbox, GNode * new_tree)
3959 {
3960 gdk_threads_enter();
3961
3962 if (mailbox->msg_tree && mailbox->msg_tree->children) {
3963 lbm_update_msg_tree(mailbox, new_tree);
3964 g_node_destroy(new_tree);
3965 } else {
3966 if (mailbox->msg_tree)
3967 g_node_destroy(mailbox->msg_tree);
3968 mailbox->msg_tree = new_tree;
3969 lbm_set_msg_tree(mailbox);
3970 }
3971
3972 mailbox->msg_tree_changed = TRUE;
3973
3974 gdk_threads_leave();
3975 }
3976
3977 static GMimeMessage *
lbm_get_mime_msg(LibBalsaMailbox * mailbox,LibBalsaMessage * msg)3978 lbm_get_mime_msg(LibBalsaMailbox * mailbox, LibBalsaMessage * msg)
3979 {
3980 GMimeMessage *mime_msg;
3981
3982 if (!msg->mime_msg)
3983 libbalsa_mailbox_fetch_message_structure(mailbox, msg,
3984 LB_FETCH_RFC822_HEADERS);
3985 mime_msg = msg->mime_msg;
3986 if (mime_msg)
3987 g_object_ref(mime_msg);
3988 else {
3989 GMimeStream *stream;
3990 GMimeParser *parser;
3991
3992 stream = libbalsa_mailbox_get_message_stream(mailbox, msg->msgno,
3993 TRUE);
3994 parser = g_mime_parser_new_with_stream(stream);
3995 g_object_unref(stream);
3996 mime_msg = g_mime_parser_construct_message(parser);
3997 g_object_unref(parser);
3998 }
3999 libbalsa_mailbox_release_message(mailbox, msg);
4000
4001 return mime_msg;
4002 }
4003
4004 /* Try to reassemble messages of type message/partial with the given id;
4005 * if successful, delete the parts, so we don't keep creating the whole
4006 * message. */
4007
4008 static void
lbm_try_reassemble_func(GMimeObject * parent,GMimeObject * mime_part,gpointer data)4009 lbm_try_reassemble_func(GMimeObject * parent, GMimeObject * mime_part,
4010 gpointer data)
4011 {
4012 if (GMIME_IS_MESSAGE_PART(mime_part))
4013 mime_part = ((GMimeMessagePart *) mime_part)->message->mime_part;
4014 if (GMIME_IS_MESSAGE_PARTIAL(mime_part)) {
4015 const GMimeMessagePartial **partial = data;
4016 *partial = (GMimeMessagePartial *) mime_part;
4017 }
4018 }
4019
4020 static void
lbm_try_reassemble(LibBalsaMailbox * mailbox,const gchar * id)4021 lbm_try_reassemble(LibBalsaMailbox * mailbox, const gchar * id)
4022 {
4023 gchar *text;
4024 guint total_messages;
4025 LibBalsaProgress progress;
4026 guint msgno;
4027 GPtrArray *partials = g_ptr_array_new();
4028 guint total = G_MAXUINT;
4029 GArray *messages = g_array_new(FALSE, FALSE, sizeof(guint));
4030
4031 text = g_strdup_printf(_("Searching %s for partial messages"),
4032 mailbox->name);
4033 total_messages = libbalsa_mailbox_total_messages(mailbox);
4034 libbalsa_progress_set_text(&progress, text, total_messages);
4035 g_free(text);
4036
4037 for (msgno = 1; msgno <= total_messages && partials->len < total;
4038 msgno++) {
4039 LibBalsaMessage *message;
4040 gchar *tmp_id;
4041
4042 if (libbalsa_mailbox_msgno_has_flags(mailbox, msgno,
4043 LIBBALSA_MESSAGE_FLAG_DELETED,
4044 0)
4045 || !(message = libbalsa_mailbox_get_message(mailbox, msgno)))
4046 continue;
4047
4048 if (!libbalsa_message_is_partial(message, &tmp_id)) {
4049 g_object_unref(message);
4050 continue;
4051 }
4052
4053 if (strcmp(tmp_id, id) == 0) {
4054 GMimeMessage *mime_message = lbm_get_mime_msg(mailbox, message);
4055 GMimeMessagePartial *partial =
4056 (GMimeMessagePartial *) mime_message->mime_part;
4057
4058 g_ptr_array_add(partials, partial);
4059 if (g_mime_message_partial_get_total(partial) > 0)
4060 total = g_mime_message_partial_get_total(partial);
4061 g_object_ref(partial);
4062 g_object_unref(mime_message);
4063
4064 g_array_append_val(messages, msgno);
4065 }
4066
4067 g_free(tmp_id);
4068 g_object_unref(message);
4069 libbalsa_progress_set_fraction(&progress, ((gdouble) msgno) /
4070 ((gdouble) total_messages));
4071 }
4072
4073 if (partials->len < total) {
4074 /* Someone might have wrapped a message/partial in a
4075 * message/multipart. */
4076 libbalsa_progress_set_fraction(&progress, 0);
4077 for (msgno = 1; msgno <= total_messages && partials->len < total;
4078 msgno++) {
4079 LibBalsaMessage *message;
4080 GMimeMessage *mime_message;
4081 GMimeMessagePartial *partial;
4082
4083 if (libbalsa_mailbox_msgno_has_flags(mailbox, msgno,
4084 LIBBALSA_MESSAGE_FLAG_DELETED,
4085 0)
4086 || !(message = libbalsa_mailbox_get_message(mailbox, msgno)))
4087 continue;
4088
4089 if (!libbalsa_message_is_multipart(message)) {
4090 g_object_unref(message);
4091 continue;
4092 }
4093
4094 mime_message = lbm_get_mime_msg(mailbox, message);
4095 partial = NULL;
4096 g_mime_multipart_foreach((GMimeMultipart *)
4097 mime_message->mime_part,
4098 lbm_try_reassemble_func, &partial);
4099 if (partial
4100 && strcmp(g_mime_message_partial_get_id(partial), id) == 0) {
4101 g_ptr_array_add(partials, partial);
4102 if (g_mime_message_partial_get_total(partial) > 0)
4103 total = g_mime_message_partial_get_total(partial);
4104 g_object_ref(partial);
4105 /* We will leave this message undeleted, as it may have
4106 * some warning content.
4107 * g_array_append_val(messages, msgno); */
4108 }
4109 g_object_unref(mime_message);
4110 g_object_unref(message);
4111 libbalsa_progress_set_fraction(&progress, ((gdouble) msgno) /
4112 ((gdouble) total_messages));
4113 }
4114 }
4115
4116 if (partials->len == total) {
4117 LibBalsaMessage *message = libbalsa_message_new();
4118 message->flags |= LIBBALSA_MESSAGE_FLAG_NEW;
4119
4120 libbalsa_information(LIBBALSA_INFORMATION_MESSAGE,
4121 _("Reconstructing message"));
4122 libbalsa_mailbox_lock_store(mailbox);
4123 message->mime_msg =
4124 g_mime_message_partial_reconstruct_message((GMimeMessagePartial
4125 **) partials->
4126 pdata, total);
4127 libbalsa_message_copy(message, mailbox, NULL);
4128 libbalsa_mailbox_unlock_store(mailbox);
4129
4130 g_object_unref(message);
4131 libbalsa_mailbox_messages_change_flags(mailbox, messages,
4132 LIBBALSA_MESSAGE_FLAG_DELETED,
4133 0);
4134 }
4135
4136 g_ptr_array_foreach(partials, (GFunc) g_object_unref, NULL);
4137 g_ptr_array_free(partials, TRUE);
4138 g_array_free(messages, TRUE);
4139
4140 libbalsa_progress_set_text(&progress, NULL, 0);
4141 }
4142
4143 #define LBM_TRY_REASSEMBLE_IDS "libbalsa-mailbox-try-reassemble-ids"
4144
4145 static gboolean
lbm_try_reassemble_idle(LibBalsaMailbox * mailbox)4146 lbm_try_reassemble_idle(LibBalsaMailbox * mailbox)
4147 {
4148 GSList *id, *ids;
4149
4150 /* Make sure the thread that detected a message/partial has
4151 * completed. */
4152 libbalsa_lock_mailbox(mailbox);
4153
4154 ids = g_object_get_data(G_OBJECT(mailbox), LBM_TRY_REASSEMBLE_IDS);
4155 for (id = ids; id; id = id->next)
4156 lbm_try_reassemble(mailbox, id->data);
4157
4158 g_slist_foreach(ids, (GFunc) g_free, NULL);
4159 g_slist_free(ids);
4160 g_object_set_data(G_OBJECT(mailbox), LBM_TRY_REASSEMBLE_IDS, NULL);
4161
4162 libbalsa_unlock_mailbox(mailbox);
4163
4164 g_object_unref(mailbox);
4165
4166 return FALSE;
4167 }
4168
4169 void
libbalsa_mailbox_try_reassemble(LibBalsaMailbox * mailbox,const gchar * id)4170 libbalsa_mailbox_try_reassemble(LibBalsaMailbox * mailbox,
4171 const gchar * id)
4172 {
4173 GSList *ids;
4174
4175 if (mailbox->no_reassemble)
4176 return;
4177
4178 ids = g_object_get_data(G_OBJECT(mailbox), LBM_TRY_REASSEMBLE_IDS);
4179 if (!ids) {
4180 g_object_ref(mailbox);
4181 g_idle_add((GSourceFunc) lbm_try_reassemble_idle, mailbox);
4182 }
4183
4184 if (!g_slist_find_custom(ids, id, (GCompareFunc) strcmp)) {
4185 ids = g_slist_prepend(ids, g_strdup(id));
4186 g_object_set_data(G_OBJECT(mailbox), LBM_TRY_REASSEMBLE_IDS, ids);
4187 }
4188 }
4189
4190 /* Use "message-expunged" signal to update an array of msgnos. */
4191 static void
lbm_update_msgnos(LibBalsaMailbox * mailbox,guint seqno,GArray * msgnos)4192 lbm_update_msgnos(LibBalsaMailbox * mailbox, guint seqno, GArray * msgnos)
4193 {
4194 guint i, j;
4195
4196 for (i = j = 0; i < msgnos->len; i++) {
4197 guint msgno = g_array_index(msgnos, guint, i);
4198 if (msgno == seqno)
4199 continue;
4200 if (msgno > seqno)
4201 --msgno;
4202 g_array_index(msgnos, guint, j) = msgno;
4203 ++j;
4204 }
4205 msgnos->len = j;
4206 }
4207
4208 void
libbalsa_mailbox_register_msgnos(LibBalsaMailbox * mailbox,GArray * msgnos)4209 libbalsa_mailbox_register_msgnos(LibBalsaMailbox * mailbox,
4210 GArray * msgnos)
4211 {
4212 g_signal_connect(mailbox, "message-expunged",
4213 G_CALLBACK(lbm_update_msgnos), msgnos);
4214 }
4215
4216 void
libbalsa_mailbox_unregister_msgnos(LibBalsaMailbox * mailbox,GArray * msgnos)4217 libbalsa_mailbox_unregister_msgnos(LibBalsaMailbox * mailbox,
4218 GArray * msgnos)
4219 {
4220 if (mailbox && msgnos)
4221 g_signal_handlers_disconnect_by_func(mailbox, lbm_update_msgnos,
4222 msgnos);
4223 }
4224
4225 /* Accessors for LibBalsaMailboxIndexEntry */
4226 LibBalsaMessageStatus
libbalsa_mailbox_msgno_get_status(LibBalsaMailbox * mailbox,guint msgno)4227 libbalsa_mailbox_msgno_get_status(LibBalsaMailbox * mailbox, guint msgno)
4228 {
4229 LibBalsaMailboxIndexEntry *entry =
4230 g_ptr_array_index(mailbox->mindex, msgno - 1);
4231 return VALID_ENTRY(entry) ?
4232 entry->status_icon : LIBBALSA_MESSAGE_STATUS_ICONS_NUM;
4233 }
4234
4235 const gchar *
libbalsa_mailbox_msgno_get_subject(LibBalsaMailbox * mailbox,guint msgno)4236 libbalsa_mailbox_msgno_get_subject(LibBalsaMailbox * mailbox, guint msgno)
4237 {
4238 LibBalsaMailboxIndexEntry *entry =
4239 g_ptr_array_index(mailbox->mindex, msgno - 1);
4240 return VALID_ENTRY(entry) ? entry->subject : NULL;
4241 }
4242
4243 /* Update icons, but only if entry has been allocated. */
4244 void
libbalsa_mailbox_msgno_update_attach(LibBalsaMailbox * mailbox,guint msgno,LibBalsaMessage * message)4245 libbalsa_mailbox_msgno_update_attach(LibBalsaMailbox * mailbox,
4246 guint msgno, LibBalsaMessage * message)
4247 {
4248 LibBalsaMailboxIndexEntry *entry;
4249 LibBalsaMessageAttach attach_icon;
4250
4251 if (!mailbox || !mailbox->mindex || mailbox->mindex->len < msgno)
4252 return;
4253
4254 entry = g_ptr_array_index(mailbox->mindex, msgno - 1);
4255 if (!VALID_ENTRY(entry))
4256 return;
4257
4258 attach_icon = libbalsa_message_get_attach_icon(message);
4259 if (entry->attach_icon != attach_icon) {
4260 GtkTreeIter iter;
4261
4262 entry->attach_icon = attach_icon;
4263 iter.user_data = NULL;
4264 lbm_msgno_changed(mailbox, msgno, &iter);
4265 }
4266 }
4267
4268 /* Queued check. */
4269
4270 static void
lbm_check_real(LibBalsaMailbox * mailbox)4271 lbm_check_real(LibBalsaMailbox * mailbox)
4272 {
4273 libbalsa_lock_mailbox(mailbox);
4274 libbalsa_mailbox_check(mailbox);
4275 libbalsa_unlock_mailbox(mailbox);
4276 g_object_unref(mailbox);
4277 }
4278
4279 static gboolean
lbm_check_idle(LibBalsaMailbox * mailbox)4280 lbm_check_idle(LibBalsaMailbox * mailbox)
4281 {
4282 #if defined(BALSA_USE_THREADS)
4283 pthread_t check_thread;
4284
4285 pthread_create(&check_thread, NULL, (void *) lbm_check_real, mailbox);
4286 pthread_detach(check_thread);
4287 #else /*BALSA_USE_THREADS */
4288 lbm_check_real(mailbox);
4289 #endif /*BALSA_USE_THREADS */
4290
4291 libbalsa_lock_mailbox(mailbox);
4292 mailbox->queue_check_idle_id = 0;
4293 libbalsa_unlock_mailbox(mailbox);
4294
4295 return FALSE;
4296 }
4297
4298 static void
lbm_queue_check(LibBalsaMailbox * mailbox)4299 lbm_queue_check(LibBalsaMailbox * mailbox)
4300 {
4301 libbalsa_lock_mailbox(mailbox);
4302 if (!mailbox->queue_check_idle_id)
4303 mailbox->queue_check_idle_id =
4304 g_idle_add((GSourceFunc) lbm_check_idle,
4305 g_object_ref(mailbox));
4306 libbalsa_unlock_mailbox(mailbox);
4307 }
4308
4309 /* Search mailbox for a message matching the condition in search_iter,
4310 * starting at iter, either forward or backward, and abandoning the
4311 * search if message stop_msgno is reached; return value indicates
4312 * success of the search.
4313 *
4314 * On return:
4315 * if return value is TRUE, iter points to the matching message;
4316 * if return value is FALSE, iter is invalid.
4317 */
4318 gboolean
libbalsa_mailbox_search_iter_step(LibBalsaMailbox * mailbox,LibBalsaMailboxSearchIter * search_iter,GtkTreeIter * iter,gboolean forward,guint stop_msgno)4319 libbalsa_mailbox_search_iter_step(LibBalsaMailbox * mailbox,
4320 LibBalsaMailboxSearchIter * search_iter,
4321 GtkTreeIter * iter,
4322 gboolean forward,
4323 guint stop_msgno)
4324 {
4325 GNode *node;
4326 gboolean retval = FALSE;
4327 gint total;
4328
4329 node = iter->user_data;
4330 if (!node)
4331 node = mailbox->msg_tree;
4332
4333 total = libbalsa_mailbox_total_messages(mailbox);
4334 for (;;) {
4335 guint msgno;
4336
4337 node = forward ? lbm_next(node) : lbm_prev(node);
4338 msgno = GPOINTER_TO_UINT(node->data);
4339 if (msgno == stop_msgno
4340 || --total < 0 /* Runaway? */ ) {
4341 retval = FALSE;
4342 break;
4343 }
4344 if (msgno > 0
4345 && libbalsa_mailbox_message_match(mailbox, msgno,
4346 search_iter)) {
4347 iter->user_data = node;
4348 retval = TRUE;
4349 break;
4350 }
4351 }
4352
4353 if (retval)
4354 VALIDATE_ITER(iter, mailbox);
4355 else
4356 INVALIDATE_ITER(iter);
4357
4358 return retval;
4359 }
4360
4361 /* Remove duplicates */
4362
4363 gboolean
libbalsa_mailbox_can_move_duplicates(LibBalsaMailbox * mailbox)4364 libbalsa_mailbox_can_move_duplicates(LibBalsaMailbox * mailbox)
4365 {
4366 return LIBBALSA_MAILBOX_GET_CLASS(mailbox)->duplicate_msgnos != NULL;
4367 }
4368
4369 gint
libbalsa_mailbox_move_duplicates(LibBalsaMailbox * mailbox,LibBalsaMailbox * dest,GError ** err)4370 libbalsa_mailbox_move_duplicates(LibBalsaMailbox * mailbox,
4371 LibBalsaMailbox * dest, GError ** err)
4372 {
4373 GArray *msgnos = NULL;
4374 gint retval;
4375
4376 if (libbalsa_mailbox_can_move_duplicates(mailbox))
4377 msgnos =
4378 LIBBALSA_MAILBOX_GET_CLASS(mailbox)->duplicate_msgnos(mailbox);
4379
4380 if (mailbox->state == LB_MAILBOX_STATE_CLOSED) {
4381 /* duplicate msgnos was interrupted */
4382 g_set_error(err, LIBBALSA_MAILBOX_ERROR,
4383 LIBBALSA_MAILBOX_DUPLICATES_ERROR,
4384 _("Finding duplicate messages in source mailbox failed"));
4385 return 0;
4386 }
4387
4388 if (!msgnos)
4389 return 0;
4390
4391 if (msgnos->len > 0) {
4392 if (dest && mailbox != dest)
4393 libbalsa_mailbox_messages_move(mailbox, msgnos, dest, err);
4394 else
4395 libbalsa_mailbox_messages_change_flags(mailbox, msgnos,
4396 LIBBALSA_MESSAGE_FLAG_DELETED,
4397 0);
4398 }
4399 retval = msgnos->len;
4400 g_array_free(msgnos, TRUE);
4401 return retval;
4402 }
4403
4404 #if BALSA_USE_THREADS
4405 /* Lock and unlock the mail store. NULL mailbox is not an error. */
4406 void
libbalsa_mailbox_lock_store(LibBalsaMailbox * mailbox)4407 libbalsa_mailbox_lock_store(LibBalsaMailbox * mailbox)
4408 {
4409 if (mailbox)
4410 LIBBALSA_MAILBOX_GET_CLASS(mailbox)->lock_store(mailbox, TRUE);
4411 }
4412
4413 void
libbalsa_mailbox_unlock_store(LibBalsaMailbox * mailbox)4414 libbalsa_mailbox_unlock_store(LibBalsaMailbox * mailbox)
4415 {
4416 if (mailbox)
4417 LIBBALSA_MAILBOX_GET_CLASS(mailbox)->lock_store(mailbox, FALSE);
4418 }
4419 #endif /* BALSA_USE_THREADS */
4420
4421 void
libbalsa_mailbox_cache_message(LibBalsaMailbox * mailbox,guint msgno,LibBalsaMessage * message)4422 libbalsa_mailbox_cache_message(LibBalsaMailbox * mailbox, guint msgno,
4423 LibBalsaMessage * message)
4424 {
4425 g_return_if_fail(LIBBALSA_IS_MAILBOX(mailbox));
4426 if (!mailbox->mindex)
4427 return;
4428 g_return_if_fail(msgno > 0);
4429 g_return_if_fail(LIBBALSA_IS_MESSAGE(message));
4430
4431 lbm_cache_message(mailbox, msgno, message);
4432 }
4433
4434 static void
lbm_set_color(LibBalsaMailbox * mailbox,GArray * msgnos,const gchar * color,gboolean foreground)4435 lbm_set_color(LibBalsaMailbox * mailbox, GArray * msgnos,
4436 const gchar * color, gboolean foreground)
4437 {
4438 guint i;
4439
4440 for (i = 0; i < msgnos->len; i++) {
4441 guint msgno = g_array_index(msgnos, guint, i);
4442 LibBalsaMailboxIndexEntry *entry;
4443
4444 if (msgno > mailbox->mindex->len)
4445 return;
4446
4447 entry = g_ptr_array_index(mailbox->mindex, msgno - 1);
4448 if (!entry)
4449 entry = g_ptr_array_index(mailbox->mindex, msgno - 1) =
4450 g_new0(LibBalsaMailboxIndexEntry, 1);
4451
4452 if (foreground) {
4453 g_free(entry->foreground);
4454 entry->foreground = g_strdup(color);
4455 entry->foreground_set = TRUE;
4456 } else {
4457 g_free(entry->background);
4458 entry->background = g_strdup(color);
4459 entry->background_set = TRUE;
4460 }
4461 }
4462 }
4463
4464 void
libbalsa_mailbox_set_foreground(LibBalsaMailbox * mailbox,GArray * msgnos,const gchar * color)4465 libbalsa_mailbox_set_foreground(LibBalsaMailbox * mailbox, GArray * msgnos,
4466 const gchar * color)
4467 {
4468 lbm_set_color(mailbox, msgnos, color, TRUE);
4469 }
4470
4471 void
libbalsa_mailbox_set_background(LibBalsaMailbox * mailbox,GArray * msgnos,const gchar * color)4472 libbalsa_mailbox_set_background(LibBalsaMailbox * mailbox, GArray * msgnos,
4473 const gchar * color)
4474 {
4475 lbm_set_color(mailbox, msgnos, color, FALSE);
4476 }
4477