/* * e-mail-ui-session.c * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the program; if not, see * * * Authors: * Jonathon Jongsma * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * Copyright (C) 2009 Intel Corporation * */ /* mail-session.c: handles the session information and resource manipulation */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #ifdef HAVE_CANBERRA #include #endif #include #include "e-mail-account-store.h" #include "e-util/e-util.h" #include "e-util/e-util-private.h" #include "shell/e-shell.h" #include "shell/e-shell-view.h" #include "shell/e-shell-content.h" #include "shell/e-shell-window.h" #include "libemail-engine/e-mail-folder-utils.h" #include "libemail-engine/e-mail-junk-filter.h" #include "libemail-engine/e-mail-session.h" #include "e-mail-ui-session.h" #include "em-composer-utils.h" #include "em-filter-context.h" #include "em-vfolder-editor-context.h" #include "em-filter-rule.h" #include "em-utils.h" #include "libemail-engine/mail-config.h" #include "libemail-engine/mail-mt.h" #include "libemail-engine/mail-ops.h" #include "mail-send-recv.h" #include "libemail-engine/mail-tools.h" #define E_MAIL_UI_SESSION_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_MAIL_UI_SESSION, EMailUISessionPrivate)) typedef struct _SourceContext SourceContext; struct _EMailUISessionPrivate { FILE *filter_logfile; ESourceRegistry *registry; EMailAccountStore *account_store; EMailLabelListStore *label_store; EPhotoCache *photo_cache; }; enum { PROP_0, PROP_ACCOUNT_STORE, PROP_LABEL_STORE, PROP_PHOTO_CACHE }; enum { ACTIVITY_ADDED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; G_DEFINE_TYPE_WITH_CODE ( EMailUISession, e_mail_ui_session, E_TYPE_MAIL_SESSION, G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL)) struct _SourceContext { EMailUISession *session; CamelService *service; }; /* Support for CamelSession.alert_user() *************************************/ static gpointer user_message_dialog; static GQueue user_message_queue = { NULL, NULL, 0 }; struct _user_message_msg { MailMsg base; CamelSessionAlertType type; gchar *prompt; GList *button_captions; EFlag *done; gint result; guint ismain : 1; }; static void user_message_exec (struct _user_message_msg *m, GCancellable *cancellable, GError **error); static void user_message_response_free (GtkDialog *dialog, gint button) { struct _user_message_msg *m = NULL; gtk_widget_destroy ((GtkWidget *) dialog); user_message_dialog = NULL; /* check for pendings */ if (!g_queue_is_empty (&user_message_queue)) { GCancellable *cancellable; m = g_queue_pop_head (&user_message_queue); cancellable = m->base.cancellable; user_message_exec (m, cancellable, &m->base.error); mail_msg_unref (m); } } /* clicked, send back the reply */ static void user_message_response (GtkDialog *dialog, gint button, struct _user_message_msg *m) { /* if !m or !button_captions, then we've already replied */ if (m && m->button_captions) { m->result = button; e_flag_set (m->done); } user_message_response_free (dialog, button); } static void user_message_exec (struct _user_message_msg *m, GCancellable *cancellable, GError **error) { gboolean info_only; GtkWindow *parent; EShell *shell; const gchar *error_type; gint index; GList *iter; info_only = g_list_length (m->button_captions) <= 1; if (!m->ismain && user_message_dialog != NULL && !info_only) { g_queue_push_tail (&user_message_queue, mail_msg_ref (m)); return; } switch (m->type) { case CAMEL_SESSION_ALERT_INFO: error_type = "system:simple-info"; break; case CAMEL_SESSION_ALERT_WARNING: error_type = "system:simple-warning"; break; case CAMEL_SESSION_ALERT_ERROR: error_type = "system:simple-error"; break; default: error_type = NULL; g_return_if_reached (); } shell = e_shell_get_default (); /* try to find "mail" view to place the informational alert to */ if (info_only) { GtkWindow *active_window; EShellWindow *shell_window; EShellView *shell_view; EShellContent *shell_content = NULL; /* check currently active window first, ... */ active_window = e_shell_get_active_window (shell); if (active_window && E_IS_SHELL_WINDOW (active_window)) { if (E_IS_SHELL_WINDOW (active_window)) { shell_window = E_SHELL_WINDOW (active_window); shell_view = e_shell_window_peek_shell_view (shell_window, "mail"); if (shell_view) shell_content = e_shell_view_get_shell_content (shell_view); } } if (!shell_content) { GList *list, *iter; list = gtk_application_get_windows (GTK_APPLICATION (shell)); /* ...then iterate through all opened * windows and pick one which has it */ for (iter = list; iter != NULL && !shell_content; iter = g_list_next (iter)) { if (E_IS_SHELL_WINDOW (iter->data)) { shell_window = iter->data; shell_view = e_shell_window_peek_shell_view (shell_window, "mail"); if (shell_view) shell_content = e_shell_view_get_shell_content (shell_view); } } } /* When no shell-content found, which might not happen, * but just in case, process the information alert like * usual, through an EAlertDialog machinery. */ if (shell_content) { e_alert_submit ( E_ALERT_SINK (shell_content), error_type, m->prompt, NULL); return; } else if (!m->ismain && user_message_dialog != NULL) { g_queue_push_tail (&user_message_queue, mail_msg_ref (m)); return; } } /* Pull in the active window from the shell to get a parent window */ parent = e_shell_get_active_window (shell); user_message_dialog = e_alert_dialog_new_for_args ( parent, error_type, m->prompt, NULL); g_object_set (user_message_dialog, "resizable", TRUE, NULL); if (m->button_captions) { GtkWidget *action_area; GList *children, *child; /* remove all default buttons and keep only those requested */ action_area = gtk_dialog_get_action_area (GTK_DIALOG (user_message_dialog)); children = gtk_container_get_children (GTK_CONTAINER (action_area)); for (child = children; child != NULL; child = child->next) { gtk_container_remove (GTK_CONTAINER (action_area), child->data); } g_list_free (children); } for (index = 0, iter = m->button_captions; iter; index++, iter = iter->next) { gtk_dialog_add_button (GTK_DIALOG (user_message_dialog), iter->data, index); } /* XXX This is a case where we need to be able to construct * custom EAlerts without a predefined XML definition. */ if (m->ismain) { gint response; response = gtk_dialog_run (user_message_dialog); user_message_response ( user_message_dialog, response, m); } else { gpointer user_data = m; if (g_list_length (m->button_captions) <= 1) user_data = NULL; g_signal_connect ( user_message_dialog, "response", G_CALLBACK (user_message_response), user_data); gtk_widget_show (user_message_dialog); } } static void user_message_free (struct _user_message_msg *m) { g_free (m->prompt); g_list_free_full (m->button_captions, g_free); e_flag_free (m->done); } static MailMsgInfo user_message_info = { sizeof (struct _user_message_msg), (MailMsgDescFunc) NULL, (MailMsgExecFunc) user_message_exec, (MailMsgDoneFunc) NULL, (MailMsgFreeFunc) user_message_free }; /* Support for CamelSession.get_filter_driver () *****************************/ static CamelFolder * get_folder (CamelFilterDriver *d, const gchar *uri, gpointer user_data, GError **error) { EMailSession *session = E_MAIL_SESSION (user_data); /* FIXME Not passing a GCancellable here. */ /* FIXME Need a camel_filter_driver_get_session(). */ return e_mail_session_uri_to_folder_sync ( session, uri, 0, NULL, error); } static gboolean session_play_sound_cb (const gchar *filename) { #ifdef HAVE_CANBERRA if (filename != NULL && *filename != '\0') ca_context_play ( ca_gtk_context_get (), 0, CA_PROP_MEDIA_FILENAME, filename, NULL); else #endif gdk_beep (); return FALSE; } static void session_play_sound (CamelFilterDriver *driver, const gchar *filename, gpointer user_data) { g_idle_add_full ( G_PRIORITY_DEFAULT_IDLE, (GSourceFunc) session_play_sound_cb, g_strdup (filename), (GDestroyNotify) g_free); } static void session_system_beep (CamelFilterDriver *driver, gpointer user_data) { g_idle_add ((GSourceFunc) session_play_sound_cb, NULL); } static CamelFilterDriver * main_get_filter_driver (CamelSession *session, const gchar *type, GError **error) { EMailSession *ms = E_MAIL_SESSION (session); CamelFilterDriver *driver; EFilterRule *rule = NULL; const gchar *config_dir; gchar *user, *system; GSettings *settings; ERuleContext *fc; EMailUISessionPrivate *priv; priv = E_MAIL_UI_SESSION_GET_PRIVATE (session); settings = g_settings_new ("org.gnome.evolution.mail"); config_dir = mail_session_get_config_dir (); user = g_build_filename (config_dir, "filters.xml", NULL); system = g_build_filename (EVOLUTION_PRIVDATADIR, "filtertypes.xml", NULL); fc = (ERuleContext *) em_filter_context_new (ms); e_rule_context_load (fc, system, user); g_free (system); g_free (user); driver = camel_filter_driver_new (session); camel_filter_driver_set_folder_func (driver, get_folder, session); if (g_settings_get_boolean (settings, "filters-log-actions")) { if (priv->filter_logfile == NULL) { gchar *filename; filename = g_settings_get_string (settings, "filters-log-file"); if (filename) { priv->filter_logfile = g_fopen (filename, "a+"); g_free (filename); } } if (priv->filter_logfile) camel_filter_driver_set_logfile (driver, priv->filter_logfile); } camel_filter_driver_set_shell_func (driver, mail_execute_shell_command, NULL); camel_filter_driver_set_play_sound_func (driver, session_play_sound, NULL); camel_filter_driver_set_system_beep_func (driver, session_system_beep, NULL); if ((!strcmp (type, E_FILTER_SOURCE_INCOMING) || !strcmp (type, E_FILTER_SOURCE_JUNKTEST)) && camel_session_get_check_junk (session)) { /* implicit junk check as 1st rule */ camel_filter_driver_add_rule ( driver, "Junk check", "(junk-test)", "(begin (set-system-flag \"junk\"))"); } if (strcmp (type, E_FILTER_SOURCE_JUNKTEST) != 0) { GString *fsearch, *faction; fsearch = g_string_new (""); faction = g_string_new (""); if (!strcmp (type, E_FILTER_SOURCE_DEMAND)) type = E_FILTER_SOURCE_INCOMING; /* add the user-defined rules next */ while ((rule = e_rule_context_next_rule (fc, rule, type))) { g_string_truncate (fsearch, 0); g_string_truncate (faction, 0); /* skip disabled rules */ if (!rule->enabled) continue; e_filter_rule_build_code (rule, fsearch); em_filter_rule_build_action ( EM_FILTER_RULE (rule), faction); camel_filter_driver_add_rule ( driver, rule->name, fsearch->str, faction->str); } g_string_free (fsearch, TRUE); g_string_free (faction, TRUE); } g_object_unref (fc); g_object_unref (settings); return driver; } static void source_context_free (SourceContext *context) { if (context->session != NULL) g_object_unref (context->session); if (context->service != NULL) g_object_unref (context->service); g_slice_free (SourceContext, context); } static gboolean mail_ui_session_add_service_cb (SourceContext *context) { EMailAccountStore *store; /* The CamelService should be fully initialized by now. */ store = e_mail_ui_session_get_account_store (context->session); e_mail_account_store_add_service (store, context->service); return FALSE; } static void mail_ui_session_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_ACCOUNT_STORE: g_value_set_object ( value, e_mail_ui_session_get_account_store ( E_MAIL_UI_SESSION (object))); return; case PROP_LABEL_STORE: g_value_set_object ( value, e_mail_ui_session_get_label_store ( E_MAIL_UI_SESSION (object))); return; case PROP_PHOTO_CACHE: g_value_set_object ( value, e_mail_ui_session_get_photo_cache ( E_MAIL_UI_SESSION (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void mail_ui_session_dispose (GObject *object) { EMailUISessionPrivate *priv; priv = E_MAIL_UI_SESSION_GET_PRIVATE (object); if (priv->registry != NULL) { g_object_unref (priv->registry); priv->registry = NULL; } if (priv->account_store != NULL) { e_mail_account_store_clear (priv->account_store); g_object_unref (priv->account_store); priv->account_store = NULL; } if (priv->label_store != NULL) { g_object_unref (priv->label_store); priv->label_store = NULL; } if (priv->photo_cache != NULL) { g_object_unref (priv->photo_cache); priv->photo_cache = NULL; } /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_mail_ui_session_parent_class)->dispose (object); } static void mail_ui_session_constructed (GObject *object) { EMailUISessionPrivate *priv; EMFolderTreeModel *folder_tree_model; ESourceRegistry *registry; EClientCache *client_cache; EMailSession *session; EShell *shell; session = E_MAIL_SESSION (object); shell = e_shell_get_default (); /* synchronize online state first, before any CamelService is created */ g_object_bind_property ( shell, "online", session, "online", G_BINDING_SYNC_CREATE); priv = E_MAIL_UI_SESSION_GET_PRIVATE (object); priv->account_store = e_mail_account_store_new (session); /* Keep our own reference to the ESourceRegistry so we * can easily disconnect signal handlers in dispose(). */ registry = e_mail_session_get_registry (session); priv->registry = g_object_ref (registry); client_cache = e_shell_get_client_cache (shell); priv->photo_cache = e_photo_cache_new (client_cache); /* XXX Make sure the folder tree model is created before we * add built-in CamelStores so it gets signals from the * EMailAccountStore. * * XXX This is creating a circular reference. Perhaps the * model should only hold a weak pointer to EMailSession? * * FIXME EMailSession should just own the default instance. */ folder_tree_model = em_folder_tree_model_get_default (); em_folder_tree_model_set_session (folder_tree_model, session); /* Chain up to parent's constructed() method. */ G_OBJECT_CLASS (e_mail_ui_session_parent_class)->constructed (object); } static CamelService * mail_ui_session_add_service (CamelSession *session, const gchar *uid, const gchar *protocol, CamelProviderType type, GError **error) { CamelService *service; /* Chain up to parent's constructed() method. */ service = CAMEL_SESSION_CLASS (e_mail_ui_session_parent_class)-> add_service (session, uid, protocol, type, error); /* Inform the EMailAccountStore of the new CamelService * from an idle callback so the service has a chance to * fully initialize first. */ if (CAMEL_IS_STORE (service)) { SourceContext *context; context = g_slice_new0 (SourceContext); context->session = g_object_ref (session); context->service = g_object_ref (service); /* Prioritize ahead of GTK+ redraws. */ g_idle_add_full ( G_PRIORITY_HIGH_IDLE, (GSourceFunc) mail_ui_session_add_service_cb, context, (GDestroyNotify) source_context_free); } return service; } static void mail_ui_session_remove_service (CamelSession *session, CamelService *service) { EMailAccountStore *store; EMailUISession *ui_session; /* Passing a NULL parent window skips confirmation prompts. */ ui_session = E_MAIL_UI_SESSION (session); store = e_mail_ui_session_get_account_store (ui_session); e_mail_account_store_remove_service (store, NULL, service); } gint e_mail_ui_session_alert_user (CamelSession *session, CamelSessionAlertType type, const gchar *prompt, GList *button_captions, GCancellable *cancellable) { struct _user_message_msg *m; gint result = -1; m = mail_msg_new (&user_message_info); m->ismain = mail_in_main_thread (); m->type = type; m->prompt = g_strdup (prompt); m->done = e_flag_new (); m->button_captions = g_list_copy_deep ( button_captions, (GCopyFunc) g_strdup, NULL); if (g_list_length (button_captions) > 1) mail_msg_ref (m); if (!cancellable) cancellable = m->base.cancellable; if (m->ismain) user_message_exec (m, cancellable, &m->base.error); else mail_msg_main_loop_push (m); if (g_list_length (button_captions) > 1) { e_flag_wait (m->done); result = m->result; mail_msg_unref (m); } else if (m->ismain) mail_msg_unref (m); return result; } CamelCertTrust e_mail_ui_session_trust_prompt (CamelSession *session, const gchar *host, const gchar *certificate, guint32 certificate_errors, GList *issuers, GCancellable *cancellable) { g_type_ensure (E_TYPE_MAIL_UI_SESSION); return CAMEL_SESSION_CLASS (e_mail_ui_session_parent_class)->trust_prompt ( session, host, certificate, certificate_errors, issuers, cancellable); } static CamelFilterDriver * mail_ui_session_get_filter_driver (CamelSession *session, const gchar *type, GError **error) { return (CamelFilterDriver *) mail_call_main ( MAIL_CALL_p_ppp, (MailMainFunc) main_get_filter_driver, session, type, error); } static void mail_ui_session_refresh_service (EMailSession *session, CamelService *service) { if (camel_session_get_online (CAMEL_SESSION (session))) mail_receive_service (service); } static EMVFolderContext * mail_ui_session_create_vfolder_context (EMailSession *session) { return (EMVFolderContext *) em_vfolder_editor_context_new (session); } static void e_mail_ui_session_class_init (EMailUISessionClass *class) { GObjectClass *object_class; CamelSessionClass *session_class; EMailSessionClass *mail_session_class; g_type_class_add_private (class, sizeof (EMailUISessionPrivate)); object_class = G_OBJECT_CLASS (class); object_class->get_property = mail_ui_session_get_property; object_class->dispose = mail_ui_session_dispose; object_class->constructed = mail_ui_session_constructed; session_class = CAMEL_SESSION_CLASS (class); session_class->add_service = mail_ui_session_add_service; session_class->remove_service = mail_ui_session_remove_service; session_class->alert_user = e_mail_ui_session_alert_user; session_class->get_filter_driver = mail_ui_session_get_filter_driver; mail_session_class = E_MAIL_SESSION_CLASS (class); mail_session_class->create_vfolder_context = mail_ui_session_create_vfolder_context; mail_session_class->refresh_service = mail_ui_session_refresh_service; g_object_class_install_property ( object_class, PROP_LABEL_STORE, g_param_spec_object ( "label-store", "Label Store", "Mail label store", E_TYPE_MAIL_LABEL_LIST_STORE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_PHOTO_CACHE, g_param_spec_object ( "photo-cache", "Photo Cache", "Contact photo cache", E_TYPE_PHOTO_CACHE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); signals[ACTIVITY_ADDED] = g_signal_new ( "activity-added", G_OBJECT_CLASS_TYPE (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EMailUISessionClass, activity_added), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, E_TYPE_ACTIVITY); } static void e_mail_ui_session_init (EMailUISession *session) { session->priv = E_MAIL_UI_SESSION_GET_PRIVATE (session); session->priv->label_store = e_mail_label_list_store_new (); } EMailSession * e_mail_ui_session_new (ESourceRegistry *registry) { const gchar *user_data_dir; const gchar *user_cache_dir; g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); user_data_dir = mail_session_get_data_dir (); user_cache_dir = mail_session_get_cache_dir (); return g_object_new ( E_TYPE_MAIL_UI_SESSION, "registry", registry, "user-data-dir", user_data_dir, "user-cache-dir", user_cache_dir, NULL); } EMailAccountStore * e_mail_ui_session_get_account_store (EMailUISession *session) { g_return_val_if_fail (E_IS_MAIL_UI_SESSION (session), NULL); return session->priv->account_store; } EMailLabelListStore * e_mail_ui_session_get_label_store (EMailUISession *session) { g_return_val_if_fail (E_IS_MAIL_UI_SESSION (session), NULL); return session->priv->label_store; } EPhotoCache * e_mail_ui_session_get_photo_cache (EMailUISession *session) { g_return_val_if_fail (E_IS_MAIL_UI_SESSION (session), NULL); return session->priv->photo_cache; } void e_mail_ui_session_add_activity (EMailUISession *session, EActivity *activity) { g_return_if_fail (E_IS_MAIL_UI_SESSION (session)); g_return_if_fail (E_IS_ACTIVITY (activity)); g_signal_emit (session, signals[ACTIVITY_ADDED], 0, activity); } /** * e_mail_ui_session_check_known_address_sync: * @session: an #EMailUISession * @addr: a #CamelInternetAddress * @check_local_only: only check the builtin address book * @cancellable: optional #GCancellable object, or %NULL * @out_known_address: return location for the determination of * whether @addr is a known address * @error: return location for a #GError, or %NULL * * Determines whether @addr is a known email address by querying address * books for contacts with a matching email address. If @check_local_only * is %TRUE then only the builtin address book is checked, otherwise all * enabled address books are checked. * * The result of the query is returned through the @out_known_address * boolean pointer, not through the return value. The return value only * indicates whether the address book queries were completed successfully. * If an error occurred, the function sets @error and returns %FALSE. * * Returns: whether address books were successfully queried **/ gboolean e_mail_ui_session_check_known_address_sync (EMailUISession *session, CamelInternetAddress *addr, gboolean check_local_only, GCancellable *cancellable, gboolean *out_known_address, GError **error) { EPhotoCache *photo_cache; EClientCache *client_cache; ESourceRegistry *registry; EBookQuery *book_query; GList *list, *link; const gchar *email_address = NULL; gchar *book_query_string; gboolean known_address = FALSE; gboolean success = TRUE; g_return_val_if_fail (E_IS_MAIL_UI_SESSION (session), FALSE); g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (addr), FALSE); camel_internet_address_get (addr, 0, NULL, &email_address); g_return_val_if_fail (email_address != NULL, FALSE); /* XXX EPhotoCache holds a reference on EClientCache, which * we need. EMailUISession should probably hold its own * EClientCache reference, but this will do for now. */ photo_cache = e_mail_ui_session_get_photo_cache (session); client_cache = e_photo_cache_ref_client_cache (photo_cache); registry = e_client_cache_ref_registry (client_cache); book_query = e_book_query_field_test ( E_CONTACT_EMAIL, E_BOOK_QUERY_IS, email_address); book_query_string = e_book_query_to_string (book_query); e_book_query_unref (book_query); if (check_local_only) { ESource *source; source = e_source_registry_ref_builtin_address_book (registry); list = g_list_prepend (NULL, g_object_ref (source)); g_object_unref (source); } else { list = e_source_registry_list_sources ( registry, E_SOURCE_EXTENSION_ADDRESS_BOOK); } for (link = list; link != NULL; link = g_list_next (link)) { ESource *source = E_SOURCE (link->data); EClient *client; GSList *uids = NULL; /* Skip disabled sources. */ if (!e_source_get_enabled (source)) continue; client = e_client_cache_get_client_sync ( client_cache, source, E_SOURCE_EXTENSION_ADDRESS_BOOK, cancellable, error); if (client == NULL) { success = FALSE; break; } success = e_book_client_get_contacts_uids_sync ( E_BOOK_CLIENT (client), book_query_string, &uids, cancellable, error); g_object_unref (client); if (!success) { g_warn_if_fail (uids == NULL); break; } if (uids != NULL) { g_slist_free_full (uids, (GDestroyNotify) g_free); known_address = TRUE; break; } } g_list_free_full (list, (GDestroyNotify) g_object_unref); g_free (book_query_string); g_object_unref (registry); g_object_unref (client_cache); if (success && out_known_address != NULL) *out_known_address = known_address; return success; }