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