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