Updated Traditional Chinese translation(Hong Kong). Updated Traditional
[evolution.git] / mail / mail-component.c
blob585c5b20fcc8259f6025b593bd4ef59faec5d558
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* mail-component.c
4 * Copyright (C) 2003 Ximian Inc.
6 * Authors: Ettore Perazzoli <ettore@ximian.com>
7 * Michael Zucchi <notzed@ximian.com>
8 * Jeffrey Stedfast <fejj@ximian.com>
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of version 2 of the GNU General Public
12 * License as published by the Free Software Foundation.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
19 * You should have received a copy of the GNU General Public
20 * License along with this program; if not, write to the
21 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 * Boston, MA 02111-1307, USA.
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
30 #include <stdlib.h>
31 #include <string.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <unistd.h>
35 #include <fcntl.h>
36 #include <errno.h>
38 #include <libedataserver/e-data-server-util.h>
39 #include "em-utils.h"
40 #include "em-composer-utils.h"
41 #include "em-format.h"
42 #include "em-folder-tree.h"
43 #include "em-folder-browser.h"
44 #include "em-message-browser.h"
45 #include "em-folder-selector.h"
46 #include "em-folder-selection.h"
47 #include "em-folder-utils.h"
48 #include "em-migrate.h"
50 #include "misc/e-info-label.h"
51 #include "e-util/e-error.h"
52 #include "e-util/e-util-private.h"
54 #include "em-search-context.h"
55 #include "mail-config.h"
56 #include "mail-component.h"
57 #include "mail-folder-cache.h"
58 #include "mail-vfolder.h"
59 #include "mail-mt.h"
60 #include "mail-ops.h"
61 #include "mail-tools.h"
62 #include "mail-send-recv.h"
63 #include "mail-session.h"
64 #include "message-list.h"
66 #include "e-activity-handler.h"
67 #include "shell/e-user-creatable-items-handler.h"
68 #include "shell/e-component-view.h"
70 #include "composer/e-msg-composer.h"
72 #include "e-task-bar.h"
74 #include <gtk/gtklabel.h>
76 #include <e-util/e-mktemp.h>
77 #include <Evolution.h>
79 #include <table/e-tree.h>
80 #include <table/e-tree-memory.h>
81 #include <libgnome/gnome-i18n.h>
83 #include <camel/camel-file-utils.h>
84 #include <camel/camel-vtrash-folder.h>
85 #include <camel/camel-disco-store.h>
86 #include <camel/camel-offline-store.h>
88 #include <bonobo/bonobo-control.h>
89 #include <bonobo/bonobo-widget.h>
91 #define d(x)
93 static void create_local_item_cb(EUserCreatableItemsHandler *handler, const char *item_type_name, void *data);
94 static void view_changed_timeout_remove (EComponentView *component_view);
96 #define MAIL_COMPONENT_DEFAULT(mc) if (mc == NULL) mc = mail_component_peek();
98 #define PARENT_TYPE evolution_component_get_type ()
99 static BonoboObjectClass *parent_class = NULL;
101 #define OFFLINE 0
102 #define ONLINE 1
104 struct _store_info {
105 CamelStore *store;
106 char *name;
108 /* we keep a reference to these so they remain around for the session */
109 CamelFolder *vtrash;
110 CamelFolder *vjunk;
112 /* for setup only */
113 void (*done)(CamelStore *store, CamelFolderInfo *info, void *data);
114 void *done_data;
116 int ref_count:31;
117 guint removed:1;
120 struct _MailComponentPrivate {
121 GMutex *lock;
123 /* states/data used during shutdown */
124 enum { MC_QUIT_START, MC_QUIT_SYNC, MC_QUIT_THREADS } quit_state;
125 int quit_count;
126 int quit_expunge; /* expunge on quit this time around? */
128 char *base_directory;
130 EMFolderTreeModel *model;
132 EActivityHandler *activity_handler;
134 MailAsyncEvent *async_event;
135 GHashTable *store_hash; /* stores store_info objects by store */
137 RuleContext *search_context;
139 char *context_path; /* current path for right-click menu */
141 CamelStore *local_store;
144 /* indexed by _mail_component_folder_t */
145 static struct {
146 char *name;
147 char *uri;
148 CamelFolder *folder;
149 } mc_default_folders[] = {
150 /* translators: standard local mailbox names */
151 { N_("Inbox"), },
152 { N_("Drafts"), },
153 { N_("Outbox"), },
154 { N_("Sent"), },
155 { "Inbox", }, /* 'always local' inbox */
158 static struct _store_info *
159 store_info_new(CamelStore *store, const char *name)
161 struct _store_info *si;
163 si = g_malloc0(sizeof(*si));
164 si->ref_count = 1;
165 if (name == NULL)
166 si->name = camel_service_get_name((CamelService *)store, TRUE);
167 else
168 si->name = g_strdup(name);
169 si->store = store;
170 camel_object_ref(store);
171 /* If these are vfolders then they need to be opened now,
172 * otherwise they wont keep track of all folders */
173 if ((store->flags & CAMEL_STORE_VTRASH) != 0)
174 si->vtrash = camel_store_get_trash(store, NULL);
175 if ((store->flags & CAMEL_STORE_VJUNK) != 0)
176 si->vjunk = camel_store_get_junk(store, NULL);
178 return si;
181 static void
182 store_info_ref(struct _store_info *si)
184 si->ref_count++;
187 static void
188 store_info_unref(struct _store_info *si)
190 if (si->ref_count > 1) {
191 si->ref_count--;
192 return;
195 if (si->vtrash)
196 camel_object_unref(si->vtrash);
197 if (si->vjunk)
198 camel_object_unref(si->vjunk);
199 camel_object_unref(si->store);
200 g_free(si->name);
201 g_free(si);
204 static void
205 mc_add_store_done(CamelStore *store, CamelFolderInfo *info, void *data)
207 struct _store_info *si = data;
209 if (si->done)
210 si->done(store, info, si);
212 if (!si->removed) {
213 /* let the counters know about the already opened junk/trash folders */
214 if (si->vtrash)
215 mail_note_folder(si->vtrash);
216 if (si->vjunk)
217 mail_note_folder(si->vjunk);
220 store_info_unref(si);
223 /* Utility functions. */
224 static void
225 mc_add_store(MailComponent *component, CamelStore *store, const char *name, void (*done)(CamelStore *store, CamelFolderInfo *info, void *data))
227 struct _store_info *si;
229 MAIL_COMPONENT_DEFAULT(component);
231 si = store_info_new(store, name);
232 si->done = done;
233 g_hash_table_insert(component->priv->store_hash, store, si);
234 em_folder_tree_model_add_store(component->priv->model, store, si->name);
235 store_info_ref(si);
236 mail_note_store(store, NULL, mc_add_store_done, si);
239 static void
240 mc_add_local_store_done(CamelStore *store, CamelFolderInfo *info, void *data)
242 /*MailComponent *mc = data;*/
243 int i;
245 for (i=0;i<sizeof(mc_default_folders)/sizeof(mc_default_folders[0]);i++) {
246 if (mc_default_folders[i].folder)
247 mail_note_folder(mc_default_folders[i].folder);
251 static void
252 mc_add_local_store(CamelStore *store, const char *name, MailComponent *mc)
254 mc_add_store(mc, store, name, mc_add_local_store_done);
255 camel_object_unref(store);
256 g_object_unref(mc);
259 static void
260 mc_setup_local_store(MailComponent *mc)
262 MailComponentPrivate *p = mc->priv;
263 CamelURL *url;
264 char *tmp;
265 CamelException ex;
266 int i;
268 g_mutex_lock(p->lock);
269 if (p->local_store != NULL) {
270 g_mutex_unlock(p->lock);
271 return;
274 camel_exception_init(&ex);
276 url = camel_url_new("mbox:", NULL);
277 tmp = g_strdup_printf("%s/mail/local", p->base_directory);
278 camel_url_set_path(url, tmp);
279 g_free(tmp);
280 tmp = camel_url_to_string(url, 0);
281 p->local_store = (CamelStore *)camel_session_get_service(session, tmp, CAMEL_PROVIDER_STORE, &ex);
282 g_free(tmp);
283 if (p->local_store == NULL)
284 goto fail;
286 for (i=0;i<sizeof(mc_default_folders)/sizeof(mc_default_folders[0]);i++) {
287 /* FIXME: should this uri be account relative? */
288 camel_url_set_fragment(url, mc_default_folders[i].name);
289 mc_default_folders[i].uri = camel_url_to_string(url, 0);
290 mc_default_folders[i].folder = camel_store_get_folder(p->local_store, mc_default_folders[i].name,
291 CAMEL_STORE_FOLDER_CREATE, &ex);
292 camel_exception_clear(&ex);
295 camel_url_free(url);
296 g_mutex_unlock(p->lock);
298 g_object_ref(mc);
299 camel_object_ref(p->local_store);
300 mail_async_event_emit(p->async_event, MAIL_ASYNC_GUI, (MailAsyncFunc)mc_add_local_store, p->local_store, _("On This Computer"), mc);
302 return;
303 fail:
304 g_mutex_unlock(p->lock);
306 g_warning("Could not setup local store/folder: %s", ex.desc);
308 camel_url_free(url);
309 camel_exception_clear(&ex);
312 static void
313 load_accounts (MailComponent *component, EAccountList *accounts)
315 EIterator *iter;
317 /* Load each service (don't connect!). Check its provider and
318 * see if this belongs in the shell's folder list. If so, add
319 * it.
322 iter = e_list_get_iterator ((EList *) accounts);
323 while (e_iterator_is_valid (iter)) {
324 EAccountService *service;
325 EAccount *account;
326 const char *name;
328 account = (EAccount *) e_iterator_get (iter);
329 service = account->source;
330 name = account->name;
332 /* HACK: mbox url's are handled by the local store setup above,
333 any that come through as account sources are really movemail sources! */
334 if (account->enabled
335 && service->url != NULL
336 && service->url[0]
337 && strncmp(service->url, "mbox:", 5) != 0)
338 mail_component_load_store_by_uri (component, service->url, name);
340 e_iterator_next (iter);
343 g_object_unref (iter);
346 static void
347 setup_search_context (MailComponent *component)
349 MailComponentPrivate *priv = component->priv;
351 if (priv->search_context == NULL) {
352 char *user = g_build_filename(component->priv->base_directory, "mail/searches.xml", NULL);
353 char *system = g_build_filename (EVOLUTION_PRIVDATADIR, "searchtypes.xml", NULL);
355 priv->search_context = (RuleContext *)em_search_context_new ();
356 g_object_set_data_full (G_OBJECT (priv->search_context), "user", user, g_free);
357 g_object_set_data_full (G_OBJECT (priv->search_context), "system", system, g_free);
358 rule_context_load (priv->search_context, system, user);
362 static void
363 mc_startup(MailComponent *mc)
365 static int started = 0;
367 if (started)
368 return;
369 started = 1;
371 mc_setup_local_store(mc);
372 load_accounts(mc, mail_config_get_accounts());
373 vfolder_load_storage();
376 static void
377 folder_selected_cb (EMFolderTree *emft, const char *path, const char *uri, guint32 flags, EMFolderView *view)
379 EMFolderTreeModel *model;
381 if ((flags & CAMEL_FOLDER_NOSELECT) || !path) {
382 em_folder_view_set_folder (view, NULL, NULL);
383 } else {
384 model = em_folder_tree_get_model (emft);
385 em_folder_tree_model_set_selected (model, uri);
386 em_folder_tree_model_save_state (model);
388 em_folder_view_set_folder_uri (view, uri);
392 static int
393 check_autosave(void *data)
395 e_msg_composer_check_autosave(NULL);
397 return FALSE;
400 static void
401 view_control_activate_cb (BonoboControl *control, gboolean activate, EMFolderView *view)
403 BonoboUIComponent *uic;
404 static int recover = 0;
406 uic = bonobo_control_get_ui_component (control);
407 g_assert (uic != NULL);
409 if (activate) {
410 Bonobo_UIContainer container;
412 container = bonobo_control_get_remote_ui_container (control, NULL);
413 bonobo_ui_component_set_container (uic, container, NULL);
414 bonobo_object_release_unref (container, NULL);
416 g_assert (container == bonobo_ui_component_get_container(uic));
417 g_return_if_fail (container != CORBA_OBJECT_NIL);
419 em_folder_view_activate (view, uic, activate);
420 e_user_creatable_items_handler_activate(g_object_get_data((GObject *)view, "e-creatable-items-handler"), uic);
421 } else {
422 em_folder_view_activate (view, uic, activate);
423 bonobo_ui_component_unset_container (uic, NULL);
426 /* This is a weird place to put it, but createControls does it too early.
427 I also think we should wait to do it until we actually visit the mailer.
428 The delay is arbitrary - without it it shows up before the main window */
429 if (!recover) {
430 recover = 1;
431 g_timeout_add(1000, check_autosave, NULL);
435 /* GObject methods. */
437 static void
438 impl_dispose (GObject *object)
440 MailComponentPrivate *priv = MAIL_COMPONENT (object)->priv;
442 view_changed_timeout_remove ((EComponentView *)object);
444 if (priv->activity_handler != NULL) {
445 g_object_unref (priv->activity_handler);
446 priv->activity_handler = NULL;
449 if (priv->search_context != NULL) {
450 g_object_unref (priv->search_context);
451 priv->search_context = NULL;
454 if (priv->local_store != NULL) {
455 camel_object_unref (priv->local_store);
456 priv->local_store = NULL;
459 (* G_OBJECT_CLASS (parent_class)->dispose) (object);
462 static void
463 store_hash_free (CamelStore *store, struct _store_info *si, void *data)
465 si->removed = 1;
466 store_info_unref(si);
469 static void
470 impl_finalize (GObject *object)
472 MailComponentPrivate *priv = MAIL_COMPONENT (object)->priv;
474 g_free (priv->base_directory);
476 mail_async_event_destroy (priv->async_event);
478 g_hash_table_foreach (priv->store_hash, (GHFunc)store_hash_free, NULL);
479 g_hash_table_destroy (priv->store_hash);
481 if (mail_async_event_destroy (priv->async_event) == -1) {
482 g_warning("Cannot destroy async event: would deadlock");
483 g_warning(" system may be unstable at exit");
486 g_free (priv->context_path);
487 g_mutex_free(priv->lock);
488 g_free (priv);
490 (* G_OBJECT_CLASS (parent_class)->finalize) (object);
493 static void
494 view_on_url (GObject *emitter, const char *url, const char *nice_url, MailComponent *mail_component)
496 MailComponentPrivate *priv = mail_component->priv;
498 e_activity_handler_set_message (priv->activity_handler, nice_url);
501 static void
502 view_changed(EMFolderView *emfv, EComponentView *component_view)
504 EInfoLabel *el = g_object_get_data((GObject *)component_view, "info-label");
505 CORBA_Environment ev;
507 CORBA_exception_init(&ev);
509 if (emfv->folder) {
510 char *name, *title;
511 guint32 visible, unread, deleted, junked;
512 GString *tmp = g_string_new("");
514 camel_object_get(emfv->folder, NULL,
515 CAMEL_FOLDER_NAME, &name,
516 CAMEL_FOLDER_DELETED, &deleted,
517 CAMEL_FOLDER_VISIBLE, &visible,
518 CAMEL_FOLDER_JUNKED, &junked,
519 CAMEL_FOLDER_UNREAD, &unread, NULL);
521 if (CAMEL_IS_VTRASH_FOLDER(emfv->folder)) {
522 if (((CamelVTrashFolder *)emfv->folder)->type == CAMEL_VTRASH_FOLDER_TRASH)
523 g_string_append_printf(tmp, ngettext ("%d deleted", "%d deleted", deleted), deleted);
524 else
525 g_string_append_printf(tmp, ngettext ("%d junk", "%d junk", junked), junked);
526 } else {
527 int bits = 0;
528 GPtrArray *selected;
530 /* This is so that if any of these are
531 * shared/reused, we fallback to the standard
532 * display behaviour */
534 selected = message_list_get_selected(emfv->list);
536 if (em_utils_folder_is_drafts(emfv->folder, emfv->folder_uri))
537 bits |= 1;
538 if (em_utils_folder_is_sent(emfv->folder, emfv->folder_uri))
539 bits |= 2;
540 if (em_utils_folder_is_outbox(emfv->folder, emfv->folder_uri))
541 bits |= 4;
542 /* HACK: hardcoded inbox or maildir '.' folder */
543 if (g_ascii_strcasecmp(emfv->folder->full_name, "inbox") == 0
544 || g_ascii_strcasecmp(emfv->folder->full_name, ".") == 0)
545 bits |= 8;
547 if (bits == 1)
548 g_string_append_printf(tmp, ngettext ("%d draft", "%d drafts", visible), visible);
549 else if (bits == 2)
550 g_string_append_printf(tmp, ngettext ("%d sent", "%d sent", visible), visible);
551 else if (bits == 4)
552 g_string_append_printf(tmp, ngettext ("%d unsent", "%d unsent", visible), visible);
553 else {
554 if (!emfv->hide_deleted)
555 visible += deleted;
556 g_string_append_printf(tmp, ngettext ("%d total", "%d total", visible), visible);
557 if (unread && selected->len <=1)
558 g_string_append_printf(tmp, ngettext (", %d unread", ", %d unread", unread), unread);
561 if (selected->len > 1)
562 g_string_append_printf(tmp, ngettext (", %d selected", ", %d selected", selected->len), selected->len);
563 message_list_free_uids(emfv->list, selected);
566 if (emfv->folder->parent_store == mail_component_peek_local_store(NULL)
567 && (!strcmp (name, "Drafts") || !strcmp (name, "Inbox")
568 || !strcmp (name, "Outbox") || !strcmp (name, "Sent")))
569 e_info_label_set_info(el, _(name), tmp->str);
570 else
571 e_info_label_set_info(el, name, tmp->str);
573 title = g_strdup_printf("%s (%s)", _(name), tmp->str);
574 e_component_view_set_title(component_view, title);
575 g_free(title);
577 g_string_free(tmp, TRUE);
578 camel_object_free(emfv->folder, CAMEL_FOLDER_NAME, name);
579 } else {
580 e_info_label_set_info(el, _("Mail"), "");
581 e_component_view_set_title(component_view, _("Mail"));
585 static void
586 view_changed_timeout_remove (EComponentView *component_view)
588 gpointer v;
589 EInfoLabel *el;
590 EMFolderView *emfv;
592 v = g_object_get_data((GObject *)component_view, "view-changed-timeout");
593 if (v) {
594 g_source_remove(GPOINTER_TO_INT(v));
595 g_object_set_data((GObject *)component_view, "view-changed-timeout", NULL);
597 el = g_object_get_data((GObject *)component_view, "info-label");
598 emfv = g_object_get_data((GObject *)el, "folderview");
599 g_object_unref(el);
600 g_object_unref(emfv);
604 static int
605 view_changed_timeout(void *d)
607 EComponentView *component_view = d;
608 EInfoLabel *el = g_object_get_data((GObject *)component_view, "info-label");
609 EMFolderView *emfv = g_object_get_data((GObject *)el, "folderview");
611 view_changed(emfv, component_view);
613 g_object_set_data((GObject *)component_view, "view-changed-timeout", NULL);
615 g_object_unref(el);
616 g_object_unref(emfv);
618 return 0;
621 static void
622 view_changed_cb(EMFolderView *emfv, EComponentView *component_view)
624 void *v;
625 EInfoLabel *el = g_object_get_data((GObject *)component_view, "info-label");
627 /* This can get called 3 times every cursor move, so
628 we don't need to/want to run it immediately */
630 /* NB: we should have a 'view' struct/object to manage this crap, but this'll do for now */
631 v = g_object_get_data((GObject *)component_view, "view-changed-timeout");
632 if (v) {
633 g_source_remove(GPOINTER_TO_INT(v));
634 } else {
635 g_object_ref(emfv);
636 g_object_ref(el);
639 g_object_set_data((GObject *)component_view, "view-changed-timeout", GINT_TO_POINTER(g_timeout_add(250, view_changed_timeout, component_view)));
642 static void
643 disable_folder_tree (gpointer *emfb, GtkWidget *widget)
645 gtk_widget_set_sensitive (widget, FALSE);
648 static void
649 enable_folder_tree (GtkWidget *emfb, GtkWidget *emft)
651 char *uri;
652 CamelURL *selected_curl, *current_curl;
653 CamelFolder *folder;
654 CamelException ex;
655 EMFolderView *emfv = (EMFolderView *)emfb;
657 /* Currently displayed folder */
658 MessageList *ml = emfv->list;
659 folder = ml->folder;
660 uri = mail_tools_folder_to_url (folder);
661 current_curl = camel_url_new (uri, NULL);
663 /* Selected folder in emft*/
664 uri = em_folder_tree_get_selected_uri ((EMFolderTree *) emft);
665 folder = mail_tool_uri_to_folder (uri, 0, &ex);
666 selected_curl = camel_url_new (uri, NULL);
668 if (!camel_url_equal (selected_curl, current_curl))
669 g_signal_emit_by_name (emft, "folder-selected", emft, uri, folder->full_name, uri, folder->folder_flags);
671 gtk_widget_set_sensitive (emft, TRUE);
673 camel_url_free (current_curl);
674 camel_url_free (selected_curl);
675 g_free (uri);
679 /* Evolution::Component CORBA methods. */
681 static GNOME_Evolution_ComponentView
682 impl_createView (PortableServer_Servant servant,
683 GNOME_Evolution_ShellView parent,
684 CORBA_Environment *ev)
686 MailComponent *mail_component = MAIL_COMPONENT (bonobo_object_from_servant (servant));
687 MailComponentPrivate *priv = mail_component->priv;
688 EComponentView *component_view;
689 GtkWidget *tree_widget, *vbox, *info;
690 GtkWidget *view_widget;
691 GtkWidget *statusbar_widget;
692 char *uri;
694 mail_session_set_interactive(TRUE);
695 mc_startup(mail_component);
697 view_widget = em_folder_browser_new ();
699 tree_widget = (GtkWidget *) em_folder_tree_new_with_model (priv->model);
700 em_folder_tree_set_excluded ((EMFolderTree *) tree_widget, 0);
701 em_folder_tree_enable_drag_and_drop ((EMFolderTree *) tree_widget);
703 if ((uri = em_folder_tree_model_get_selected (priv->model))) {
704 em_folder_tree_set_selected ((EMFolderTree *) tree_widget, uri);
705 g_free (uri);
708 em_format_set_session ((EMFormat *) ((EMFolderView *) view_widget)->preview, session);
710 g_signal_connect (view_widget, "on-url", G_CALLBACK (view_on_url), mail_component);
711 em_folder_view_set_statusbar ((EMFolderView*)view_widget, FALSE);
713 statusbar_widget = e_task_bar_new ();
714 e_activity_handler_attach_task_bar (priv->activity_handler, E_TASK_BAR (statusbar_widget));
716 gtk_widget_show (tree_widget);
717 gtk_widget_show (view_widget);
718 gtk_widget_show (statusbar_widget);
720 vbox = gtk_vbox_new(FALSE, 0);
721 info = e_info_label_new("stock_mail");
722 e_info_label_set_info((EInfoLabel *)info, _("Mail"), "");
723 gtk_box_pack_start((GtkBox *)vbox, info, FALSE, TRUE, 0);
724 gtk_box_pack_start((GtkBox *)vbox, tree_widget, TRUE, TRUE, 0);
726 gtk_widget_show(info);
727 gtk_widget_show(vbox);
729 component_view = e_component_view_new(parent, "mail", vbox, view_widget, statusbar_widget);
731 g_object_set_data((GObject *)component_view, "info-label", info);
733 g_object_set_data_full((GObject *)view_widget, "e-creatable-items-handler",
734 e_user_creatable_items_handler_new("mail", create_local_item_cb, tree_widget),
735 (GDestroyNotify)g_object_unref);
738 g_signal_connect (component_view->view_control, "activate", G_CALLBACK (view_control_activate_cb), view_widget);
739 g_signal_connect (tree_widget, "folder-selected", G_CALLBACK (folder_selected_cb), view_widget);
741 g_signal_connect((EMFolderBrowser *)view_widget, "account_search_cleared", G_CALLBACK (enable_folder_tree), tree_widget);
742 g_signal_connect(((EMFolderBrowser *)view_widget), "account_search_activated", G_CALLBACK (disable_folder_tree), tree_widget);
743 g_signal_connect(view_widget, "changed", G_CALLBACK(view_changed_cb), component_view);
744 g_signal_connect(view_widget, "loaded", G_CALLBACK(view_changed_cb), component_view);
746 g_object_set_data((GObject*)info, "folderview", view_widget);
748 return BONOBO_OBJREF(component_view);
751 static CORBA_boolean
752 impl_requestQuit(PortableServer_Servant servant, CORBA_Environment *ev)
754 /*MailComponent *mc = MAIL_COMPONENT(bonobo_object_from_servant(servant));*/
755 CamelFolder *folder;
756 guint32 unsent;
758 if (!e_msg_composer_request_close_all())
759 return FALSE;
761 folder = mc_default_folders[MAIL_COMPONENT_FOLDER_OUTBOX].folder;
762 if (folder != NULL
763 && camel_session_is_online(session)
764 && camel_object_get(folder, NULL, CAMEL_FOLDER_VISIBLE, &unsent, 0) == 0
765 && unsent > 0
766 && e_error_run(NULL, "mail:exit-unsaved", NULL) != GTK_RESPONSE_YES)
767 return FALSE;
769 return TRUE;
772 static void
773 mc_quit_sync_done(CamelStore *store, void *data)
775 MailComponent *mc = data;
777 mc->priv->quit_count--;
780 static void
781 mc_quit_sync(CamelStore *store, struct _store_info *si, MailComponent *mc)
783 mc->priv->quit_count++;
784 mail_sync_store(store, mc->priv->quit_expunge, mc_quit_sync_done, mc);
787 static CORBA_boolean
788 impl_quit(PortableServer_Servant servant, CORBA_Environment *ev)
790 MailComponent *mc = MAIL_COMPONENT(bonobo_object_from_servant(servant));
792 mail_config_prune_proxies ();
793 switch (mc->priv->quit_state) {
794 case MC_QUIT_START: {
795 int now = time(NULL)/60/60/24, days;
796 GConfClient *gconf = mail_config_get_gconf_client();
798 mail_vfolder_shutdown();
800 mc->priv->quit_expunge = gconf_client_get_bool(gconf, "/apps/evolution/mail/trash/empty_on_exit", NULL)
801 && ((days = gconf_client_get_int(gconf, "/apps/evolution/mail/trash/empty_on_exit_days", NULL)) == 0
802 || (days + gconf_client_get_int(gconf, "/apps/evolution/mail/trash/empty_date", NULL)) <= now);
804 g_hash_table_foreach(mc->priv->store_hash, (GHFunc)mc_quit_sync, mc);
806 if (mc->priv->quit_expunge)
807 gconf_client_set_int(gconf, "/apps/evolution/mail/trash/empty_date", now, NULL);
809 mc->priv->quit_state = MC_QUIT_SYNC;
811 /* Falls through */
812 case MC_QUIT_SYNC:
813 if (mc->priv->quit_count > 0)
814 return FALSE;
816 mail_cancel_all();
817 mc->priv->quit_state = MC_QUIT_THREADS;
819 /* Falls through */
820 case MC_QUIT_THREADS:
821 /* should we keep cancelling? */
822 return !mail_msg_active((unsigned int)-1);
825 return TRUE;
828 static GNOME_Evolution_CreatableItemTypeList *
829 impl__get_userCreatableItems (PortableServer_Servant servant, CORBA_Environment *ev)
831 GNOME_Evolution_CreatableItemTypeList *list = GNOME_Evolution_CreatableItemTypeList__alloc ();
833 list->_length = 2;
834 list->_maximum = list->_length;
835 list->_buffer = GNOME_Evolution_CreatableItemTypeList_allocbuf (list->_length);
837 CORBA_sequence_set_release (list, FALSE);
839 list->_buffer[0].id = "message";
840 list->_buffer[0].description = _("New Mail Message");
841 list->_buffer[0].menuDescription = _("_Mail Message");
842 list->_buffer[0].tooltip = _("Compose a new mail message");
843 list->_buffer[0].menuShortcut = 'm';
844 list->_buffer[0].iconName = "stock_mail-compose";
845 list->_buffer[0].type = GNOME_Evolution_CREATABLE_OBJECT;
847 list->_buffer[1].id = "folder";
848 list->_buffer[1].description = _("New Mail Folder");
849 list->_buffer[1].menuDescription = _("Mail _Folder");
850 list->_buffer[1].tooltip = _("Create a new mail folder");
851 list->_buffer[1].menuShortcut = '\0';
852 list->_buffer[1].iconName = "stock_new-dir";
853 list->_buffer[1].type = GNOME_Evolution_CREATABLE_FOLDER;
855 return list;
858 static int
859 create_item(const char *type, EMFolderTreeModel *model, const char *uri)
861 if (strcmp(type, "message") == 0) {
862 if (!em_utils_check_user_can_send_mail(NULL))
863 return 0;
865 em_utils_compose_new_message(uri);
866 } else if (strcmp(type, "folder") == 0) {
867 em_folder_utils_create_folder(NULL);
868 } else
869 return -1;
871 return 0;
874 static void
875 create_local_item_cb(EUserCreatableItemsHandler *handler, const char *item_type_name, void *data)
877 EMFolderTree *tree = data;
878 char *uri = em_folder_tree_get_selected_uri(tree);
880 create_item(item_type_name, em_folder_tree_get_model(tree), uri);
881 g_free(uri);
884 static void
885 impl_requestCreateItem (PortableServer_Servant servant,
886 const CORBA_char *item_type_name,
887 CORBA_Environment *ev)
889 MailComponent *mc = MAIL_COMPONENT(bonobo_object_from_servant(servant));
891 if (create_item(item_type_name, mc->priv->model, NULL) == -1) {
892 CORBA_exception_set (ev, CORBA_USER_EXCEPTION,
893 ex_GNOME_Evolution_Component_UnknownType, NULL);
897 static void
898 handleuri_got_folder(char *uri, CamelFolder *folder, void *data)
900 CamelURL *url = data;
901 EMMessageBrowser *emmb;
903 if (folder != NULL) {
904 const char *reply = camel_url_get_param(url, "reply");
905 const char *forward = camel_url_get_param(url, "forward");
906 int mode;
908 if (reply) {
910 if (!strcmp(reply, "all"))
911 mode = REPLY_MODE_ALL;
912 else if (!strcmp(reply, "list"))
913 mode = REPLY_MODE_LIST;
914 else /* if "sender" or anything else */
915 mode = REPLY_MODE_SENDER;
917 em_utils_reply_to_message(folder, camel_url_get_param(url, "uid"), NULL, mode, NULL);
918 } else if (forward) {
919 GPtrArray *uids;
920 const char* uid;
922 uid = camel_url_get_param(url, "uid");
923 if (uid == NULL)
924 g_warning("Could not forward the message. UID is NULL.");
925 else {
926 uids = g_ptr_array_new();
927 g_ptr_array_add(uids, g_strdup(uid));
929 if (!strcmp(forward, "attached"))
930 em_utils_forward_attached(folder, uids, uri);
931 else if (!strcmp(forward, "inline"))
932 em_utils_forward_inline(folder, uids, uri);
933 else if (!strcmp(forward, "quoted"))
934 em_utils_forward_quoted(folder, uids, uri);
935 else { /* Just the default forward */
936 em_utils_forward_messages(folder, uids, uri);
939 } else {
940 emmb = (EMMessageBrowser *)em_message_browser_window_new();
941 /*message_list_set_threaded(((EMFolderView *)emmb)->list, emfv->list->threaded);*/
942 /* FIXME: session needs to be passed easier than this */
943 em_format_set_session((EMFormat *)((EMFolderView *)emmb)->preview, session);
944 em_folder_view_set_folder((EMFolderView *)emmb, folder, uri);
945 em_folder_view_set_message((EMFolderView *)emmb, camel_url_get_param(url, "uid"), FALSE);
946 gtk_widget_show(emmb->window);
948 } else {
949 g_warning("Couldn't open folder '%s'", uri);
951 camel_url_free(url);
954 static void
955 impl_handleURI (PortableServer_Servant servant, const char *uri, CORBA_Environment *ev)
957 if (!strncmp (uri, "mailto:", 7)) {
958 if (!em_utils_check_user_can_send_mail(NULL))
959 return;
961 em_utils_compose_new_message_with_mailto (uri, NULL);
962 } else if (!strncmp(uri, "email:", 6)) {
963 CamelURL *url = camel_url_new(uri, NULL);
965 if (camel_url_get_param(url, "uid") != NULL) {
966 char *curi = em_uri_to_camel(uri);
968 mail_get_folder(curi, 0, handleuri_got_folder, url, mail_thread_new);
969 g_free(curi);
970 } else {
971 g_warning("email uri's must include a uid parameter");
972 camel_url_free(url);
977 static void
978 impl_sendAndReceive (PortableServer_Servant servant, CORBA_Environment *ev)
980 mail_send_receive ();
983 static void
984 impl_upgradeFromVersion (PortableServer_Servant servant, const short major, const short minor, const short revision, CORBA_Environment *ev)
986 MailComponent *component;
987 CamelException ex;
989 component = mail_component_peek ();
991 camel_exception_init (&ex);
992 if (em_migrate (component->priv->base_directory, major, minor, revision, &ex) == -1) {
993 GNOME_Evolution_Component_UpgradeFailed *failedex;
995 failedex = GNOME_Evolution_Component_UpgradeFailed__alloc();
996 failedex->what = CORBA_string_dup(_("Failed upgrading Mail settings or folders."));
997 failedex->why = CORBA_string_dup(ex.desc);
998 CORBA_exception_set(ev, CORBA_USER_EXCEPTION, ex_GNOME_Evolution_Component_UpgradeFailed, failedex);
1001 camel_exception_clear (&ex);
1004 struct _setline_data {
1005 GNOME_Evolution_Listener listener;
1006 CORBA_boolean status;
1007 int pending;
1010 static void
1011 setline_done(CamelStore *store, void *data)
1013 struct _setline_data *sd = data;
1015 g_assert(sd->pending > 0);
1017 sd->pending--;
1018 if (sd->pending == 0) {
1019 CORBA_Environment ev = { 0 };
1021 GNOME_Evolution_Listener_complete(sd->listener, &ev);
1022 CORBA_exception_free(&ev);
1023 CORBA_Object_release(sd->listener, &ev);
1024 CORBA_exception_free(&ev);
1025 if (!sd->status)
1026 camel_session_set_online(session, sd->status);
1027 g_free(sd);
1031 static void
1032 setline_check(void *key, void *value, void *data)
1034 CamelService *service = key;
1035 struct _setline_data *sd = data;
1037 if (CAMEL_IS_DISCO_STORE(service)
1038 || CAMEL_IS_OFFLINE_STORE(service)) {
1039 sd->pending++;
1040 mail_store_set_offline((CamelStore *)service, !sd->status, setline_done, sd);
1044 int
1045 status_check (GNOME_Evolution_ShellState shell_state)
1047 int status;
1049 switch (shell_state)
1051 case GNOME_Evolution_USER_OFFLINE:
1052 status = OFFLINE;
1053 break;
1054 case GNOME_Evolution_FORCED_OFFLINE:
1055 /*Network is down so change network state on the camel session*/
1056 status = OFFLINE;
1057 /* Cancel all operations as they wont happen anyway cos Network is down*/
1058 mail_cancel_all ();
1059 camel_session_set_network_state (session, FALSE);
1060 break;
1061 case GNOME_Evolution_USER_ONLINE:
1062 camel_session_set_network_state (session, TRUE);
1063 status = ONLINE;
1066 return status;
1069 static void
1070 impl_setLineStatus(PortableServer_Servant servant, GNOME_Evolution_ShellState shell_state, GNOME_Evolution_Listener listener, CORBA_Environment *ev)
1072 struct _setline_data *sd;
1073 int status = status_check(shell_state);
1075 /* This will dis/enable further auto-mail-check action. */
1076 /* FIXME: If send/receive active, wait for it to finish? */
1077 if (status)
1078 camel_session_set_online(session, status);
1080 sd = g_malloc0(sizeof(*sd));
1081 sd->status = status;
1082 sd->listener = CORBA_Object_duplicate(listener, ev);
1083 if (ev->_major == CORBA_NO_EXCEPTION)
1084 mail_component_stores_foreach(mail_component_peek(), setline_check, sd);
1085 else
1086 CORBA_exception_free(ev);
1088 if (sd->pending == 0) {
1089 if (sd->listener) {
1090 CORBA_Object_release(sd->listener, ev);
1091 CORBA_exception_free(ev);
1094 g_free(sd);
1096 if (!status)
1097 camel_session_set_online(session, status);
1098 GNOME_Evolution_Listener_complete(listener, ev);
1102 static void
1103 impl_mail_test(PortableServer_Servant servant, CORBA_Environment *ev)
1105 printf("*** Testing mail interface!! ***\n");
1108 /* Initialization. */
1110 static void
1111 mail_component_class_init (MailComponentClass *class)
1113 POA_GNOME_Evolution_Component__epv *epv = &((EvolutionComponentClass *)class)->epv;
1114 POA_GNOME_Evolution_MailComponent__epv *mepv = &class->epv;
1115 GObjectClass *object_class = G_OBJECT_CLASS (class);
1117 parent_class = g_type_class_peek_parent (class);
1119 object_class->dispose = impl_dispose;
1120 object_class->finalize = impl_finalize;
1122 epv->createView = impl_createView;
1123 epv->requestQuit = impl_requestQuit;
1124 epv->quit = impl_quit;
1125 epv->_get_userCreatableItems = impl__get_userCreatableItems;
1126 epv->requestCreateItem = impl_requestCreateItem;
1127 epv->handleURI = impl_handleURI;
1128 epv->sendAndReceive = impl_sendAndReceive;
1129 epv->upgradeFromVersion = impl_upgradeFromVersion;
1130 epv->setLineStatus = impl_setLineStatus;
1132 mepv->test = impl_mail_test;
1135 static void
1136 mail_component_init (MailComponent *component)
1138 MailComponentPrivate *priv;
1140 priv = g_new0 (MailComponentPrivate, 1);
1141 component->priv = priv;
1143 priv->lock = g_mutex_new();
1145 priv->base_directory = g_build_filename (g_get_home_dir (), ".evolution", NULL);
1146 #ifdef G_OS_WIN32
1148 char *p = priv->base_directory;
1149 while ((p = strchr(p, '\\')))
1150 *p++ = '/';
1152 #endif
1153 if (e_util_mkdir_hier (priv->base_directory, 0777) == -1 && errno != EEXIST)
1154 abort ();
1156 priv->model = em_folder_tree_model_new (priv->base_directory);
1158 priv->activity_handler = e_activity_handler_new ();
1160 mail_session_init (priv->base_directory);
1162 priv->async_event = mail_async_event_new();
1163 priv->store_hash = g_hash_table_new (NULL, NULL);
1165 mail_autoreceive_init();
1168 /* Public API. */
1169 MailComponent *
1170 mail_component_peek (void)
1172 static MailComponent *component = NULL;
1174 if (component == NULL)
1175 component = g_object_new(mail_component_get_type(), NULL);
1177 return component;
1180 const char *
1181 mail_component_peek_base_directory (MailComponent *component)
1183 MAIL_COMPONENT_DEFAULT(component);
1185 return component->priv->base_directory;
1188 RuleContext *
1189 mail_component_peek_search_context (MailComponent *component)
1191 MAIL_COMPONENT_DEFAULT(component);
1193 setup_search_context(component);
1195 return component->priv->search_context;
1198 EActivityHandler *
1199 mail_component_peek_activity_handler (MailComponent *component)
1201 MAIL_COMPONENT_DEFAULT(component);
1203 return component->priv->activity_handler;
1206 struct _CamelSession *mail_component_peek_session(MailComponent *component)
1208 MAIL_COMPONENT_DEFAULT(component);
1210 return session;
1213 void
1214 mail_component_add_store (MailComponent *component, CamelStore *store, const char *name)
1216 mc_add_store(component, store, name, NULL);
1220 * mail_component_load_store_by_uri:
1221 * @component: mail component
1222 * @uri: uri of store
1223 * @name: name of store (used for display purposes)
1225 * Return value: Pointer to the newly added CamelStore. The caller is supposed
1226 * to ref the object if it wants to store it.
1228 CamelStore *
1229 mail_component_load_store_by_uri (MailComponent *component, const char *uri, const char *name)
1231 CamelException ex;
1232 CamelStore *store;
1233 CamelProvider *prov;
1235 MAIL_COMPONENT_DEFAULT(component);
1237 camel_exception_init (&ex);
1239 /* Load the service (don't connect!). Check its provider and
1240 * see if this belongs in the shell's folder list. If so, add
1241 * it.
1244 prov = camel_provider_get(uri, &ex);
1245 if (prov == NULL) {
1246 /* EPFIXME: real error dialog */
1247 g_warning ("couldn't get service %s: %s\n", uri,
1248 camel_exception_get_description (&ex));
1249 camel_exception_clear (&ex);
1250 return NULL;
1253 if (!(prov->flags & CAMEL_PROVIDER_IS_STORAGE))
1254 return NULL;
1256 store = (CamelStore *) camel_session_get_service (session, uri, CAMEL_PROVIDER_STORE, &ex);
1257 if (store == NULL) {
1258 /* EPFIXME: real error dialog */
1259 g_warning ("couldn't get service %s: %s\n", uri,
1260 camel_exception_get_description (&ex));
1261 camel_exception_clear (&ex);
1262 return NULL;
1265 mail_component_add_store(component, store, name);
1266 camel_object_unref (store);
1268 return store;
1271 static void
1272 store_disconnect (CamelStore *store, void *event_data, void *user_data)
1274 camel_service_disconnect (CAMEL_SERVICE (store), TRUE, NULL);
1275 camel_object_unref (store);
1278 void
1279 mail_component_remove_store (MailComponent *component, CamelStore *store)
1281 MailComponentPrivate *priv;
1282 struct _store_info *si;
1284 MAIL_COMPONENT_DEFAULT(component);
1286 priv = component->priv;
1288 /* Because the store_hash holds a reference to each store
1289 * used as a key in it, none of them will ever be gc'ed, meaning
1290 * any call to camel_session_get_{service,store} with the same
1291 * URL will always return the same object. So this works.
1294 if (!(si = g_hash_table_lookup (priv->store_hash, store)))
1295 return;
1297 camel_object_ref (store);
1298 g_hash_table_remove (priv->store_hash, store);
1299 si->removed = 1;
1300 store_info_unref(si);
1302 /* so i guess potentially we could have a race, add a store while one
1303 being removed. ?? */
1304 mail_note_store_remove (store);
1306 em_folder_tree_model_remove_store (priv->model, store);
1308 mail_async_event_emit (priv->async_event, MAIL_ASYNC_THREAD, (MailAsyncFunc) store_disconnect, store, NULL, NULL);
1311 void
1312 mail_component_remove_store_by_uri (MailComponent *component, const char *uri)
1314 CamelProvider *prov;
1315 CamelStore *store;
1317 MAIL_COMPONENT_DEFAULT(component);
1319 if (!(prov = camel_provider_get(uri, NULL)))
1320 return;
1322 if (!(prov->flags & CAMEL_PROVIDER_IS_STORAGE))
1323 return;
1325 store = (CamelStore *) camel_session_get_service (session, uri, CAMEL_PROVIDER_STORE, NULL);
1326 if (store != NULL) {
1327 mail_component_remove_store (component, store);
1328 camel_object_unref (store);
1333 mail_component_get_store_count (MailComponent *component)
1335 MAIL_COMPONENT_DEFAULT(component);
1337 return g_hash_table_size (component->priv->store_hash);
1340 /* need to map from internal struct to external api */
1341 struct _store_foreach_data {
1342 GHFunc func;
1343 void *data;
1346 static void
1347 mc_stores_foreach(CamelStore *store, struct _store_info *si, struct _store_foreach_data *data)
1349 data->func((void *)store, (void *)si->name, data->data);
1352 void
1353 mail_component_stores_foreach (MailComponent *component, GHFunc func, void *user_data)
1355 struct _store_foreach_data data = { func, user_data };
1357 MAIL_COMPONENT_DEFAULT(component);
1359 g_hash_table_foreach (component->priv->store_hash, (GHFunc)mc_stores_foreach, &data);
1362 void
1363 mail_component_remove_folder (MailComponent *component, CamelStore *store, const char *path)
1365 MAIL_COMPONENT_DEFAULT(component);
1367 /* FIXME: implement me. but first, am I really even needed? */
1370 EMFolderTreeModel *
1371 mail_component_peek_tree_model (MailComponent *component)
1373 MAIL_COMPONENT_DEFAULT(component);
1375 return component->priv->model;
1378 CamelStore *
1379 mail_component_peek_local_store (MailComponent *mc)
1381 MAIL_COMPONENT_DEFAULT (mc);
1382 mc_setup_local_store (mc);
1384 return mc->priv->local_store;
1388 * mail_component_get_folder:
1389 * @mc:
1390 * @id:
1392 * Get a standard/default folder by id. This call is thread-safe.
1394 * Return value:
1396 struct _CamelFolder *
1397 mail_component_get_folder(MailComponent *mc, enum _mail_component_folder_t id)
1399 g_assert(id <= MAIL_COMPONENT_FOLDER_LOCAL_INBOX);
1401 MAIL_COMPONENT_DEFAULT(mc);
1402 mc_setup_local_store(mc);
1404 return mc_default_folders[id].folder;
1408 * mail_component_get_folder_uri:
1409 * @mc:
1410 * @id:
1412 * Get a standard/default folder's uri. This call is thread-safe.
1414 * Return value:
1416 const char *
1417 mail_component_get_folder_uri(MailComponent *mc, enum _mail_component_folder_t id)
1419 g_assert(id <= MAIL_COMPONENT_FOLDER_LOCAL_INBOX);
1421 MAIL_COMPONENT_DEFAULT(mc);
1422 mc_setup_local_store(mc);
1424 return mc_default_folders[id].uri;
1427 BONOBO_TYPE_FUNC_FULL (MailComponent, GNOME_Evolution_MailComponent, PARENT_TYPE, mail_component)