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 #include "balsa-app.h"
26 
27 #include <string.h>
28 #include <stdlib.h>
29 #ifdef BALSA_USE_THREADS
30 #include <pthread.h>
31 #endif
32 
33 /* for creat(2) */
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <fcntl.h>
37 
38 #include "filter-funcs.h"
39 #include "libbalsa-conf.h"
40 #include "misc.h"
41 #include "server.h"
42 #include "smtp-server.h"
43 #include "save-restore.h"
44 
45 #if HAVE_MACOSX_DESKTOP
46 #  include "macosx-helpers.h"
47 #endif
48 
49 #include <glib/gi18n.h>	/* Must come after balsa-app.h. */
50 
51 /* Global application structure */
52 struct BalsaApplication balsa_app;
53 
54 #define HIG_PADDING 12
55 
56 /* ask_password:
57    asks the user for the password to the mailbox on given remote server.
58 */
59 static gchar *
ask_password_real(LibBalsaServer * server,LibBalsaMailbox * mbox)60 ask_password_real(LibBalsaServer * server, LibBalsaMailbox * mbox)
61 {
62     GtkWidget *dialog, *entry, *rememb;
63     GtkWidget *content_area;
64     gchar *prompt, *passwd = NULL;
65 #if defined(HAVE_LIBSECRET)
66     static const gchar *remember_password_message =
67         N_("_Remember password in Secret Service");
68 #elif defined (HAVE_GNOME_KEYRING)
69     static const gchar *remember_password_message =
70         N_("_Remember password in keyring");
71 #else
72     static const gchar *remember_password_message =
73         N_("_Remember password");
74 #endif                          /* defined(HAVE_LIBSECRET) */
75 
76     g_return_val_if_fail(server != NULL, NULL);
77     if (mbox)
78 	prompt =
79 	    g_strdup_printf(_("Opening remote mailbox %s.\n"
80                               "The _password for %s@%s:"),
81 			    mbox->name, server->user, server->host);
82     else
83 	prompt =
84 	    g_strdup_printf(_("_Password for %s@%s (%s):"), server->user,
85 			    server->host, server->protocol);
86 
87     dialog = gtk_dialog_new_with_buttons(_("Password needed"),
88                                          GTK_WINDOW(balsa_app.main_window),
89                                          GTK_DIALOG_DESTROY_WITH_PARENT,
90                                          GTK_STOCK_OK, GTK_RESPONSE_OK,
91                                          GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
92                                          NULL);
93 #if HAVE_MACOSX_DESKTOP
94     libbalsa_macosx_menu_for_parent(dialog, GTK_WINDOW(balsa_app.main_window));
95 #endif
96     content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
97     gtk_box_set_spacing(GTK_BOX(content_area), HIG_PADDING);
98     gtk_container_add(GTK_CONTAINER(content_area),
99                       gtk_label_new_with_mnemonic(prompt));
100     g_free(prompt);
101     gtk_container_add(GTK_CONTAINER(content_area),
102                       entry = gtk_entry_new());
103     gtk_entry_set_width_chars(GTK_ENTRY(entry), 20);
104     gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
105 
106     rememb =  gtk_check_button_new_with_mnemonic(_(remember_password_message));
107     gtk_container_add(GTK_CONTAINER(content_area), rememb);
108     if(server->remember_passwd)
109         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rememb), TRUE);
110 
111     gtk_widget_show_all(content_area);
112     gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
113     gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
114     gtk_widget_grab_focus (entry);
115 
116     if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) {
117         unsigned old_rem = server->remember_passwd;
118         passwd = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
119         server->remember_passwd =
120             !!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(rememb));
121         libbalsa_server_set_password(server, passwd);
122         if( server->remember_passwd || old_rem )
123             libbalsa_server_config_changed(server);
124     }
125     gtk_widget_destroy(dialog);
126     return passwd;
127 }
128 
129 #ifdef BALSA_USE_THREADS
130 typedef struct {
131     pthread_cond_t cond;
132     LibBalsaServer* server;
133     LibBalsaMailbox* mbox;
134     gchar* res;
135 } AskPasswdData;
136 
137 /* ask_passwd_idle:
138    called in MT mode by the main thread.
139  */
140 static gboolean
ask_passwd_idle(gpointer data)141 ask_passwd_idle(gpointer data)
142 {
143     AskPasswdData* apd = (AskPasswdData*)data;
144     gdk_threads_enter();
145     apd->res = ask_password_real(apd->server, apd->mbox);
146     gdk_threads_leave();
147     pthread_cond_signal(&apd->cond);
148     return FALSE;
149 }
150 
151 /* ask_password_mt:
152    GDK lock must not be held.
153 */
154 static gchar *
ask_password_mt(LibBalsaServer * server,LibBalsaMailbox * mbox)155 ask_password_mt(LibBalsaServer * server, LibBalsaMailbox * mbox)
156 {
157     static pthread_mutex_t ask_passwd_lock = PTHREAD_MUTEX_INITIALIZER;
158     AskPasswdData apd;
159 
160     pthread_mutex_lock(&ask_passwd_lock);
161     pthread_cond_init(&apd.cond, NULL);
162     apd.server = server;
163     apd.mbox   = mbox;
164     g_idle_add(ask_passwd_idle, &apd);
165     pthread_cond_wait(&apd.cond, &ask_passwd_lock);
166 
167     pthread_cond_destroy(&apd.cond);
168     pthread_mutex_unlock(&ask_passwd_lock);
169     return apd.res;
170 }
171 #endif
172 
173 static gboolean
set_passwd_from_matching_server(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)174 set_passwd_from_matching_server(GtkTreeModel *model,
175 				GtkTreePath *path,
176 				GtkTreeIter *iter,
177 				gpointer data)
178 {
179     LibBalsaServer *server;
180     LibBalsaServer *master;
181     LibBalsaMailbox *mbox;
182     BalsaMailboxNode *node;
183 
184     gtk_tree_model_get(model, iter, 0, &node, -1);
185     g_return_val_if_fail(node != NULL, FALSE);
186     if(node->server) {
187         server = node->server;
188 	g_object_unref(node);
189     } else {
190         mbox = node->mailbox;
191 	g_object_unref(node);
192         if(!mbox) /* eg. a collection of mboxes */
193             return FALSE;
194         g_return_val_if_fail(LIBBALSA_IS_MAILBOX(mbox), FALSE);
195 
196         if (!LIBBALSA_IS_MAILBOX_REMOTE(mbox)) return FALSE;
197         server = LIBBALSA_MAILBOX_REMOTE_SERVER(mbox);
198         g_return_val_if_fail(server != NULL, FALSE);
199     }
200     g_return_val_if_fail(server->host != NULL, FALSE);
201     g_return_val_if_fail(server->user != NULL, FALSE);
202     if (server->passwd == NULL) return FALSE;
203 
204     master = (LibBalsaServer *)data;
205     g_return_val_if_fail(LIBBALSA_IS_SERVER(master), FALSE);
206     if (master == server) return FALSE;
207 
208     g_return_val_if_fail(server->host != NULL, FALSE);
209     g_return_val_if_fail(server->user != NULL, FALSE);
210 
211     if ((strcmp(server->host, master->host) == 0) &&
212 	(strcmp(server->user, master->user) == 0)) {
213 	g_free(master->passwd);
214 	master->passwd = g_strdup(server->passwd);
215 	return TRUE;
216     };
217 
218     return FALSE;
219 }
220 /* ask_password:
221    when called from thread, gdk lock must not be held.
222 */
223 gchar *
ask_password(LibBalsaServer * server,LibBalsaMailbox * mbox)224 ask_password(LibBalsaServer *server, LibBalsaMailbox *mbox)
225 {
226     gchar *password;
227 
228     g_return_val_if_fail(server != NULL, NULL);
229 
230     password = NULL;
231     if (mbox) {
232 	gtk_tree_model_foreach(GTK_TREE_MODEL(balsa_app.mblist_tree_store),
233 			       (GtkTreeModelForeachFunc)
234 			       set_passwd_from_matching_server, server);
235 
236 	if (server->passwd != NULL) {
237 	    password = server->passwd;
238 	    server->passwd = NULL;
239 	}
240     }
241 
242     if (!password)
243 #ifdef BALSA_USE_THREADS
244     {
245         G_LOCK_DEFINE_STATIC(ask_password);
246 
247         G_LOCK(ask_password);
248 	password = (pthread_self() == libbalsa_get_main_thread()) ?
249             ask_password_real(server, mbox) : ask_password_mt(server, mbox);
250         G_UNLOCK(ask_password);
251 	return password;
252     }
253 #else
254 	return ask_password_real(server, mbox);
255 #endif
256     else
257 	return password;
258 }
259 
260 #if ENABLE_ESMTP
261 static void
authapi_exit(void)262 authapi_exit (void)
263 {
264     g_slist_foreach(balsa_app.smtp_servers, (GFunc) g_object_unref, NULL);
265     g_slist_free(balsa_app.smtp_servers);
266     balsa_app.smtp_servers = NULL;
267     auth_client_exit ();
268 }
269 #endif /* ESMTP */
270 
271 void
balsa_app_init(void)272 balsa_app_init(void)
273 {
274     /*
275      * initalize application structure before ALL ELSE
276      * to some reasonable defaults
277      */
278     balsa_app.identities = NULL;
279     balsa_app.current_ident = NULL;
280     balsa_app.local_mail_directory = NULL;
281 
282 #if ENABLE_ESMTP
283     balsa_app.smtp_servers = NULL;
284 
285     /* Do what's needed at application level to allow libESMTP
286        to use authentication.  */
287     auth_client_init ();
288     atexit (authapi_exit);
289 #endif
290 
291     balsa_app.inbox = NULL;
292     balsa_app.inbox_input = NULL;
293     balsa_app.outbox = NULL;
294     balsa_app.sentbox = NULL;
295     balsa_app.draftbox = NULL;
296     balsa_app.trash = NULL;
297 
298     balsa_app.new_messages_timer = 0;
299     balsa_app.new_messages = 0;
300 
301     balsa_app.check_mail_auto = TRUE;
302     balsa_app.check_mail_timer = 10;
303 
304     balsa_app.debug = FALSE;
305     balsa_app.previewpane = TRUE;
306     balsa_app.pgdownmod = FALSE;
307     balsa_app.pgdown_percent = 50;
308 
309     /* GUI settings */
310     balsa_app.mblist = NULL;
311     balsa_app.mblist_width = 100;
312     balsa_app.mw_width = MW_DEFAULT_WIDTH;
313     balsa_app.mw_height = MW_DEFAULT_HEIGHT;
314     balsa_app.mw_maximized = FALSE;
315 
316     balsa_app.sw_width = 0;
317     balsa_app.sw_height = 0;
318     balsa_app.sw_maximized = FALSE;
319 
320     balsa_app.toolbar_wrap_button_text = TRUE;
321     balsa_app.pwindow_option = WHILERETR;
322     balsa_app.wordwrap = FALSE; /* default to format=flowed. */
323     balsa_app.wraplength = 72;
324     balsa_app.browse_wrap = FALSE; /* GtkTextView will wrap for us. */
325     balsa_app.browse_wrap_length = 79;
326     balsa_app.shown_headers = HEADERS_SELECTED;
327     balsa_app.show_all_headers = FALSE;
328     balsa_app.selected_headers = g_strdup(DEFAULT_SELECTED_HDRS);
329     balsa_app.expand_tree = FALSE;
330     balsa_app.show_mblist = TRUE;
331     balsa_app.show_notebook_tabs = FALSE;
332     balsa_app.layout_type = LAYOUT_DEFAULT;
333     balsa_app.view_message_on_open = TRUE;
334     balsa_app.ask_before_select = FALSE;
335     balsa_app.mw_action_after_move = NEXT_UNREAD;
336 
337     balsa_app.index_num_width = NUM_DEFAULT_WIDTH;
338     balsa_app.index_status_width = STATUS_DEFAULT_WIDTH;
339     balsa_app.index_attachment_width = ATTACHMENT_DEFAULT_WIDTH;
340     balsa_app.index_from_width = FROM_DEFAULT_WIDTH;
341     balsa_app.index_subject_width = SUBJECT_DEFAULT_WIDTH;
342     balsa_app.index_date_width = DATE_DEFAULT_WIDTH;
343     balsa_app.index_size_width = SIZE_DEFAULT_WIDTH;
344 
345     /* file paths */
346     balsa_app.attach_dir = NULL;
347     balsa_app.save_dir = NULL;
348 
349     /* Mailbox list column width (not fully implemented) */
350     balsa_app.mblist_name_width = MBNAME_DEFAULT_WIDTH;
351 
352     balsa_app.mblist_show_mb_content_info = FALSE;
353     balsa_app.mblist_newmsg_width = NEWMSGCOUNT_DEFAULT_WIDTH;
354     balsa_app.mblist_totalmsg_width = TOTALMSGCOUNT_DEFAULT_WIDTH;
355 
356     /* arp */
357     balsa_app.quote_str = NULL;
358 
359     /* quote regex */
360     balsa_app.quote_regex = g_strdup(DEFAULT_QUOTE_REGEX);
361 
362     /* font */
363     balsa_app.message_font = NULL;
364     balsa_app.subject_font = NULL;
365 
366     /* compose: shown headers */
367     balsa_app.compose_headers = NULL;
368 
369     /* command line options */
370 #if defined(ENABLE_TOUCH_UI)
371     balsa_app.open_inbox_upon_startup = TRUE;
372 #endif /* ENABLE_TOUCH_UI */
373 
374     /* date format */
375     balsa_app.date_string = g_strdup(DEFAULT_DATE_FORMAT);
376 
377     /* printing */
378     balsa_app.print_settings = gtk_print_settings_new();
379     balsa_app.page_setup = gtk_page_setup_new();
380 
381     balsa_app.print_header_font = g_strdup(DEFAULT_PRINT_HEADER_FONT);
382     balsa_app.print_footer_font = g_strdup(DEFAULT_PRINT_FOOTER_FONT);
383     balsa_app.print_body_font   = g_strdup(DEFAULT_PRINT_BODY_FONT);
384     balsa_app.print_highlight_cited = FALSE;
385     balsa_app.print_highlight_phrases = FALSE;
386 
387     /* address book */
388     balsa_app.address_book_list = NULL;
389     balsa_app.default_address_book = NULL;
390 
391     /* Filters */
392     balsa_app.filters=NULL;
393 
394     /* spell check */
395 #if HAVE_GTKSPELL
396     balsa_app.spell_check_lang = NULL;
397     balsa_app.spell_check_active = FALSE;
398 #else                           /* HAVE_GTKSPELL */
399     balsa_app.check_sig = DEFAULT_CHECK_SIG;
400     balsa_app.check_quoted = DEFAULT_CHECK_QUOTED;
401 #endif                          /* HAVE_GTKSPELL */
402 
403     /* Information messages */
404     balsa_app.information_message = 0;
405     balsa_app.warning_message = 0;
406     balsa_app.error_message = 0;
407     balsa_app.debug_message = 0;
408 
409     balsa_app.notify_new_mail_sound = 1;
410     balsa_app.notify_new_mail_dialog = 0;
411     balsa_app.notify_new_mail_icon = 1;
412 
413     /* Local and IMAP */
414     balsa_app.local_scan_depth = 1;
415     balsa_app.check_imap = 1;
416     balsa_app.check_imap_inbox = 0;
417     balsa_app.imap_scan_depth = 1;
418 
419 #ifdef HAVE_GPGME
420     /* gpgme stuff */
421     balsa_app.has_openpgp = FALSE;
422     balsa_app.has_smime = FALSE;
423 #endif
424 
425     /* Message filing */
426     balsa_app.folder_mru=NULL;
427     balsa_app.fcc_mru=NULL;
428 
429     g_object_set(gtk_settings_get_for_screen(gdk_screen_get_default()),
430                  "gtk-fallback-icon-theme", "gnome", NULL);
431 }
432 
433 void
balsa_app_destroy(void)434 balsa_app_destroy(void)
435 {
436     config_save();
437 
438     g_list_foreach(balsa_app.address_book_list, (GFunc)g_object_unref, NULL);
439     g_list_free(balsa_app.address_book_list);
440     balsa_app.address_book_list = NULL;
441 
442     /* now free filters */
443     g_slist_foreach(balsa_app.filters, (GFunc)libbalsa_filter_free,
444 		    GINT_TO_POINTER(TRUE));
445     g_slist_free(balsa_app.filters);
446     balsa_app.filters = NULL;
447 
448     g_list_foreach(balsa_app.identities, (GFunc)g_object_unref, NULL);
449     g_list_free(balsa_app.identities);
450     balsa_app.identities = NULL;
451 
452 
453     g_list_foreach(balsa_app.folder_mru, (GFunc)g_free, NULL);
454     g_list_free(balsa_app.folder_mru);
455     balsa_app.folder_mru = NULL;
456 
457     g_list_foreach(balsa_app.fcc_mru, (GFunc)g_free, NULL);
458     g_list_free(balsa_app.fcc_mru);
459     balsa_app.fcc_mru = NULL;
460 
461 
462     if(balsa_app.debug) g_print("balsa_app: Finished cleaning up.\n");
463 }
464 
465 static gint
check_new_messages_auto_cb(gpointer data)466 check_new_messages_auto_cb(gpointer data)
467 {
468     check_new_messages_real(balsa_app.main_window, TYPE_BACKGROUND);
469 
470     if (balsa_app.debug)
471         fprintf(stderr, "Auto-checked for new messages...\n");
472 
473     /*  preserver timer */
474     return TRUE;
475 }
476 
477 
478 void
update_timer(gboolean update,guint minutes)479 update_timer(gboolean update, guint minutes)
480 {
481     if (balsa_app.check_mail_timer_id)
482         g_source_remove(balsa_app.check_mail_timer_id);
483 
484     balsa_app.check_mail_timer_id = update ?
485         g_timeout_add_seconds(minutes * 60,
486                               (GSourceFunc) check_new_messages_auto_cb,
487                               NULL) : 0;
488 }
489 
490 
491 /*
492  * balsa_open_mailbox_list:
493  * Called on startup if remember_open_mboxes is set, and also after
494  * rescanning.
495  * Frees the passed argument when done.
496  */
497 
498 static gboolean
append_url_if_open(const gchar * group,const gchar * encoded_url,GPtrArray * array)499 append_url_if_open(const gchar * group, const gchar * encoded_url,
500                    GPtrArray * array)
501 {
502     gchar *url;
503 
504     url = libbalsa_urldecode(encoded_url);
505 
506     if (config_mailbox_was_open(url))
507         g_ptr_array_add(array, url);
508     else
509         g_free(url);
510 
511     return FALSE;
512 }
513 
514 static void
open_mailbox_by_url(const gchar * url,gboolean hidden)515 open_mailbox_by_url(const gchar * url, gboolean hidden)
516 {
517     LibBalsaMailbox *mailbox;
518 
519     mailbox = balsa_find_mailbox_by_url(url);
520     if (balsa_app.debug)
521         fprintf(stderr, "balsa_open_mailbox_list: opening %s => %p..\n",
522                 url, mailbox);
523     if (mailbox) {
524         if (hidden)
525             balsa_mblist_open_mailbox_hidden(mailbox);
526         else
527             balsa_mblist_open_mailbox(mailbox);
528     } else {
529         /* Do not try to open it next time. */
530         LibBalsaMailboxView *view = config_load_mailbox_view(url);
531         /* The mailbox may have been requested to be open because its
532          * stored view might say so or the user requested it from the
533          * command line - in which case, view may or may not be present.
534          * We will be careful here. */
535         if (view) {
536             view->open = FALSE;
537             view->in_sync = FALSE;
538             config_save_mailbox_view(url, view);
539             libbalsa_mailbox_view_free(view);
540         }
541         balsa_information(LIBBALSA_INFORMATION_WARNING,
542                           _("Couldn't open mailbox \"%s\""), url);
543     }
544 }
545 
546 void
balsa_open_mailbox_list(gchar ** urls)547 balsa_open_mailbox_list(gchar ** urls)
548 {
549     gboolean hidden = FALSE;
550     gchar **tmp;
551 
552     g_return_if_fail(urls != NULL);
553 
554     gdk_threads_enter();
555 
556     for (tmp = urls; *tmp; ++tmp) {
557         gchar **p;
558 
559         /* Have we already seen this URL? */
560         for (p = urls; p < tmp; ++p)
561             if (!strcmp(*p, *tmp))
562                 break;
563         if (p == tmp) {
564             open_mailbox_by_url(*tmp, hidden);
565             hidden = TRUE;
566         }
567     }
568 
569     g_strfreev(urls);
570 
571     gdk_threads_leave();
572 }
573 
574 void
balsa_add_open_mailbox_urls(GPtrArray * url_array)575 balsa_add_open_mailbox_urls(GPtrArray * url_array)
576 {
577     libbalsa_conf_foreach_group(VIEW_BY_URL_SECTION_PREFIX,
578                                 (LibBalsaConfForeachFunc)
579                                 append_url_if_open, url_array);
580 }
581 
582 GtkWidget *
balsa_stock_button_with_label(const char * icon,const char * text)583 balsa_stock_button_with_label(const char *icon, const char *text)
584 {
585     GtkWidget *button;
586     GtkWidget *pixmap = gtk_image_new_from_stock(icon, GTK_ICON_SIZE_BUTTON);
587     GtkWidget *align = gtk_alignment_new(0.5, 0.5, 0, 0);
588     GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
589 
590     button = gtk_button_new();
591     gtk_container_add(GTK_CONTAINER(button), align);
592     gtk_container_add(GTK_CONTAINER(align), hbox);
593 
594     gtk_box_pack_start(GTK_BOX(hbox), pixmap, FALSE, FALSE, 0);
595     if (text && *text) {
596         GtkWidget *label = gtk_label_new_with_mnemonic(text);
597         gtk_label_set_mnemonic_widget(GTK_LABEL(label), button);
598         gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 2);
599     }
600 
601     gtk_widget_show_all(button);
602     return button;
603 }
604 
605 /*
606  * Utilities for searching a GNode tree of BalsaMailboxNodes
607  *
608  * First a structure for the search info
609  */
610 struct _BalsaFind {
611     gconstpointer data;
612     LibBalsaServer   *server;
613     BalsaMailboxNode *mbnode;
614 };
615 typedef struct _BalsaFind BalsaFind;
616 
617 static gint
find_mailbox(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer user_data)618 find_mailbox(GtkTreeModel * model, GtkTreePath * path, GtkTreeIter * iter,
619 	     gpointer user_data)
620 {
621     BalsaFind *bf = user_data;
622     BalsaMailboxNode *mbnode;
623 
624     gtk_tree_model_get(model, iter, 0, &mbnode, -1);
625     if (mbnode->mailbox == bf->data) {
626 	bf->mbnode = mbnode;
627 	return TRUE;
628     }
629     g_object_unref(mbnode);
630 
631     return FALSE;
632 }
633 
634 /* balsa_find_mailbox:
635    looks for given mailbox in the GNode tree, usually but not limited to
636    balsa_app.mailbox_nodes; caller must unref mbnode if non-NULL.
637 */
638 BalsaMailboxNode *
balsa_find_mailbox(LibBalsaMailbox * mailbox)639 balsa_find_mailbox(LibBalsaMailbox * mailbox)
640 {
641     BalsaFind bf;
642 
643     gdk_threads_enter();
644 
645     bf.data = mailbox;
646     bf.mbnode = NULL;
647     if (balsa_app.mblist_tree_store)
648         gtk_tree_model_foreach(GTK_TREE_MODEL(balsa_app.mblist_tree_store),
649                                find_mailbox, &bf);
650 
651     gdk_threads_leave();
652 
653     return bf.mbnode;
654 }
655 
656 /* balsa_find_dir:
657    looks for a mailbox node with dir equal to path.
658    returns NULL on failure; caller must unref mbnode when non-NULL.
659 */
660 static gint
find_path(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,BalsaFind * bf)661 find_path(GtkTreeModel * model, GtkTreePath * path, GtkTreeIter * iter,
662 	  BalsaFind * bf)
663 {
664     BalsaMailboxNode *mbnode;
665 
666     gtk_tree_model_get(model, iter, 0, &mbnode, -1);
667     if (mbnode->server == bf->server &&
668         mbnode->dir && !strcmp(mbnode->dir, bf->data)) {
669 	bf->mbnode = mbnode;
670 	return TRUE;
671     }
672     g_object_unref(mbnode);
673 
674     return FALSE;
675 }
676 
677 BalsaMailboxNode *
balsa_find_dir(LibBalsaServer * server,const gchar * path)678 balsa_find_dir(LibBalsaServer *server, const gchar * path)
679 {
680     BalsaFind bf;
681     gboolean is_sub_thread = libbalsa_am_i_subthread();
682 
683     if (is_sub_thread)
684 	gdk_threads_enter();
685 
686     bf.data = path;
687     bf.server = server;
688     bf.mbnode = NULL;
689     gtk_tree_model_foreach(GTK_TREE_MODEL(balsa_app.mblist_tree_store),
690 			   (GtkTreeModelForeachFunc) find_path, &bf);
691 
692     if (is_sub_thread)
693 	gdk_threads_leave();
694 
695     return bf.mbnode;
696 }
697 
698 static gint
find_url(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,BalsaFind * bf)699 find_url(GtkTreeModel * model, GtkTreePath * path, GtkTreeIter * iter,
700 	 BalsaFind * bf)
701 {
702     BalsaMailboxNode *mbnode;
703     LibBalsaMailbox *mailbox;
704 
705     gtk_tree_model_get(model, iter, 0, &mbnode, -1);
706     if ((mailbox = mbnode->mailbox) && !strcmp(mailbox->url, bf->data)) {
707         bf->mbnode = mbnode;
708         return TRUE;
709     }
710     g_object_unref(mbnode);
711 
712     return FALSE;
713 }
714 
715 /* balsa_find_url:
716  * looks for a mailbox node with the given url.
717  * returns NULL on failure; caller must unref mbnode when non-NULL.
718  */
719 
720 BalsaMailboxNode *
balsa_find_url(const gchar * url)721 balsa_find_url(const gchar * url)
722 {
723     BalsaFind bf;
724 
725     bf.data = url;
726     bf.mbnode = NULL;
727 
728     if (balsa_app.mblist_tree_store)
729         g_object_ref(balsa_app.mblist_tree_store);
730     /*
731      * Check again, in case the main thread managed to finalize
732      * balsa_app.mblist_tree_store between the check and the object-ref.
733      */
734     if (balsa_app.mblist_tree_store) {
735         gtk_tree_model_foreach(GTK_TREE_MODEL(balsa_app.mblist_tree_store),
736                                (GtkTreeModelForeachFunc) find_url,
737                                &bf);
738         g_object_unref(balsa_app.mblist_tree_store);
739     }
740 
741     return bf.mbnode;
742 }
743 
744 /* balsa_find_mailbox_by_url:
745  * looks for a mailbox with the given url.
746  * returns NULL on failure
747  */
748 LibBalsaMailbox *
balsa_find_mailbox_by_url(const gchar * url)749 balsa_find_mailbox_by_url(const gchar * url)
750 {
751     BalsaMailboxNode *mbnode;
752     LibBalsaMailbox *mailbox = NULL;
753 
754     if ((mbnode = balsa_find_url(url))) {
755 	mailbox = mbnode->mailbox;
756 	g_object_unref(mbnode);
757     }
758     return mailbox;
759 }
760 
761 LibBalsaMailbox*
balsa_find_sentbox_by_url(const gchar * url)762 balsa_find_sentbox_by_url(const gchar *url)
763 {
764     LibBalsaMailbox *res = balsa_find_mailbox_by_url(url);
765     return res ? res : balsa_app.sentbox;
766 }
767 
768 gchar*
balsa_get_short_mailbox_name(const gchar * url)769 balsa_get_short_mailbox_name(const gchar *url)
770 {
771     BalsaMailboxNode *mbnode;
772 
773     if ((mbnode = balsa_find_url(url)) && mbnode->mailbox) {
774         if (mbnode->server) {
775             return g_strconcat(mbnode->server->host, ":",
776                                mbnode->mailbox->name, NULL);
777         } else {
778             return g_strdup(mbnode->mailbox->name);
779         }
780     }
781     return g_strdup(url);
782 }
783 
784 struct balsa_find_iter_by_data_info {
785     GtkTreeIter *iter;
786     gpointer data;
787     gboolean found;
788 };
789 
790 static gboolean
balsa_find_iter_by_data_func(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer user_data)791 balsa_find_iter_by_data_func(GtkTreeModel * model, GtkTreePath * path,
792 			       GtkTreeIter * iter, gpointer user_data)
793 {
794     struct balsa_find_iter_by_data_info *bf = user_data;
795     BalsaMailboxNode *mbnode = NULL;
796 
797     gtk_tree_model_get(model, iter, 0, &mbnode, -1);
798     if(!mbnode)
799         return FALSE;
800     if (mbnode == bf->data || mbnode->mailbox == bf->data) {
801 	*bf->iter = *iter;
802 	bf->found = TRUE;
803     }
804     g_object_unref(mbnode);
805 
806     return bf->found;
807 }
808 
809 gboolean
balsa_find_iter_by_data(GtkTreeIter * iter,gpointer data)810 balsa_find_iter_by_data(GtkTreeIter * iter , gpointer data)
811 {
812     struct balsa_find_iter_by_data_info bf;
813     GtkTreeModel *model;
814 
815     /* We may call it from initial config, it's ok for
816        mblist_tree_store not to exist. */
817 #ifdef BALSA_DEBUG_THREADS
818     if (libbalsa_am_i_subthread())
819         g_warning("%s sub-thread!\n", __func__);
820 #endif
821     if(!balsa_app.mblist_tree_store)
822         return FALSE;
823 
824     model = GTK_TREE_MODEL(balsa_app.mblist_tree_store);
825 
826     bf.iter = iter;
827     bf.data = data;
828     bf.found = FALSE;
829     gtk_tree_model_foreach(model, balsa_find_iter_by_data_func, &bf);
830 
831     return bf.found;
832 }
833 
834 /* End of search utilities. */
835 
836 /* balsa_remove_children_mailbox_nodes:
837    remove all children of given node leaving the node itself intact.
838  */
839 static void
ba_remove_children_mailbox_nodes(GtkTreeModel * model,GtkTreeIter * parent,GSList ** specials)840 ba_remove_children_mailbox_nodes(GtkTreeModel * model, GtkTreeIter * parent,
841 				 GSList ** specials)
842 {
843     GtkTreeIter iter;
844     BalsaMailboxNode *mbnode;
845     gboolean valid;
846 
847     if (!gtk_tree_model_iter_children(model, &iter, parent))
848 	return;
849 
850     do {
851 	gtk_tree_model_get(model, &iter, 0, &mbnode, -1);
852 	if (mbnode->parent) {
853 	    LibBalsaMailbox *mailbox = mbnode->mailbox;
854 	    if (mailbox == balsa_app.inbox
855 		|| mailbox == balsa_app.outbox
856 		|| mailbox == balsa_app.sentbox
857 		|| mailbox == balsa_app.draftbox
858 		|| mailbox == balsa_app.trash) {
859 		g_object_ref(mailbox);
860 		*specials = g_slist_prepend(*specials, mailbox);
861 	    }
862 	    ba_remove_children_mailbox_nodes(model, &iter, specials);
863 	    valid =
864 		gtk_tree_store_remove(balsa_app.mblist_tree_store, &iter);
865 	} else {
866 	    printf("sparing %s %s\n",
867 		   mbnode->mailbox ? "mailbox" : "folder ",
868 		   mbnode->mailbox ? mbnode->mailbox->name : mbnode->name);
869 	    valid = gtk_tree_model_iter_next(model, &iter);
870 	}
871 	g_object_unref(mbnode);
872     } while (valid);
873 }
874 
875 void
balsa_remove_children_mailbox_nodes(BalsaMailboxNode * mbnode)876 balsa_remove_children_mailbox_nodes(BalsaMailboxNode * mbnode)
877 {
878     GtkTreeModel *model = GTK_TREE_MODEL(balsa_app.mblist_tree_store);
879     GtkTreeIter parent;
880     GtkTreeIter *iter = NULL;
881     GSList *specials = NULL, *l;
882 
883     if (balsa_app.debug)
884 	printf("Destroying children of %p %s\n",
885 	       mbnode, mbnode && mbnode->name ? mbnode->name : "");
886 
887     if (mbnode && balsa_find_iter_by_data(&parent, mbnode))
888 	iter = &parent;
889 
890     ba_remove_children_mailbox_nodes(model, iter, &specials);
891 
892     for (l = specials; l; l = l->next)
893         balsa_mblist_mailbox_node_append(NULL,
894                                          balsa_mailbox_node_new_from_mailbox
895                                          (l->data));
896     g_slist_free(specials);
897 }
898 
899 /* balsa_find_index_by_mailbox:
900    returns BalsaIndex displaying passed mailbox, or NULL, if mailbox is
901    not displayed.
902 */
903 BalsaIndex*
balsa_find_index_by_mailbox(LibBalsaMailbox * mailbox)904 balsa_find_index_by_mailbox(LibBalsaMailbox * mailbox)
905 {
906     GtkWidget *page;
907     GtkWidget *index;
908     guint i;
909     g_return_val_if_fail(balsa_app.notebook, NULL);
910 
911     for (i = 0;
912 	 (page =
913 	  gtk_notebook_get_nth_page(GTK_NOTEBOOK(balsa_app.notebook), i));
914 	 i++) {
915         index = gtk_bin_get_child(GTK_BIN(page));
916 	if (index && BALSA_INDEX(index)->mailbox_node
917             && BALSA_INDEX(index)->mailbox_node->mailbox == mailbox)
918 	    return BALSA_INDEX(index);
919     }
920 
921     /* didn't find a matching mailbox */
922     return NULL;
923 }
924 
925 #if USE_GREGEX
926 GRegex *
balsa_quote_regex_new(void)927 balsa_quote_regex_new(void)
928 {
929     static GRegex *regex  = NULL;
930     static gchar  *string = NULL;
931 
932     if (string && strcmp(string, balsa_app.quote_regex) != 0) {
933         g_free(string);
934         string = NULL;
935         g_regex_unref(regex);
936         regex = NULL;
937     }
938 
939     if (!regex) {
940         GError *err = NULL;
941 
942         regex = g_regex_new(balsa_app.quote_regex, 0, 0, &err);
943         if (err) {
944             g_warning("quote regex compilation failed: %s", err->message);
945             g_error_free(err);
946             return NULL;
947         }
948         string = g_strdup(balsa_app.quote_regex);
949     }
950 
951     return g_regex_ref(regex);
952 }
953 #endif                          /* USE_GREGEX */
954