Rewrite e_load_book_source_async().
authorMatthew Barnes <mbarnes@redhat.com>
Wed, 18 Aug 2010 11:08:06 +0000 (07:08 -0400)
committerMatthew Barnes <mbarnes@redhat.com>
Wed, 18 Aug 2010 20:45:09 +0000 (16:45 -0400)
This is a late libedataserverui API break, but so be it.

Rewrite e_load_book_source_async() to use a GIO-style asynchronous
pattern, and add a corresponding e_load_book_source_finish().

The API is fairly obvious:

   void     e_load_book_source_async   (ESource *source,
                                        GtkWindow *parent,
                                        GCancellable *cancellable,
                                        GAsyncReadyCallback callback,
                                        gpointer user_data);

   EBook *  e_load_book_source_finish  (ESource *source,
                                        GAsyncResult *result,
                                        GError **error);

This also eliminates the thread for loading EBooks from ENameSelector.

configure.ac
docs/reference/libedataserverui/libedataserverui-sections.txt
docs/reference/libedataserverui/tmpl/e-book-auth-util.sgml
libedataserverui/e-book-auth-util.c
libedataserverui/e-book-auth-util.h
libedataserverui/e-name-selector-dialog.c
libedataserverui/e-name-selector-entry.c
libedataserverui/e-name-selector.c

index 180468e..0961866 100644 (file)
@@ -85,7 +85,7 @@ LIBEDATASERVER_CURRENT=14
 LIBEDATASERVER_REVISION=0
 LIBEDATASERVER_AGE=0
 
-LIBEDATASERVERUI_CURRENT=10
+LIBEDATASERVERUI_CURRENT=11
 LIBEDATASERVERUI_REVISION=0
 LIBEDATASERVERUI_AGE=0
 
index 6e918cf..f089ce3 100644 (file)
@@ -2,6 +2,7 @@
 <FILE>e-book-auth-util</FILE>
 e_load_book_source
 e_load_book_source_async
+e_load_book_source_finish
 </SECTION>
 
 <SECTION>
index 5eda8ea..38dce82 100644 (file)
@@ -37,8 +37,20 @@ e-book-auth-util
 </para>
 
 @source: 
-@open_func: 
+@parent: 
+@cancellable: 
+@callback: 
 @user_data: 
+
+
+<!-- ##### FUNCTION e_load_book_source_finish ##### -->
+<para>
+
+</para>
+
+@source: 
+@result: 
+@error: 
 @Returns: 
 
 
index db794a4..25b380b 100644 (file)
@@ -318,41 +318,358 @@ e_load_book_source (ESource *source, EBookCallback open_func, gpointer user_data
 }
 #endif
 
+typedef struct {
+       EBook *book;
+       GtkWindow *parent;
+       GCancellable *cancellable;
+
+       gboolean anonymous_alert;
+
+       /* Authentication Details */
+       gchar *auth_uri;
+       gchar *auth_method;
+       gchar *auth_username;
+       gchar *auth_component;
+       gboolean auth_remember;
+} LoadContext;
+
+static void
+load_book_source_context_free (LoadContext *context)
+{
+       if (context->book != NULL)
+               g_object_unref (context->book);
+
+       if (context->parent != NULL)
+               g_object_unref (context->parent);
+
+       if (context->cancellable != NULL)
+               g_object_unref (context->cancellable);
+
+       g_free (context->auth_uri);
+       g_free (context->auth_method);
+       g_free (context->auth_username);
+       g_free (context->auth_component);
+
+       g_slice_free (LoadContext, context);
+}
+
+static void
+load_book_source_get_auth_details (ESource *source,
+                                   LoadContext *context)
+{
+       const gchar *property;
+       gchar *uri;
+
+       /* auth_method */
+
+       property = e_source_get_property (source, "auth");
+
+       if (property == NULL || strcmp (property, "none") == 0)
+               return;
+
+       context->auth_method = g_strdup (property);
+
+       /* auth_uri */
+
+       uri = e_source_get_uri (source);
+       context->auth_uri = remove_parameters_from_uri (uri);
+       g_free (uri);
+
+       /* auth_username */
+
+       if (g_strcmp0 (context->auth_method, "ldap/simple-binddn") == 0) {
+               property = e_source_get_property (source, "binddn");
+
+       } else if (g_strcmp0 (context->auth_method, "plain/password") == 0) {
+               property = e_source_get_property (source, "user");
+               if (property == NULL)
+                       property = e_source_get_property (source, "username");
+
+       } else
+               property = e_source_get_property (source, "email_addr");
+
+       if (property == NULL)
+               property = "";
+
+       context->auth_username = g_strdup (property);
+
+       /* auth_component */
+
+       property = e_source_get_property (source, "auth-domain");
+
+       if (property == NULL)
+               property = "Addressbook";
+
+       context->auth_component = g_strdup (property);
+
+       /* auth_remember */
+
+       property = e_source_get_property (source, "remember_password");
+
+       context->auth_remember = (g_strcmp0 (property, "true") == 0);
+}
+
+static gchar *
+load_book_source_password_prompt (EBook *book,
+                                  LoadContext *context,
+                                  gboolean reprompt)
+{
+       ESource *source;
+       GString *string;
+       const gchar *title;
+       gchar *password;
+       guint32 flags;
+
+       source = e_book_get_source (book);
+       string = g_string_sized_new (256);
+
+       flags = E_PASSWORDS_REMEMBER_FOREVER |
+               E_PASSWORDS_SECRET | E_PASSWORDS_ONLINE;
+
+       if (reprompt) {
+               g_string_assign (string, _("Failed to authenticate.\n"));
+               flags |= E_PASSWORDS_REPROMPT;
+       }
+
+       g_string_append_printf (
+               string, _("Enter password for %s (user %s)"),
+               e_source_peek_name (source), context->auth_username);
+
+       /* XXX Dialog windows should not have titles. */
+       title = "";
+
+       password = e_passwords_ask_password (
+               title, context->auth_component,
+               context->auth_uri, string->str, flags,
+               &context->auth_remember, context->parent);
+
+       g_string_free (string, TRUE);
+
+       return password;
+}
+
+static void
+load_book_source_thread (GSimpleAsyncResult *simple,
+                         ESource *source,
+                         GCancellable *cancellable)
+{
+       EBook *book;
+       LoadContext *context;
+       gchar *password;
+       gboolean reprompt = FALSE;
+       GError *error = NULL;
+
+       context = g_simple_async_result_get_op_res_gpointer (simple);
+
+       book = e_book_new (source, &error);
+       if (book == NULL) {
+               g_simple_async_result_set_from_error (simple, error);
+               g_error_free (error);
+               return;
+       }
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, &error)) {
+               g_simple_async_result_set_from_error (simple, error);
+               g_object_unref (book);
+               g_error_free (error);
+               return;
+       }
+
+       if (!e_book_open (book, FALSE, &error)) {
+               g_simple_async_result_set_from_error (simple, error);
+               g_object_unref (book);
+               g_error_free (error);
+               return;
+       }
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, &error)) {
+               g_simple_async_result_set_from_error (simple, error);
+               g_object_unref (book);
+               g_error_free (error);
+               return;
+       }
+
+       /* Do we need to authenticate? */
+       if (context->auth_method == NULL)
+               goto exit;
+
+       password = e_passwords_get_password (
+               context->auth_component, context->auth_uri);
+
+prompt:
+       if (g_cancellable_set_error_if_cancelled (cancellable, &error)) {
+               g_simple_async_result_set_from_error (simple, error);
+               g_object_unref (book);
+               g_error_free (error);
+               g_free (password);
+               return;
+       }
+
+       if (password == NULL) {
+               password = load_book_source_password_prompt (
+                       book, context, reprompt);
+               reprompt = TRUE;
+       }
+
+       /* If we have a password, attempt to authenticate with it. */
+       if (password != NULL) {
+               e_book_authenticate_user (
+                       book, context->auth_username, password,
+                       context->auth_method, &error);
+
+               g_free (password);
+               password = NULL;
+
+       /* The user did not wish to provide a password.  If the address
+        * book can be accessed anonymously, do that but warn about it. */
+       } else if (e_book_check_static_capability (book, "anon-access")) {
+               context->anonymous_alert = TRUE;
+               goto exit;
+
+       /* Final fallback is to fail. */
+       } else {
+               g_cancellable_cancel (cancellable);
+               goto prompt;
+       }
+
+       /* If authentication failed, forget the password and reprompt. */
+       if (g_error_matches (
+               error, E_BOOK_ERROR, E_BOOK_ERROR_AUTHENTICATION_FAILED)) {
+               e_passwords_forget_password (
+                       context->auth_component, context->auth_uri);
+               g_clear_error (&error);
+               goto prompt;
+
+       } else if (error != NULL) {
+               g_simple_async_result_set_from_error (simple, error);
+               g_object_unref (book);
+               g_error_free (error);
+               return;
+       }
+
+exit:
+       context->book = book;
+}
+
 /**
  * e_load_book_source_async:
  * @source: an #ESource
- * @open_func_ex: a function to call when the operation finishes, or %NULL
- * @user_data: data to pass to callback function
+ * @parent: parent window for password dialogs, or %NULL
+ * @cancellable: optional #GCancellable object, %NULL to ignore
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: the data to pass to @callback
  *
- * Creates a new #EBook specified by @source, and starts a non-blocking
- * open operation on it. If the book requires authorization, presents
- * a window asking the user for such.
+ * Creates a new #EBook specified by @source and opens it, prompting the
+ * user for authentication if necessary.
  *
- * When the operation finishes, calls the callback function indicating
- * if it succeeded or not. If you don't care, you can pass %NULL for
- * @open_func_ex, and no action will be taken on completion.
+ * When the operation is finished, @callback will be called.  You can
+ * then call e_load_book_source_finish() to obtain the resulting #EBook.
  *
- * Returns: A new #EBook that is being opened.
+ * Since: 2.32
+ **/
+void
+e_load_book_source_async (ESource *source,
+                          GtkWindow *parent,
+                          GCancellable *cancellable,
+                          GAsyncReadyCallback callback,
+                          gpointer user_data)
+{
+       GSimpleAsyncResult *simple;
+       LoadContext *context;
+
+       g_return_if_fail (E_IS_SOURCE (source));
+
+       /* Source must have a group so we can obtain its URI. */
+       g_return_if_fail (e_source_peek_group (source) != NULL);
+
+       if (parent != NULL) {
+               g_return_if_fail (GTK_IS_WINDOW (parent));
+               g_object_ref (parent);
+       }
+
+       if (cancellable != NULL) {
+               g_return_if_fail (G_IS_CANCELLABLE (cancellable));
+               g_object_ref (cancellable);
+       }
+
+       context = g_slice_new0 (LoadContext);
+       context->parent = parent;
+       context->cancellable = cancellable;
+
+       /* Extract authentication details from the ESource before
+        * spawning the thread, since ESource is not thread-safe. */
+       load_book_source_get_auth_details (source, context);
+
+       simple = g_simple_async_result_new (
+               G_OBJECT (source), callback,
+               user_data, e_load_book_source_async);
+
+       g_simple_async_result_set_op_res_gpointer (
+               simple, context, (GDestroyNotify)
+               load_book_source_context_free);
+
+       g_simple_async_result_run_in_thread (
+               simple, (GSimpleAsyncThreadFunc) load_book_source_thread,
+               G_PRIORITY_DEFAULT, context->cancellable);
+
+       g_object_unref (simple);
+}
+
+/**
+ * e_load_book_source_finish:
+ * @source: an #ESource
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes an asynchronous #EBook open operation started with
+ * e_load_book_source_async().  If an error occurred, or the user
+ * declined to authenticate, the function will return %NULL and
+ * set @error.
+ *
+ * Returns: a ready-to-use #EBook, or %NULL or error
  *
  * Since: 2.32
  **/
 EBook *
-e_load_book_source_async (ESource *source, EBookAsyncCallback open_func_ex, gpointer user_data)
+e_load_book_source_finish (ESource *source,
+                           GAsyncResult *result,
+                           GError **error)
 {
-       EBook          *book;
-       LoadSourceData *load_source_data = g_new0 (LoadSourceData, 1);
+       GSimpleAsyncResult *simple;
+       LoadContext *context;
 
-       load_source_data->source = g_object_ref (source);
-       load_source_data->open_func_ex = open_func_ex;
-       load_source_data->open_func_data = user_data;
+       g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+       g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
 
-       book = e_book_new (source, NULL);
-       if (!book)
+       g_return_val_if_fail (
+               g_simple_async_result_is_valid (
+                       result, G_OBJECT (source),
+                       e_load_book_source_async), NULL);
+
+       simple = G_SIMPLE_ASYNC_RESULT (result);
+
+       if (g_simple_async_result_propagate_error (simple, error))
                return NULL;
 
-       load_source_data->book = book;
-       g_object_ref (book);
-       e_book_open_async (book, FALSE, load_source_cb, load_source_data);
+       context = g_simple_async_result_get_op_res_gpointer (simple);
+       g_return_val_if_fail (context != NULL, NULL);
 
-       return book;
+       /* Alert the user that an address book is being accessed anonymously.
+        * FIXME Do not mention "LDAP", as this may apply to other backends. */
+       if (context->anonymous_alert) {
+               GtkWidget *dialog;
+
+               dialog = gtk_message_dialog_new (
+                       context->parent, 0,
+                       GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
+                       _("Accessing LDAP Server anonymously"));
+               gtk_dialog_run (GTK_DIALOG (dialog));
+               gtk_widget_destroy (dialog);
+       }
+
+       e_source_set_property (
+               source, "remember_password",
+               context->auth_remember ? "true" : "false");
+
+       return g_object_ref (context->book);
 }
index 893991e..b7075b5 100644 (file)
 #ifndef E_BOOK_AUTH_UTIL_H
 #define E_BOOK_AUTH_UTIL_H
 
+#include <gtk/gtk.h>
 #include <libebook/e-book.h>
 
+G_BEGIN_DECLS
+
 #ifndef E_BOOK_DISABLE_DEPRECATED
-EBook *e_load_book_source (ESource *source, EBookCallback open_func, gpointer user_data);
+EBook *                e_load_book_source              (ESource *source,
+                                                EBookCallback open_func,
+                                                gpointer user_data);
 #endif
 
-EBook *e_load_book_source_async (ESource *source, EBookAsyncCallback open_func, gpointer user_data);
+void           e_load_book_source_async        (ESource *source,
+                                                GtkWindow *parent,
+                                                GCancellable *cancellable,
+                                                GAsyncReadyCallback callback,
+                                                gpointer user_data);
+EBook *                e_load_book_source_finish       (ESource *source,
+                                                GAsyncResult *result,
+                                                GError **error);
 
-#endif
+G_END_DECLS
+
+#endif /* E_BOOK_AUTH_UTIL_H */
index 88fe902..c3a58d7 100644 (file)
@@ -58,9 +58,9 @@ typedef struct {
 
 struct _ENameSelectorDialogPrivate {
 
-       EBook *pending_book;
        ENameSelectorModel *name_selector_model;
        GtkTreeModelSort *contact_sort;
+       GCancellable *cancellable;
 
        GtkBuilder *gui;
        GtkTreeView *contact_view;
@@ -460,10 +460,10 @@ remove_books (ENameSelectorDialog *name_selector_dialog)
        g_list_free (books);
 
        /* See if we have a book pending; stop loading it if so */
-       if (name_selector_dialog->priv->pending_book) {
-               e_book_cancel (name_selector_dialog->priv->pending_book, NULL);
-               g_object_unref (name_selector_dialog->priv->pending_book);
-               name_selector_dialog->priv->pending_book = NULL;
+       if (name_selector_dialog->priv->cancellable != NULL) {
+               g_cancellable_cancel (name_selector_dialog->priv->cancellable);
+               g_object_unref (name_selector_dialog->priv->cancellable);
+               name_selector_dialog->priv->cancellable = NULL;
        }
 }
 
@@ -777,51 +777,83 @@ view_complete(EBookView *view, EBookViewStatus status, const gchar *error_msg, E
 }
 
 static void
-book_opened (EBook *book, const GError *error, gpointer data)
+book_loaded_cb (ESource *source,
+                GAsyncResult *result,
+                ENameSelectorDialog *name_selector_dialog)
 {
-       ENameSelectorDialog *name_selector_dialog = E_NAME_SELECTOR_DIALOG (data);
-       EContactStore       *contact_store;
-       EBookView           *view;
+       EBook *book;
+       EBookView *view;
+       EContactStore *store;
+       ENameSelectorModel *model;
+       GError *error = NULL;
 
-       if (error) {
-               gchar *msg;
+       book = e_load_book_source_finish (source, result, &error);
 
-               msg = g_strdup_printf ("Error loading addressbook, code:%d (%s)", error->code, error->message);
+       if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+               g_warn_if_fail (book == NULL);
+               g_error_free (error);
+               goto exit;
+       }
 
-               gtk_label_set_text(
-                       name_selector_dialog->priv->status_label,
-                       msg);
+       if (error != NULL) {
+               gchar *message;
 
-               g_warning ("ENameSelectorDialog failed to open book! (%d - %s)", error->code, error->message);
+               /* FIXME This shold be translated, no? */
+               message = g_strdup_printf (
+                       "Error loading address book: %s", error->message);
+               gtk_label_set_text (
+                       name_selector_dialog->priv->status_label, message);
+               g_free (message);
 
-               return;
+               g_warn_if_fail (book == NULL);
+               g_error_free (error);
+               goto exit;
        }
 
-       contact_store = e_name_selector_model_peek_contact_store (
-               name_selector_dialog->priv->name_selector_model);
-       e_contact_store_add_book (contact_store, book);
-       view = find_contact_source_by_book_return_view(contact_store, book);
-       g_signal_connect(view, "status_message", G_CALLBACK(status_message), name_selector_dialog);
-       g_signal_connect(view, "view_complete", G_CALLBACK(view_complete), name_selector_dialog);
+       model = name_selector_dialog->priv->name_selector_model;
+       store = e_name_selector_model_peek_contact_store (model);
+       e_contact_store_add_book (store, book);
+
+       view = find_contact_source_by_book_return_view (store, book);
+
+       g_signal_connect (
+               view, "status-message",
+               G_CALLBACK (status_message), name_selector_dialog);
+
+       g_signal_connect (
+               view, "view-complete",
+               G_CALLBACK (view_complete), name_selector_dialog);
 
        g_object_unref (book);
-       name_selector_dialog->priv->pending_book = NULL;
+
+exit:
+       g_object_unref (name_selector_dialog);
 }
 
 static void
 source_changed (ENameSelectorDialog *name_selector_dialog,
                 ESourceComboBox *source_combo_box)
 {
+       GCancellable *cancellable;
        ESource *source;
+       gpointer parent;
 
        source = e_source_combo_box_get_active (source_combo_box);
 
+       parent = gtk_widget_get_toplevel (GTK_WIDGET (name_selector_dialog));
+       parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
        /* Remove any previous books being shown or loaded */
        remove_books (name_selector_dialog);
 
+       cancellable = g_cancellable_new ();
+       name_selector_dialog->priv->cancellable = cancellable;
+
        /* Start loading selected book */
-       name_selector_dialog->priv->pending_book = e_load_book_source_async (
-               source, book_opened, name_selector_dialog);
+       e_load_book_source_async (
+               source, parent, cancellable,
+               (GAsyncReadyCallback) book_loaded_cb,
+               g_object_ref (name_selector_dialog));
 }
 
 /* --------------- *
index b0422fc..ab44bdc 100644 (file)
@@ -1948,6 +1948,40 @@ setup_contact_store (ENameSelectorEntry *name_selector_entry)
 }
 
 static void
+book_loaded_cb (ESource *source,
+                GAsyncResult *result,
+                ENameSelectorEntry *name_selector_entry)
+{
+       EBook *book;
+       EContactStore *store;
+       GError *error = NULL;
+
+       book = e_load_book_source_finish (source, result, &error);
+
+       if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+               g_warn_if_fail (book == NULL);
+               g_error_free (error);
+               goto exit;
+       }
+
+       if (error != NULL) {
+               g_warning ("%s", error->message);
+               g_warn_if_fail (book == NULL);
+               g_error_free (error);
+               goto exit;
+       }
+
+       g_return_if_fail (E_IS_BOOK (book));
+
+       store = name_selector_entry->priv->contact_store;
+       e_contact_store_add_book (store, book);
+       g_object_unref (book);
+
+exit:
+       g_object_unref (name_selector_entry);
+}
+
+static void
 setup_default_contact_store (ENameSelectorEntry *name_selector_entry)
 {
        GSList *groups;
@@ -1966,8 +2000,7 @@ setup_default_contact_store (ENameSelectorEntry *name_selector_entry)
                GSList       *m;
 
                for (m = sources; m; m = g_slist_next (m)) {
-                       ESource     *source = m->data;
-                       EBook       *book;
+                       ESource *source = m->data;
                        const gchar *completion;
 
                        /* Skip non-completion sources */
@@ -1975,12 +2008,10 @@ setup_default_contact_store (ENameSelectorEntry *name_selector_entry)
                        if (!completion || g_ascii_strcasecmp (completion, "true"))
                                continue;
 
-                       book = e_load_book_source_async (source, NULL, NULL);
-                       if (!book)
-                               continue;
-
-                       e_contact_store_add_book (name_selector_entry->priv->contact_store, book);
-                       g_object_unref (book);
+                       e_load_book_source_async (
+                               source, NULL, NULL,
+                               (GAsyncReadyCallback) book_loaded_cb,
+                               g_object_ref (name_selector_entry));
                }
        }
 
index 96178dd..ec72835 100644 (file)
@@ -54,84 +54,142 @@ struct _ENameSelectorPrivate {
        ENameSelectorDialog *dialog;
 
        GArray *sections;
+       ESourceList *source_list;
 
-       GThread *load_book_thread;
        gboolean load_cancelled;
        GArray *source_books;
 };
 
 G_DEFINE_TYPE (ENameSelector, e_name_selector, G_TYPE_OBJECT)
 
-static gpointer
-load_books_thread (gpointer user_data)
+static void
+name_selector_book_loaded_cb (ESource *source,
+                              GAsyncResult *result,
+                              ENameSelector *name_selector)
 {
-       ENameSelector *name_selector = user_data;
-       ENameSelectorPrivate *priv;
-       ESourceList *source_list;
-       GSList      *groups;
-       GSList      *l;
+       EBook *book;
+       GArray *sections;
+       SourceBook source_book;
+       guint ii;
+       GError *error = NULL;
+
+       book = e_load_book_source_finish (source, result, &error);
+
+       if (error != NULL) {
+               g_warning (
+                       "ENameSelector: Could not load \"%s\": %s",
+                       e_source_peek_name (source), error->message);
+               g_error_free (error);
+               goto exit;
+       }
 
-       /* XXX This thread is necessary because the e_book_new can block.
-          See gnome's bug #540779 for more information. */
+       g_return_if_fail (E_IS_BOOK (book));
 
-       g_return_val_if_fail (name_selector != NULL, NULL);
+       source_book.book = book;
+       source_book.is_completion_book = TRUE;
 
-       priv = E_NAME_SELECTOR_GET_PRIVATE (name_selector);
+       g_array_append_val (name_selector->priv->source_books, source_book);
+
+       sections = name_selector->priv->sections;
+
+       for (ii = 0; ii < sections->len; ii++) {
+               EContactStore *store;
+               Section *section;
+
+               section = &g_array_index (sections, Section, ii);
+               if (section->entry == NULL)
+                       continue;
+
+               store = e_name_selector_entry_peek_contact_store (
+                       section->entry);
+               if (store != NULL)
+                       e_contact_store_add_book (store, book);
+       }
+
+exit:
+       g_object_unref (name_selector);
+}
+
+static void
+name_selector_load_books (ENameSelector *name_selector)
+{
+       ESourceList *source_list;
+       GSList *groups;
+       GSList *iter1;
 
        if (!e_book_get_addressbooks (&source_list, NULL)) {
                g_warning ("ENameSelector can't find any addressbooks!");
-               return NULL;
+               return;
        }
 
+       /* This keeps the source groups alive while the async operation
+        * is running, without which e_book_new() can't obtain an absolute
+        * URI for the ESource.   We drop the reference in dispose(). */
+       name_selector->priv->source_list = source_list;
+
        groups = e_source_list_peek_groups (source_list);
 
-       for (l = groups; l && !priv->load_cancelled; l = g_slist_next (l)) {
-               ESourceGroup *group   = l->data;
-               GSList       *sources = e_source_group_peek_sources (group);
-               GSList       *m;
+       for (iter1 = groups; iter1 != NULL; iter1 = iter1->next) {
+               ESourceGroup *source_group;
+               GSList *sources;
+               GSList *iter2;
+
+               source_group = E_SOURCE_GROUP (iter1->data);
+               sources = e_source_group_peek_sources (source_group);
 
-               for (m = sources; m && !priv->load_cancelled; m = g_slist_next (m)) {
-                       ESource      *source = m->data;
-                       const gchar  *completion;
-                       SourceBook    source_book;
+               for (iter2 = sources; iter2 != NULL; iter2 = iter2->next) {
+                       ESource *source;
+                       const gchar *property;
 
-                       /* We're only loading completion books for now, as we don't want
-                        * unnecessary auth prompts */
-                       completion = e_source_get_property (source, "completion");
-                       if (!completion || g_ascii_strcasecmp (completion, "true"))
+                       source = E_SOURCE (iter2->data);
+
+                       /* We're only loading completion books for now,
+                        * as we don't want unnecessary authentication
+                        * prompts. */
+                       property = e_source_get_property (source, "completion");
+
+                       if (property == NULL)
                                continue;
 
-                       source_book.book = e_load_book_source_async (source, NULL, NULL);
-                       if (!source_book.book)
+                       if (g_ascii_strcasecmp (property, "true") != 0)
                                continue;
 
-                       source_book.is_completion_book = TRUE;
+                       /* XXX Should we allow for cancellation? */
+                       e_load_book_source_async (
+                               source, NULL, NULL,
+                               (GAsyncReadyCallback)
+                               name_selector_book_loaded_cb,
+                               g_object_ref (name_selector));
+               }
+       }
+}
 
-                       g_array_append_val (priv->source_books, source_book);
+static void
+name_selector_dispose (GObject *object)
+{
+       ENameSelectorPrivate *priv;
+       guint ii;
 
-                       if (!priv->load_cancelled) {
-                               EContactStore *store;
+       priv = E_NAME_SELECTOR_GET_PRIVATE (object);
 
-                               if (name_selector->priv->sections) {
-                                       gint j;
+       if (priv->source_list != NULL) {
+               g_object_unref (priv->source_list);
+               priv->source_list = NULL;
+       }
 
-                                       for (j = 0; j < name_selector->priv->sections->len && !priv->load_cancelled; j++) {
-                                               Section *section = &g_array_index (name_selector->priv->sections, Section, j);
+       for (ii = 0; ii < priv->source_books->len; ii++) {
+               SourceBook *source_book;
 
-                                               if (section->entry) {
-                                                       store = e_name_selector_entry_peek_contact_store (section->entry);
-                                                       if (store)
-                                                               e_contact_store_add_book (store, source_book.book);
-                                               }
-                                       }
-                               }
-                       }
-               }
+               source_book = &g_array_index (
+                       priv->source_books, SourceBook, ii);
+               if (source_book->book != NULL)
+                       g_object_unref (source_book->book);
        }
 
-       g_object_unref (source_list);
+       g_array_set_size (priv->source_books, 0);
 
-       return NULL;
+       /* Chain up to parent's dispose() method. */
+       G_OBJECT_CLASS (e_name_selector_parent_class)->dispose (object);
 }
 
 static void
@@ -141,27 +199,9 @@ name_selector_finalize (GObject *object)
 
        priv = E_NAME_SELECTOR_GET_PRIVATE (object);
 
-       if (priv->load_book_thread) {
-               priv->load_cancelled = TRUE;
-               g_thread_join (priv->load_book_thread);
-               priv->load_book_thread = NULL;
-       }
-
-       if (priv->source_books) {
-               gint i;
-
-               for (i = 0; i < priv->source_books->len; i++) {
-                       SourceBook *source_book = &g_array_index (priv->source_books, SourceBook, i);
+       g_array_free (priv->source_books, TRUE);
 
-                       if (source_book->book)
-                               g_object_unref (source_book->book);
-               }
-
-               g_array_free (priv->source_books, TRUE);
-               priv->source_books = NULL;
-       }
-
-       /* Chain up to parent's finalize() methods. */
+       /* Chain up to parent's finalize() method. */
        G_OBJECT_CLASS (e_name_selector_parent_class)->finalize (object);
 }
 
@@ -173,6 +213,7 @@ e_name_selector_class_init (ENameSelectorClass *class)
        g_type_class_add_private (class, sizeof (ENameSelectorPrivate));
 
        object_class = G_OBJECT_CLASS (class);
+       object_class->dispose = name_selector_dispose;
        object_class->finalize = name_selector_finalize;
 }
 
@@ -181,7 +222,6 @@ e_name_selector_init (ENameSelector *name_selector)
 {
        GArray *sections;
        GArray *source_books;
-       GThread *load_book_thread;
 
        sections = g_array_new (FALSE, FALSE, sizeof (Section));
        source_books = g_array_new (FALSE, FALSE, sizeof (SourceBook));
@@ -190,11 +230,8 @@ e_name_selector_init (ENameSelector *name_selector)
        name_selector->priv->sections = sections;
        name_selector->priv->model = e_name_selector_model_new ();
        name_selector->priv->source_books = source_books;
-       name_selector->priv->load_book_thread = load_book_thread;
-
-       load_book_thread = g_thread_create (
-               load_books_thread, name_selector, TRUE, NULL);
 
+       name_selector_load_books (name_selector);
 }
 
 /**