/* * 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. * * 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 General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this 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 "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 "mail-send-recv.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; gboolean check_junk; GSList *address_cache; /* data is AddressCacheData struct */ GMutex address_cache_mutex; }; enum { PROP_0, PROP_ACCOUNT_STORE, PROP_CHECK_JUNK, 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; }; typedef struct _AddressCacheData { gchar *email_address; gint64 stamp; /* when it was added to cache, in microseconds */ gboolean is_known; } AddressCacheData; static void address_cache_data_free (gpointer pdata) { AddressCacheData *data = pdata; if (data) { g_free (data->email_address); g_free (data); } } static GSList * address_cache_data_remove_old_and_test (GSList *items, const gchar *email_address, gboolean *found, gboolean *is_known) { gint64 old_when; GSList *iter, *prev = NULL; if (!items) return NULL; /* let the cache value live for 5 minutes */ old_when = g_get_real_time () - 5 * 60 * 1000 * 1000; for (iter = items; iter; prev = iter, iter = iter->next) { AddressCacheData *data = iter->data; if (!data || data->stamp <= old_when || !data->email_address) break; if (g_ascii_strcasecmp (email_address, data->email_address) == 0) { *found = TRUE; *is_known = data->is_known; /* a match was found, shorten the list later */ return items; } } g_slist_free_full (iter, address_cache_data_free); if (prev) prev->next = NULL; else items = NULL; return items; } /* 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; gboolean add_junk_test; 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); add_junk_test = priv->check_junk && ( g_str_equal (type, E_FILTER_SOURCE_INCOMING) || g_str_equal (type, E_FILTER_SOURCE_JUNKTEST)); if (add_junk_test) { /* 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_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_CHECK_JUNK: e_mail_ui_session_set_check_junk ( E_MAIL_UI_SESSION (object), g_value_get_boolean (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } 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_CHECK_JUNK: g_value_set_boolean ( value, e_mail_ui_session_get_check_junk ( 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; } g_mutex_lock (&priv->address_cache_mutex); g_slist_free_full (priv->address_cache, address_cache_data_free); priv->address_cache = NULL; g_mutex_unlock (&priv->address_cache_mutex); /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_mail_ui_session_parent_class)->dispose (object); } static void mail_ui_session_finalize (GObject *object) { EMailUISessionPrivate *priv; priv = E_MAIL_UI_SESSION_GET_PRIVATE (object); g_mutex_clear (&priv->address_cache_mutex); /* Chain up to parent's method. */ G_OBJECT_CLASS (e_mail_ui_session_parent_class)->finalize (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); } CamelCertTrust e_mail_ui_session_trust_prompt (CamelSession *session, CamelService *service, GTlsCertificate *certificate, GTlsCertificateFlags errors) { g_type_ensure (E_TYPE_MAIL_UI_SESSION); return CAMEL_SESSION_CLASS (e_mail_ui_session_parent_class)-> trust_prompt (session, service, certificate, errors); } 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 gboolean mail_ui_session_lookup_addressbook (CamelSession *session, const gchar *name) { CamelInternetAddress *cia; gboolean known_address = FALSE; /* FIXME CamelSession's lookup_addressbook() method needs redone. * No GCancellable provided, no means of reporting an error. */ if (!mail_config_get_lookup_book ()) return FALSE; cia = camel_internet_address_new (); if (camel_address_decode (CAMEL_ADDRESS (cia), name) > 0) { GError *error = NULL; e_mail_ui_session_check_known_address_sync ( E_MAIL_UI_SESSION (session), cia, mail_config_get_lookup_book_local_only (), NULL, &known_address, &error); if (error != NULL) { g_warning ("%s: %s", G_STRFUNC, error->message); g_error_free (error); } } else { g_warning ( "%s: Failed to decode internet " "address '%s'", G_STRFUNC, name); } g_object_unref (cia); return known_address; } static void mail_ui_session_user_alert (CamelSession *session, CamelService *service, CamelSessionAlertType type, const gchar *alert_message) { EAlert *alert; EShell *shell; const gchar *alert_tag; gchar *display_name; shell = e_shell_get_default (); switch (type) { case CAMEL_SESSION_ALERT_INFO: alert_tag = "mail:user-alert-info"; break; case CAMEL_SESSION_ALERT_WARNING: alert_tag = "mail:user-alert-warning"; break; case CAMEL_SESSION_ALERT_ERROR: alert_tag = "mail:user-alert-error"; break; default: g_return_if_reached (); } display_name = camel_service_dup_display_name (service); /* Just submit the alert to the EShell rather than hunting for * a suitable window. This will post it to all shell windows in * all views, but if it's coming from the server then it must be * important... right? */ alert = e_alert_new (alert_tag, display_name, alert_message, NULL); e_shell_submit_alert (shell, alert); g_object_unref (alert); g_free (display_name); } 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->set_property = mail_ui_session_set_property; object_class->get_property = mail_ui_session_get_property; object_class->dispose = mail_ui_session_dispose; object_class->finalize = mail_ui_session_finalize; 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->get_filter_driver = mail_ui_session_get_filter_driver; session_class->lookup_addressbook = mail_ui_session_lookup_addressbook; session_class->user_alert = mail_ui_session_user_alert; 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_CHECK_JUNK, g_param_spec_boolean ( "check-junk", "Check Junk", "Check if incoming messages are junk", TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); 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); g_mutex_init (&session->priv->address_cache_mutex); 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; } /** * e_mail_ui_session_get_check_junk: * @session: an #EMailUISession * * Returns whether to automatically check incoming messages for junk content. * * Returns: whether to check for junk messages **/ gboolean e_mail_ui_session_get_check_junk (EMailUISession *session) { g_return_val_if_fail (E_IS_MAIL_UI_SESSION (session), FALSE); return session->priv->check_junk; } /** * e_mail_ui_session_set_check_junk: * @session: an #EMailUISession * @check_junk: whether to check for junk messages * * Sets whether to automatically check incoming messages for junk content. **/ void e_mail_ui_session_set_check_junk (EMailUISession *session, gboolean check_junk) { g_return_if_fail (E_IS_MAIL_UI_SESSION (session)); if (check_junk == session->priv->check_junk) return; session->priv->check_junk = check_junk; g_object_notify (G_OBJECT (session), "check-junk"); } 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); } /* let's test in local books first */ static gint sort_local_books_first_cb (gconstpointer a, gconstpointer b) { ESource *asource = (ESource *) a; ESource *bsource = (ESource *) b; ESourceBackend *abackend, *bbackend; abackend = e_source_get_extension (asource, E_SOURCE_EXTENSION_ADDRESS_BOOK); bbackend = e_source_get_extension (bsource, E_SOURCE_EXTENSION_ADDRESS_BOOK); if (g_strcmp0 (e_source_backend_get_backend_name (abackend), "local") == 0) { if (g_strcmp0 (e_source_backend_get_backend_name (bbackend), "local") == 0) return 0; return -1; } if (g_strcmp0 (e_source_backend_get_backend_name (bbackend), "local") == 0) return 1; return g_strcmp0 (e_source_backend_get_backend_name (abackend), e_source_backend_get_backend_name (bbackend)); } /** * 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 = FALSE; 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); g_mutex_lock (&session->priv->address_cache_mutex); session->priv->address_cache = address_cache_data_remove_old_and_test ( session->priv->address_cache, email_address, &success, &known_address); if (success) { g_mutex_unlock (&session->priv->address_cache_mutex); if (out_known_address) *out_known_address = known_address; return success; } /* 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_enabled ( registry, E_SOURCE_EXTENSION_ADDRESS_BOOK); list = g_list_sort (list, sort_local_books_first_cb); } for (link = list; link != NULL && !g_cancellable_is_cancelled (cancellable); link = g_list_next (link)) { ESource *source = E_SOURCE (link->data); EClient *client; GSList *uids = NULL; GError *local_error = 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, &local_error); if (client == NULL) { /* ignore E_CLIENT_ERROR-s, no need to stop searching if one of the books is temporarily unreachable or any such issue */ if (local_error && local_error->domain == E_CLIENT_ERROR) { g_clear_error (&local_error); continue; } if (local_error) g_propagate_error (error, local_error); 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; if (!g_cancellable_is_cancelled (cancellable)) { AddressCacheData *data = g_new0 (AddressCacheData, 1); data->email_address = g_strdup (email_address); data->stamp = g_get_real_time (); data->is_known = known_address; /* this makes the list sorted by time, from newest to oldest */ session->priv->address_cache = g_slist_prepend ( session->priv->address_cache, data); } g_mutex_unlock (&session->priv->address_cache_mutex); return success; }