From 9d6a9a2bdb9e923dacca100d7a147f02a98db816 Mon Sep 17 00:00:00 2001 From: Packit Date: Sep 25 2020 09:30:56 +0000 Subject: Apply patch evolution-3.28.5-config-lookup-crash.patch patch_name: evolution-3.28.5-config-lookup-crash.patch present_in_specfile: true --- diff --git a/src/e-util/e-collection-account-wizard.c b/src/e-util/e-collection-account-wizard.c index 49b1ac4..84ccd3f 100644 --- a/src/e-util/e-collection-account-wizard.c +++ b/src/e-util/e-collection-account-wizard.c @@ -1858,8 +1858,6 @@ collection_account_wizard_dispose (GObject *object) wizard->priv->store_passwords = NULL; } - g_warn_if_fail (wizard->priv->running_result == NULL); - if (wizard->priv->running_result) { e_simple_async_result_complete_idle (wizard->priv->running_result); g_clear_object (&wizard->priv->running_result); diff --git a/src/e-util/e-collection-account-wizard.c.config-lookup-crash b/src/e-util/e-collection-account-wizard.c.config-lookup-crash new file mode 100644 index 0000000..49b1ac4 --- /dev/null +++ b/src/e-util/e-collection-account-wizard.c.config-lookup-crash @@ -0,0 +1,2358 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2017 Red Hat, Inc. (www.redhat.com) + * + * This library 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 library 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 this library. If not, see . + */ + +/** + * SECTION: e-collection-account-wizard + * @include: e-util/e-util.h + * @short_description: Collection account wizard + * + * #ECollectionAccountWizard is a configuration wizard which guides + * user through steps to created collection accounts. Such accounts + * provide multiple sources at once, being it address books, calendars, + * mail and others. + **/ + +#include "evolution-config.h" + +#include +#include +#include +#include + +#include "e-config-lookup.h" +#include "e-dialog-widgets.h" +#include "e-misc-utils.h" +#include "e-spinner.h" +#include "e-simple-async-result.h" + +#include "e-collection-account-wizard.h" + +/* There is no mail identity in the EConfigLookupResultKind enum, thus define a fake one */ +#define FAKE_E_CONFIG_LOOKUP_RESULT_MAIL_IDENTITY E_CONFIG_LOOKUP_RESULT_UNKNOWN + +struct _ECollectionAccountWizardPrivate { + ESourceRegistry *registry; + EConfigLookup *config_lookup; + GHashTable *store_passwords; /* gchar *source-uid ~> gchar *password */ + GHashTable *workers; /* EConfigLookupWorker * ~> WorkerData * */ + guint running_workers; + ESimpleAsyncResult *running_result; + gboolean changed; + + ESource *sources[E_CONFIG_LOOKUP_RESULT_LAST_KIND + 1]; + + /* Lookup page */ + GtkWidget *email_entry; + GtkWidget *advanced_expander; + GtkWidget *servers_entry; + GtkWidget *results_label; + + /* Parts page */ + GtkTreeView *parts_tree_view; + + /* Finish page */ + GtkWidget *display_name_entry; + GtkWidget *finish_running_box; + GtkWidget *finish_spinner; + GtkWidget *finish_label; + GtkWidget *finish_cancel_button; + GCancellable *finish_cancellable; +}; + +enum { + PROP_0, + PROP_REGISTRY, + PROP_CAN_RUN +}; + +enum { + DONE, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +G_DEFINE_TYPE (ECollectionAccountWizard, e_collection_account_wizard, GTK_TYPE_NOTEBOOK) + +enum { + PART_COLUMN_BOOL_ENABLED, /* G_TYPE_BOOLEAN */ + PART_COLUMN_BOOL_ENABLED_VISIBLE, /* G_TYPE_BOOLEAN */ + PART_COLUMN_BOOL_RADIO, /* G_TYPE_BOOLEAN */ + PART_COLUMN_BOOL_SENSITIVE, /* G_TYPE_BOOLEAN */ + PART_COLUMN_BOOL_IS_COLLECTION_GROUP, /* G_TYPE_BOOLEAN */ + PART_COLUMN_BOOL_ICON_VISIBLE, /* G_TYPE_BOOLEAN */ + PART_COLUMN_STRING_ICON_NAME, /* G_TYPE_STRING */ + PART_COLUMN_STRING_DESCRIPTION, /* G_TYPE_STRING */ + PART_COLUMN_STRING_PROTOCOL, /* G_TYPE_STRING */ + PART_COLUMN_OBJECT_RESULT, /* E_TYPE_CONFIG_LOOKUP_RESULT */ + PART_N_COLUMNS +}; + +typedef struct _WorkerData { + GtkWidget *enabled_check; + GtkWidget *running_box; + GtkWidget *spinner; + GtkWidget *running_label; + GtkWidget *cancel_button; + GCancellable *cancellable; + gulong status_id; + ENamedParameters *restart_params; + gchar *certificate_error; + gboolean remember_password; +} WorkerData; + +static void +worker_data_free (gpointer ptr) +{ + WorkerData *wd = ptr; + + if (wd) { + if (wd->cancellable) { + g_cancellable_cancel (wd->cancellable); + + if (wd->status_id) { + g_signal_handler_disconnect (wd->cancellable, wd->status_id); + wd->status_id = 0; + } + + g_clear_object (&wd->cancellable); + } + + g_clear_pointer (&wd->certificate_error, g_free); + g_clear_pointer (&wd->restart_params, e_named_parameters_free); + + g_free (wd); + } +} + +static void +collection_account_wizard_update_status_cb (CamelOperation *op, + const gchar *what, + gint pc, + gpointer user_data) +{ + GtkLabel *label = user_data; + + g_return_if_fail (GTK_IS_LABEL (label)); + + if (what) + gtk_label_set_label (label, what); +} + +static void +collection_account_wizard_notify_can_run (GObject *wizard) +{ + g_return_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard)); + + g_object_notify (wizard, "can-run"); +} + +static void +collection_account_wizard_mark_changed (ECollectionAccountWizard *wizard) +{ + g_return_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard)); + + wizard->priv->changed = TRUE; +} + +static void +collection_account_wizard_worker_cancel_clicked_cb (GtkWidget *button, + gpointer user_data) +{ + WorkerData *wd = user_data; + + g_return_if_fail (wd != NULL); + + if (wd->cancellable) + g_cancellable_cancel (wd->cancellable); +} + +static void +collection_account_wizard_worker_started_cb (EConfigLookup *config_lookup, + EConfigLookupWorker *worker, + GCancellable *cancellable, + gpointer user_data) +{ + ECollectionAccountWizard *wizard = user_data; + WorkerData *wd; + + g_return_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard)); + + wd = g_hash_table_lookup (wizard->priv->workers, worker); + g_return_if_fail (wd != NULL); + + if (wizard->priv->changed) { + wizard->priv->changed = FALSE; + e_config_lookup_clear_results (wizard->priv->config_lookup); + } + + wizard->priv->running_workers++; + + g_warn_if_fail (wd->cancellable == NULL); + wd->cancellable = g_object_ref (cancellable); + + wd->status_id = 0; + if (CAMEL_IS_OPERATION (wd->cancellable)) { + wd->status_id = g_signal_connect (wd->cancellable, "status", + G_CALLBACK (collection_account_wizard_update_status_cb), wd->running_label); + } + + gtk_label_set_label (GTK_LABEL (wd->running_label), _("Looking up details, please wait…")); + e_spinner_start (E_SPINNER (wd->spinner)); + gtk_widget_show (wd->spinner); + gtk_widget_show (wd->cancel_button); + gtk_widget_show (wd->running_box); + + if (wizard->priv->running_workers == 1) { + GHashTableIter iter; + gpointer value; + + g_hash_table_iter_init (&iter, wizard->priv->workers); + while (g_hash_table_iter_next (&iter, NULL, &value)) { + WorkerData *wd2 = value; + + gtk_widget_set_sensitive (wd2->enabled_check, FALSE); + } + + g_object_notify (G_OBJECT (wizard), "can-run"); + + gtk_label_set_text (GTK_LABEL (wizard->priv->results_label), ""); + } +} + +static void +collection_account_wizard_worker_finished_cb (EConfigLookup *config_lookup, + EConfigLookupWorker *worker, + const ENamedParameters *restart_params, + const GError *error, + gpointer user_data) +{ + ECollectionAccountWizard *wizard = user_data; + WorkerData *wd; + + g_return_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard)); + + wd = g_hash_table_lookup (wizard->priv->workers, worker); + g_return_if_fail (wd != NULL); + + wizard->priv->running_workers--; + + if (wd->status_id) { + g_signal_handler_disconnect (wd->cancellable, wd->status_id); + wd->status_id = 0; + } + g_clear_object (&wd->cancellable); + g_clear_pointer (&wd->certificate_error, g_free); + + e_spinner_stop (E_SPINNER (wd->spinner)); + gtk_widget_hide (wd->spinner); + gtk_widget_hide (wd->cancel_button); + + if (g_error_matches (error, E_CONFIG_LOOKUP_WORKER_ERROR, E_CONFIG_LOOKUP_WORKER_ERROR_REQUIRES_PASSWORD)) { + gchar *markup, *link; + + link = g_markup_printf_escaped ("%s", _("Enter password")); + + /* Translators: The %s is replaced with a clickable text "Enter password", thus it'll be "Requires password to continue. Enter password." at the end. */ + markup = g_strdup_printf (_("Requires password to continue. %s."), link); + + gtk_label_set_markup (GTK_LABEL (wd->running_label), markup); + + g_free (markup); + g_free (link); + } else if (g_error_matches (error, E_CONFIG_LOOKUP_WORKER_ERROR, E_CONFIG_LOOKUP_WORKER_ERROR_CERTIFICATE) && + restart_params && e_named_parameters_exists (restart_params, E_CONFIG_LOOKUP_PARAM_CERTIFICATE_PEM) && + e_named_parameters_exists (restart_params, E_CONFIG_LOOKUP_PARAM_CERTIFICATE_HOST)) { + gchar *markup, *link; + + wd->certificate_error = g_strdup (error->message); + + link = g_markup_printf_escaped ("%s", _("View certificate")); + + /* Translators: The first %s is replaced with actual error message about site certificate invalidity, + like "The signing certificate authority is not known.", the second %s is replaced with a link "View certificate". */ + markup = g_strdup_printf (C_("collection-account-wizard", "%s %s."), error->message, link); + + gtk_label_set_markup (GTK_LABEL (wd->running_label), markup); + + g_free (markup); + g_free (link); + } else if (error) { + gtk_label_set_text (GTK_LABEL (wd->running_label), error->message); + } else { + gtk_widget_hide (wd->running_box); + } + + e_named_parameters_free (wd->restart_params); + wd->restart_params = restart_params ? e_named_parameters_new_clone (restart_params) : NULL; + + if (!wizard->priv->running_workers) { + GHashTableIter iter; + gpointer value; + gint n_results; + gchar *str; + + g_hash_table_iter_init (&iter, wizard->priv->workers); + while (g_hash_table_iter_next (&iter, NULL, &value)) { + WorkerData *wd2 = value; + + gtk_widget_set_sensitive (wd2->enabled_check, TRUE); + } + + if (wizard->priv->running_result) { + e_simple_async_result_complete_idle (wizard->priv->running_result); + g_clear_object (&wizard->priv->running_result); + } + + g_object_notify (G_OBJECT (wizard), "can-run"); + + n_results = e_config_lookup_count_results (wizard->priv->config_lookup); + + if (!n_results) { + gtk_label_set_text (GTK_LABEL (wizard->priv->results_label), _("Found no candidates. It can also mean that the server doesn’t provide any information about its configuration using the selected lookup methods. Enter the account manually instead or change above settings.")); + } else { + str = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "Found one candidate", "Found %d candidates", n_results), n_results); + gtk_label_set_text (GTK_LABEL (wizard->priv->results_label), str); + g_free (str); + } + } +} + +typedef struct _PasswordPromptData { + ECollectionAccountWizard *wizard; + EConfigLookupWorker *worker; + GtkWidget *popover; + GtkWidget *password_entry; + GtkWidget *remember_check; +} PasswordPromptData; + +static PasswordPromptData * +password_prompt_data_new (ECollectionAccountWizard *wizard, + EConfigLookupWorker *worker, + GtkWidget *popover, + GtkWidget *password_entry, + GtkWidget *remember_check) +{ + PasswordPromptData *ppd; + + ppd = g_new0 (PasswordPromptData, 1); + ppd->wizard = wizard; + ppd->worker = worker; + ppd->popover = popover; + ppd->password_entry = password_entry; + ppd->remember_check = remember_check; + + return ppd; +} + +static void +password_prompt_data_free (gpointer data, + GClosure *closure) +{ + PasswordPromptData *ppd = data; + + if (ppd) { + /* Nothing to free inside the structure */ + g_free (ppd); + } +} + +static void +collection_account_wizard_try_again_clicked_cb (GtkButton *button, + gpointer user_data) +{ + PasswordPromptData *ppd = user_data; + ENamedParameters *params; + WorkerData *wd; + + g_return_if_fail (ppd != NULL); + g_return_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (ppd->wizard)); + g_return_if_fail (GTK_IS_ENTRY (ppd->password_entry)); + + wd = g_hash_table_lookup (ppd->wizard->priv->workers, ppd->worker); + g_return_if_fail (wd != NULL); + + params = e_named_parameters_new_clone (wd->restart_params); + g_return_if_fail (params != NULL); + + wd->remember_password = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ppd->remember_check)); + + e_named_parameters_set (params, E_CONFIG_LOOKUP_PARAM_EMAIL_ADDRESS, gtk_entry_get_text (GTK_ENTRY (ppd->wizard->priv->email_entry))); + e_named_parameters_set (params, E_CONFIG_LOOKUP_PARAM_SERVERS, gtk_entry_get_text (GTK_ENTRY (ppd->wizard->priv->servers_entry))); + e_named_parameters_set (params, E_CONFIG_LOOKUP_PARAM_PASSWORD, gtk_entry_get_text (GTK_ENTRY (ppd->password_entry))); + e_named_parameters_set (params, E_CONFIG_LOOKUP_PARAM_REMEMBER_PASSWORD, wd->remember_password ? "1" : NULL); + + e_config_lookup_run_worker (ppd->wizard->priv->config_lookup, ppd->worker, params, NULL); + + e_named_parameters_free (params); + + gtk_widget_hide (ppd->popover); +} + +static void +collection_account_wizard_show_password_prompt (ECollectionAccountWizard *wizard, + EConfigLookupWorker *worker, + WorkerData *wd) +{ + GtkWidget *widget, *label, *entry, *check, *button; + GtkGrid *grid; + + g_return_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard)); + g_return_if_fail (E_IS_CONFIG_LOOKUP_WORKER (worker)); + g_return_if_fail (wd != NULL); + + widget = gtk_grid_new (); + grid = GTK_GRID (widget); + gtk_grid_set_column_spacing (grid, 6); + gtk_grid_set_row_spacing (grid, 6); + + widget = gtk_label_new_with_mnemonic (_("_Password:")); + gtk_widget_set_halign (widget, GTK_ALIGN_END); + gtk_grid_attach (grid, widget, 0, 0, 1, 1); + label = widget; + + widget = gtk_entry_new (); + gtk_entry_set_visibility (GTK_ENTRY (widget), FALSE); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget); + gtk_grid_attach (grid, widget, 1, 0, 1, 1); + entry = widget; + + widget = gtk_check_button_new_with_mnemonic (_("_Remember password")); + gtk_grid_attach (grid, widget, 0, 1, 2, 1); + check = widget; + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), wd->remember_password); + + widget = gtk_button_new_with_mnemonic (_("_Try Again")); + gtk_widget_set_halign (widget, GTK_ALIGN_END); + gtk_widget_set_can_default (widget, TRUE); + gtk_grid_attach (grid, widget, 0, 2, 2, 1); + button = widget; + + gtk_widget_show_all (GTK_WIDGET (grid)); + + widget = gtk_popover_new (wd->running_label); + gtk_popover_set_position (GTK_POPOVER (widget), GTK_POS_BOTTOM); + gtk_popover_set_default_widget (GTK_POPOVER (widget), button); + gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); + gtk_container_add (GTK_CONTAINER (widget), GTK_WIDGET (grid)); + gtk_container_set_border_width (GTK_CONTAINER (widget), 6); + + g_signal_connect_data (button, "clicked", + G_CALLBACK (collection_account_wizard_try_again_clicked_cb), + password_prompt_data_new (wizard, worker, widget, entry, check), + password_prompt_data_free, 0); + + g_signal_connect (widget, "closed", + G_CALLBACK (gtk_widget_destroy), NULL); + + gtk_widget_show (widget); + + gtk_widget_grab_focus (entry); +} + +static void +collection_account_wizard_view_certificate (ECollectionAccountWizard *wizard, + EConfigLookupWorker *worker, + WorkerData *wd) +{ + ETrustPromptResponse response; + GtkWidget *toplevel; + GtkWindow *parent = NULL; + + g_return_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard)); + g_return_if_fail (E_IS_CONFIG_LOOKUP_WORKER (worker)); + g_return_if_fail (wd != NULL); + g_return_if_fail (wd->restart_params != NULL); + g_return_if_fail (e_named_parameters_exists (wd->restart_params, E_CONFIG_LOOKUP_PARAM_CERTIFICATE_PEM)); + g_return_if_fail (e_named_parameters_exists (wd->restart_params, E_CONFIG_LOOKUP_PARAM_CERTIFICATE_HOST)); + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (wizard)); + if (GTK_IS_WINDOW (toplevel)) + parent = GTK_WINDOW (toplevel); + + response = e_trust_prompt_run_modal (parent, NULL, NULL, + e_named_parameters_get (wd->restart_params, E_CONFIG_LOOKUP_PARAM_CERTIFICATE_HOST), + e_named_parameters_get (wd->restart_params, E_CONFIG_LOOKUP_PARAM_CERTIFICATE_PEM), + 0, wd->certificate_error); + + if (response != E_TRUST_PROMPT_RESPONSE_UNKNOWN) { + ENamedParameters *params; + + params = e_named_parameters_new_clone (wd->restart_params); + g_return_if_fail (params != NULL); + + e_named_parameters_set (params, E_CONFIG_LOOKUP_PARAM_EMAIL_ADDRESS, gtk_entry_get_text (GTK_ENTRY (wizard->priv->email_entry))); + e_named_parameters_set (params, E_CONFIG_LOOKUP_PARAM_SERVERS, gtk_entry_get_text (GTK_ENTRY (wizard->priv->servers_entry))); + e_named_parameters_set (params, E_CONFIG_LOOKUP_PARAM_CERTIFICATE_TRUST, e_config_lookup_encode_certificate_trust (response)); + + e_config_lookup_run_worker (wizard->priv->config_lookup, worker, params, NULL); + + e_named_parameters_free (params); + } +} + +static gboolean +collection_account_wizard_activate_link_cb (GtkWidget *label, + const gchar *uri, + gpointer user_data) +{ + ECollectionAccountWizard *wizard = user_data; + EConfigLookupWorker *worker = NULL; + WorkerData *wd; + GHashTableIter iter; + gpointer key, value; + + g_return_val_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard), TRUE); + + g_hash_table_iter_init (&iter, wizard->priv->workers); + while (g_hash_table_iter_next (&iter, &key, &value)) { + worker = key; + wd = value; + + if (worker && wd && wd->running_label == label) + break; + + worker = NULL; + wd = NULL; + } + + if (worker && wd) { + if (g_strcmp0 (uri, "evo:enter-password") == 0) + collection_account_wizard_show_password_prompt (wizard, worker, wd); + else if (g_strcmp0 (uri, "evo:view-certificate") == 0) + collection_account_wizard_view_certificate (wizard, worker, wd); + else + g_warning ("%s: Do not know what to do with '%s'", G_STRFUNC, uri); + } + + return TRUE; +} + +static gboolean +collection_account_wizard_is_first_result_of_this_kind (GSList *known_results, + EConfigLookupResult *result) +{ + GSList *link; + gboolean known = FALSE; + + for (link = known_results; link && !known; link = g_slist_next (link)) { + EConfigLookupResult *result2 = link->data; + + if (!result2 || result2 == result) + continue; + + known = e_config_lookup_result_get_kind (result) == + e_config_lookup_result_get_kind (result2) && + g_strcmp0 (e_config_lookup_result_get_protocol (result), + e_config_lookup_result_get_protocol (result2)) == 0; + } + + return !known; +} + +static gboolean +collection_account_wizard_fill_results (ECollectionAccountWizard *wizard) +{ + struct _results_info { + EConfigLookupResultKind kind; + const gchar *display_name; + const gchar *icon_name; + GSList *results; /* EConfigLookupResult * */ + } results_info[] = { + { E_CONFIG_LOOKUP_RESULT_COLLECTION, N_("Collection"), "evolution", NULL }, + { E_CONFIG_LOOKUP_RESULT_MAIL_RECEIVE, N_("Mail Receive"), "evolution-mail", NULL }, + { E_CONFIG_LOOKUP_RESULT_MAIL_SEND, N_("Mail Send"), "mail-send", NULL }, + { E_CONFIG_LOOKUP_RESULT_ADDRESS_BOOK, N_("Address Book"), "x-office-address-book",NULL }, + { E_CONFIG_LOOKUP_RESULT_CALENDAR, N_("Calendar"), "x-office-calendar", NULL }, + { E_CONFIG_LOOKUP_RESULT_MEMO_LIST, N_("Memo List"), "evolution-memos", NULL }, + { E_CONFIG_LOOKUP_RESULT_TASK_LIST, N_("Task List"), "evolution-tasks", NULL } + }; + + GtkTreeStore *tree_store; + GtkTreeIter iter, parent; + gint ii; + gboolean found_any = FALSE; + + g_return_val_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard), FALSE); + + tree_store = GTK_TREE_STORE (gtk_tree_view_get_model (wizard->priv->parts_tree_view)); + gtk_tree_store_clear (tree_store); + + for (ii = 0; ii < G_N_ELEMENTS (results_info); ii++) { + results_info[ii].results = e_config_lookup_dup_results (wizard->priv->config_lookup, results_info[ii].kind, NULL); + + if (results_info[ii].results) { + found_any = TRUE; + + results_info[ii].results = g_slist_sort (results_info[ii].results, e_config_lookup_result_compare); + } + } + + if (!found_any) + return FALSE; + + for (ii = 0; ii < G_N_ELEMENTS (results_info); ii++) { + GSList *results = results_info[ii].results, *link, *known_results = NULL, *klink; + gboolean is_collection_kind = results_info[ii].kind == E_CONFIG_LOOKUP_RESULT_COLLECTION; + GtkTreePath *path; + + /* Skip empty groups */ + if (!results) + continue; + + gtk_tree_store_append (tree_store, &parent, NULL); + gtk_tree_store_set (tree_store, &parent, + PART_COLUMN_BOOL_ENABLED, TRUE, + PART_COLUMN_BOOL_ENABLED_VISIBLE, TRUE, + PART_COLUMN_BOOL_RADIO, FALSE, + PART_COLUMN_BOOL_SENSITIVE, results != NULL, + PART_COLUMN_BOOL_IS_COLLECTION_GROUP, is_collection_kind, + PART_COLUMN_BOOL_ICON_VISIBLE, results_info[ii].icon_name != NULL, + PART_COLUMN_STRING_ICON_NAME, results_info[ii].icon_name, + PART_COLUMN_STRING_DESCRIPTION, _(results_info[ii].display_name), + -1); + + for (link = results; link; link = g_slist_next (link)) { + EConfigLookupResult *result = link->data; + const gchar *display_name, *description; + gchar *markup; + + if (!result) + continue; + + for (klink = known_results; klink; klink = g_slist_next (klink)) { + if (e_config_lookup_result_equal (result, klink->data)) + break; + } + + /* Found one such processed already. */ + if (klink) + continue; + + /* Just borrow it, no need to reference it. */ + known_results = g_slist_prepend (known_results, result); + + display_name = e_config_lookup_result_get_display_name (result); + description = e_config_lookup_result_get_description (result); + + if (description && *description) + markup = g_markup_printf_escaped ("%s\n%s", display_name, description); + else + markup = g_markup_printf_escaped ("%s", display_name); + + gtk_tree_store_append (tree_store, &iter, &parent); + gtk_tree_store_set (tree_store, &iter, + PART_COLUMN_BOOL_ENABLED, link == results || (is_collection_kind && collection_account_wizard_is_first_result_of_this_kind (known_results, result)), + PART_COLUMN_BOOL_ENABLED_VISIBLE, g_slist_next (results) != NULL, + PART_COLUMN_BOOL_RADIO, !is_collection_kind, + PART_COLUMN_BOOL_SENSITIVE, TRUE, + PART_COLUMN_BOOL_ICON_VISIBLE, NULL, + PART_COLUMN_STRING_ICON_NAME, NULL, + PART_COLUMN_STRING_DESCRIPTION, markup, + PART_COLUMN_STRING_PROTOCOL, e_config_lookup_result_get_protocol (result), + PART_COLUMN_OBJECT_RESULT, result, + -1); + + g_free (markup); + } + + g_slist_free (known_results); + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (tree_store), &parent); + if (path) { + gtk_tree_view_expand_to_path (wizard->priv->parts_tree_view, path); + gtk_tree_path_free (path); + } + } + + for (ii = 0; ii < G_N_ELEMENTS (results_info); ii++) { + g_slist_free_full (results_info[ii].results, g_object_unref); + results_info[ii].results = NULL; + } + + return TRUE; +} + +static void +collection_account_wizard_part_enabled_toggled_cb (GtkCellRendererToggle *cell_renderer, + const gchar *path_string, + gpointer user_data) +{ + ECollectionAccountWizard *wizard = user_data; + GtkTreeStore *tree_store; + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter, parent, child; + EConfigLookupResult *src_result = NULL, *cur_result = NULL; + gboolean set_enabled, is_radio = FALSE; + + g_return_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard)); + + model = gtk_tree_view_get_model (wizard->priv->parts_tree_view); + tree_store = GTK_TREE_STORE (model); + + path = gtk_tree_path_new_from_string (path_string); + if (!gtk_tree_model_get_iter (model, &child, path)) { + g_warn_if_reached (); + gtk_tree_path_free (path); + + return; + } + + gtk_tree_path_free (path); + + set_enabled = !gtk_cell_renderer_toggle_get_active (cell_renderer); + + gtk_tree_model_get (model, &child, + PART_COLUMN_BOOL_RADIO, &is_radio, + PART_COLUMN_OBJECT_RESULT, &src_result, + -1); + + /* Reflect the change for other radio-s in this level */ + if (is_radio) { + GtkTreeIter sibling = child; + + iter = child; + + /* Move to the first sibling */ + if (gtk_tree_model_iter_parent (model, &parent, &child) && + gtk_tree_model_iter_nth_child (model, &iter, &parent, 0)) { + sibling = iter; + } else { + while (gtk_tree_model_iter_previous (model, &iter)) + sibling = iter; + } + + do { + is_radio = FALSE; + + gtk_tree_model_get (model, &sibling, + PART_COLUMN_BOOL_RADIO, &is_radio, + PART_COLUMN_OBJECT_RESULT, &cur_result, + -1); + + if (is_radio) { + gtk_tree_store_set (tree_store, &sibling, + PART_COLUMN_BOOL_ENABLED, cur_result == src_result, + -1); + } + + g_clear_object (&cur_result); + } while (gtk_tree_model_iter_next (model, &sibling)); + } else { + gtk_tree_store_set (tree_store, &child, + PART_COLUMN_BOOL_ENABLED, set_enabled, + -1); + } + + /* De/sensitize children of the group nodes */ + if (!gtk_tree_model_iter_parent (model, &parent, &child) && + gtk_tree_model_iter_nth_child (model, &iter, &child, 0)) { + do { + gtk_tree_store_set (tree_store, &iter, + PART_COLUMN_BOOL_SENSITIVE, set_enabled, + -1); + } while (gtk_tree_model_iter_next (model, &iter)); + } + + g_clear_object (&src_result); + + if (!is_radio) + g_object_notify (G_OBJECT (wizard), "can-run"); +} + +static ESource * +collection_account_wizard_create_child_source (ECollectionAccountWizard *wizard, + const gchar *add_extension_name) +{ + ESource *source; + + g_return_val_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard), NULL); + g_return_val_if_fail (wizard->priv->sources[E_CONFIG_LOOKUP_RESULT_COLLECTION] != NULL, NULL); + + source = e_source_new (NULL, NULL, NULL); + + e_source_set_parent (source, e_source_get_uid (wizard->priv->sources[E_CONFIG_LOOKUP_RESULT_COLLECTION])); + + if (add_extension_name) + e_source_get_extension (source, add_extension_name); + + return source; +} + +static ESource * +collection_account_wizard_get_source (ECollectionAccountWizard *wizard, + EConfigLookupResultKind kind) +{ + ESource *source = NULL; + const gchar *extension_name = NULL; + + g_return_val_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard), NULL); + + switch (kind) { + case E_CONFIG_LOOKUP_RESULT_COLLECTION: + source = wizard->priv->sources[kind]; + g_warn_if_fail (source != NULL); + break; + case E_CONFIG_LOOKUP_RESULT_MAIL_RECEIVE: + extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT; + break; + case FAKE_E_CONFIG_LOOKUP_RESULT_MAIL_IDENTITY: /* E_CONFIG_LOOKUP_RESULT_UNKNOWN */ + extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY; + break; + case E_CONFIG_LOOKUP_RESULT_MAIL_SEND: + extension_name = E_SOURCE_EXTENSION_MAIL_TRANSPORT; + break; + case E_CONFIG_LOOKUP_RESULT_ADDRESS_BOOK: + extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK; + break; + case E_CONFIG_LOOKUP_RESULT_CALENDAR: + extension_name = E_SOURCE_EXTENSION_CALENDAR; + break; + case E_CONFIG_LOOKUP_RESULT_MEMO_LIST: + extension_name = E_SOURCE_EXTENSION_MEMO_LIST; + break; + case E_CONFIG_LOOKUP_RESULT_TASK_LIST: + extension_name = E_SOURCE_EXTENSION_TASK_LIST; + break; + } + + g_return_val_if_fail (kind >= 0 && kind <= E_CONFIG_LOOKUP_RESULT_LAST_KIND, NULL); + + source = wizard->priv->sources[kind]; + + if (!source && kind != E_CONFIG_LOOKUP_RESULT_COLLECTION) { + source = collection_account_wizard_create_child_source (wizard, extension_name); + wizard->priv->sources[kind] = source; + } + + return source; +} + +static ESource * +collection_account_wizard_get_source_cb (ECollectionAccountWizard *wizard, + EConfigLookupSourceKind kind) +{ + ESource *source = NULL; + + g_return_val_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard), NULL); + + switch (kind) { + case E_CONFIG_LOOKUP_SOURCE_UNKNOWN: + break; + case E_CONFIG_LOOKUP_SOURCE_COLLECTION: + source = collection_account_wizard_get_source (wizard, E_CONFIG_LOOKUP_RESULT_COLLECTION); + break; + case E_CONFIG_LOOKUP_SOURCE_MAIL_ACCOUNT: + source = collection_account_wizard_get_source (wizard, E_CONFIG_LOOKUP_RESULT_MAIL_RECEIVE); + break; + case E_CONFIG_LOOKUP_SOURCE_MAIL_IDENTITY: + source = collection_account_wizard_get_source (wizard, FAKE_E_CONFIG_LOOKUP_RESULT_MAIL_IDENTITY); + break; + case E_CONFIG_LOOKUP_SOURCE_MAIL_TRANSPORT: + source = collection_account_wizard_get_source (wizard, E_CONFIG_LOOKUP_RESULT_MAIL_SEND); + break; + } + + return source; +} + +static gboolean +collection_account_wizard_host_is_google_server (const gchar *host) +{ + if (!host || !*host) + return FALSE; + + return camel_strstrcase (host, "gmail.com") || + camel_strstrcase (host, "googlemail.com") || + camel_strstrcase (host, "google.com") || + camel_strstrcase (host, "googleusercontent.com"); +} + +static void +collection_account_wizard_write_changes_thread (ESimpleAsyncResult *result, + gpointer source_object, + GCancellable *cancellable) +{ + ECollectionAccountWizard *wizard = source_object; + ESourceCollection *collection_extension; + ESource *source; + gint ii; + const gchar *text; + GList *sources = NULL; + gboolean google_supported, any_is_google = FALSE; + GError *local_error = NULL; + + g_return_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard)); + + /* Deal with LDAP addressbook first */ + source = wizard->priv->sources[E_CONFIG_LOOKUP_RESULT_ADDRESS_BOOK]; + if (source && + e_source_has_extension (source, E_SOURCE_EXTENSION_LDAP_BACKEND) && + e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) { + ESourceAuthentication *auth_extension; + ESourceLDAP *ldap_extension; + const gchar *root_dn; + + auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); + ldap_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_LDAP_BACKEND); + root_dn = e_source_ldap_get_root_dn (ldap_extension); + + if (!root_dn || !*root_dn) { + gchar **root_dse = NULL; + + camel_operation_push_message (cancellable, "%s", _("Looking up LDAP server’s search base…")); + + if (e_util_query_ldap_root_dse_sync ( + e_source_authentication_get_host (auth_extension), + e_source_authentication_get_port (auth_extension), + &root_dse, cancellable, NULL)) { + if (root_dse && root_dse[0]) + e_source_ldap_set_root_dn (ldap_extension, root_dse[0]); + + g_strfreev (root_dse); + } + + camel_operation_pop_message (cancellable); + } + } + + if (wizard->priv->sources[E_CONFIG_LOOKUP_RESULT_MAIL_RECEIVE]) { + ESourceMailAccount *mail_account_extension; + ESourceMailIdentity *mail_identity_extension; + ESourceMailTransport *mail_transport_extension; + ESourceMailSubmission *mail_submission_extension; + + mail_account_extension = e_source_get_extension (wizard->priv->sources[E_CONFIG_LOOKUP_RESULT_MAIL_RECEIVE], E_SOURCE_EXTENSION_MAIL_ACCOUNT); + e_source_mail_account_set_identity_uid (mail_account_extension, e_source_get_uid (wizard->priv->sources[FAKE_E_CONFIG_LOOKUP_RESULT_MAIL_IDENTITY])); + + text = e_source_backend_get_backend_name (E_SOURCE_BACKEND (mail_account_extension)); + if (!text || !*text) + e_source_backend_set_backend_name (E_SOURCE_BACKEND (mail_account_extension), "none"); + + mail_identity_extension = e_source_get_extension (wizard->priv->sources[FAKE_E_CONFIG_LOOKUP_RESULT_MAIL_IDENTITY], E_SOURCE_EXTENSION_MAIL_IDENTITY); + text = e_source_mail_identity_get_name (mail_identity_extension); + if (!text || !*text) + e_source_mail_identity_set_name (mail_identity_extension, g_get_real_name ()); + + mail_submission_extension = e_source_get_extension (wizard->priv->sources[FAKE_E_CONFIG_LOOKUP_RESULT_MAIL_IDENTITY], E_SOURCE_EXTENSION_MAIL_SUBMISSION); + e_source_mail_submission_set_transport_uid (mail_submission_extension, e_source_get_uid (wizard->priv->sources[E_CONFIG_LOOKUP_RESULT_MAIL_SEND])); + + mail_transport_extension = e_source_get_extension (wizard->priv->sources[E_CONFIG_LOOKUP_RESULT_MAIL_SEND], E_SOURCE_EXTENSION_MAIL_TRANSPORT); + + text = e_source_backend_get_backend_name (E_SOURCE_BACKEND (mail_transport_extension)); + if (!text || !*text) + e_source_backend_set_backend_name (E_SOURCE_BACKEND (mail_transport_extension), "none"); + } + + if (!e_source_has_extension (wizard->priv->sources[E_CONFIG_LOOKUP_RESULT_COLLECTION], E_SOURCE_EXTENSION_AUTHENTICATION)) { + /* Make sure the collection source has the Authentication extension, + thus the credentials can be reused. It's fine when the extension + doesn't have set values. */ + e_source_get_extension (wizard->priv->sources[E_CONFIG_LOOKUP_RESULT_COLLECTION], E_SOURCE_EXTENSION_AUTHENTICATION); + } + + collection_extension = e_source_get_extension (wizard->priv->sources[E_CONFIG_LOOKUP_RESULT_COLLECTION], E_SOURCE_EXTENSION_COLLECTION); + + /* Collections with empty backend-name are skipped by ESourceRegistry */ + text = e_source_backend_get_backend_name (E_SOURCE_BACKEND (collection_extension)); + if (!text || !*text) + e_source_backend_set_backend_name (E_SOURCE_BACKEND (collection_extension), "none"); + + google_supported = e_oauth2_services_is_oauth2_alias (e_source_registry_get_oauth2_services (wizard->priv->registry), "Google"); + + for (ii = 0; ii <= E_CONFIG_LOOKUP_RESULT_LAST_KIND; ii++) { + source = wizard->priv->sources[ii]; + + if (!source) + continue; + + /* This is not great, to special-case the Google server, but there's nothing + better at the moment and it's the only OAuth2 right now anyway. */ + if (google_supported && ii != E_CONFIG_LOOKUP_RESULT_COLLECTION && + e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) { + ESourceAuthentication *authentication_extension; + + authentication_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); + if (collection_account_wizard_host_is_google_server (e_source_authentication_get_host (authentication_extension))) { + any_is_google = TRUE; + e_source_authentication_set_method (authentication_extension, "Google"); + } + } + + sources = g_list_prepend (sources, source); + } + + source = wizard->priv->sources[E_CONFIG_LOOKUP_RESULT_COLLECTION]; + /* It is always true, but have the variables local in this place only */ + if (source) { + ESourceAuthentication *authentication_extension; + ESourceCollection *collection_extension; + const gchar *host; + + authentication_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); + collection_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_COLLECTION); + + host = e_source_collection_get_calendar_url (collection_extension); + if (collection_account_wizard_host_is_google_server (host)) { + any_is_google = TRUE; + if (strstr (host, "calendar.google.com")) { + e_source_backend_set_backend_name (E_SOURCE_BACKEND (collection_extension), "webdav"); + + if (google_supported) + e_source_collection_set_calendar_url (collection_extension, "https://apidata.googleusercontent.com/caldav/v2/"); + else + e_source_collection_set_calendar_url (collection_extension, "https://www.google.com/calendar/dav/"); + } + } + + if (any_is_google && google_supported) { + e_source_authentication_set_method (authentication_extension, "Google"); + e_source_backend_set_backend_name (E_SOURCE_BACKEND (collection_extension), "google"); + } + } + + /* First store passwords, thus the evolution-source-registry has them ready if needed. */ + if (g_hash_table_size (wizard->priv->store_passwords) > 0) { + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, wizard->priv->store_passwords); + while (g_hash_table_iter_next (&iter, &key, &value)) { + const gchar *uid = key, *password = value; + + if (uid && *uid && password && *password) { + source = NULL; + + for (ii = 0; ii <= E_CONFIG_LOOKUP_RESULT_LAST_KIND; ii++) { + source = wizard->priv->sources[ii]; + + if (source && g_strcmp0 (uid, e_source_get_uid (source)) == 0) + break; + + source = NULL; + } + + if (source && !e_source_store_password_sync (source, password, TRUE, cancellable, &local_error)) { + e_simple_async_result_set_user_data (result, local_error, (GDestroyNotify) g_error_free); + break; + } + } + } + } + + if (!e_simple_async_result_get_user_data (result) && /* No error from password save */ + !e_source_registry_create_sources_sync (wizard->priv->registry, sources, cancellable, &local_error) && local_error) { + e_simple_async_result_set_user_data (result, local_error, (GDestroyNotify) g_error_free); + } + + g_list_free (sources); +} + +static void +collection_account_wizard_write_changes_done (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + ECollectionAccountWizard *wizard; + const GError *error; + gboolean is_cancelled = FALSE; + + g_return_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (source_object)); + + wizard = E_COLLECTION_ACCOUNT_WIZARD (source_object); + + g_clear_object (&wizard->priv->finish_cancellable); + g_hash_table_remove_all (wizard->priv->store_passwords); + + error = e_simple_async_result_get_user_data (E_SIMPLE_ASYNC_RESULT (result)); + if (error) { + is_cancelled = g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + + gtk_label_set_text (GTK_LABEL (wizard->priv->finish_label), error->message); + } + + e_spinner_stop (E_SPINNER (wizard->priv->finish_spinner)); + + gtk_widget_set_visible (wizard->priv->finish_running_box, error && !is_cancelled); + gtk_widget_set_visible (wizard->priv->finish_spinner, FALSE); + gtk_widget_set_visible (wizard->priv->finish_label, !is_cancelled); + gtk_widget_set_visible (wizard->priv->finish_cancel_button, FALSE); + + g_object_notify (source_object, "can-run"); + + if (!error) { + ESource *source = wizard->priv->sources[E_CONFIG_LOOKUP_RESULT_COLLECTION]; + + g_warn_if_fail (source != NULL); + + g_signal_emit (wizard, signals[DONE], 0, e_source_get_uid (source)); + } +} + +static void +collection_account_wizard_save_sources (ECollectionAccountWizard *wizard) +{ + GtkTreeModel *model; + GtkTreeIter iter; + ESimpleAsyncResult *simple_result; + ESource *source; + const gchar *display_name; + gint ii; + + g_return_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard)); + + g_hash_table_remove_all (wizard->priv->store_passwords); + + model = gtk_tree_view_get_model (wizard->priv->parts_tree_view); + if (gtk_tree_model_get_iter_first (model, &iter)) { + do { + gboolean enabled = FALSE; + + gtk_tree_model_get (model, &iter, + PART_COLUMN_BOOL_ENABLED, &enabled, + -1); + + if (enabled) { + GtkTreeIter child; + + if (gtk_tree_model_iter_nth_child (model, &child, &iter, 0)) { + do { + enabled = FALSE; + + gtk_tree_model_get (model, &child, + PART_COLUMN_BOOL_ENABLED, &enabled, + -1); + + if (enabled) { + EConfigLookupResult *lookup_result = NULL; + + gtk_tree_model_get (model, &child, + PART_COLUMN_OBJECT_RESULT, &lookup_result, + -1); + + if (lookup_result) { + source = collection_account_wizard_get_source (wizard, e_config_lookup_result_get_kind (lookup_result)); + if (source) { + g_warn_if_fail (e_config_lookup_result_configure_source (lookup_result, wizard->priv->config_lookup, source)); + + if (e_config_lookup_result_get_password (lookup_result)) { + g_hash_table_insert (wizard->priv->store_passwords, e_source_dup_uid (source), + g_strdup (e_config_lookup_result_get_password (lookup_result))); + } + } + + g_clear_object (&lookup_result); + } + } + } while (gtk_tree_model_iter_next (model, &child)); + } + } + } while (gtk_tree_model_iter_next (model, &iter)); + } + + display_name = gtk_entry_get_text (GTK_ENTRY (wizard->priv->display_name_entry)); + + if (wizard->priv->sources[E_CONFIG_LOOKUP_RESULT_MAIL_RECEIVE] || + wizard->priv->sources[E_CONFIG_LOOKUP_RESULT_MAIL_SEND]) { + ESourceMailIdentity *identity_extension; + + /* Ensure all three exist */ + collection_account_wizard_get_source (wizard, E_CONFIG_LOOKUP_RESULT_MAIL_RECEIVE); + collection_account_wizard_get_source (wizard, E_CONFIG_LOOKUP_RESULT_MAIL_SEND); + + source = collection_account_wizard_get_source (wizard, FAKE_E_CONFIG_LOOKUP_RESULT_MAIL_IDENTITY); + identity_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_MAIL_IDENTITY); + e_source_mail_identity_set_address (identity_extension, gtk_entry_get_text (GTK_ENTRY (wizard->priv->email_entry))); + } else { + g_clear_object (&wizard->priv->sources[E_CONFIG_LOOKUP_RESULT_MAIL_RECEIVE]); + g_clear_object (&wizard->priv->sources[E_CONFIG_LOOKUP_RESULT_MAIL_SEND]); + g_clear_object (&wizard->priv->sources[FAKE_E_CONFIG_LOOKUP_RESULT_MAIL_IDENTITY]); + } + + for (ii = 0; ii <= E_CONFIG_LOOKUP_RESULT_LAST_KIND; ii++) { + source = wizard->priv->sources[ii]; + + if (source) { + if (ii == E_CONFIG_LOOKUP_RESULT_COLLECTION) { + ESourceAuthentication *authentication_extension; + ESourceCollection *collection_extension; + const gchar *user; + + authentication_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); + collection_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_COLLECTION); + user = gtk_entry_get_text (GTK_ENTRY (wizard->priv->email_entry)); + + if (!e_source_authentication_get_user (authentication_extension)) + e_source_authentication_set_user (authentication_extension, user); + + if (!e_source_collection_get_identity (collection_extension)) + e_source_collection_set_identity (collection_extension, user); + } else { + e_source_set_parent (source, e_source_get_uid (wizard->priv->sources[E_CONFIG_LOOKUP_RESULT_COLLECTION])); + } + + e_source_set_display_name (source, display_name); + } + } + + g_warn_if_fail (wizard->priv->finish_cancellable == NULL); + + gtk_label_set_text (GTK_LABEL (wizard->priv->finish_label), _("Saving account settings, please wait…")); + gtk_widget_show (wizard->priv->finish_spinner); + gtk_widget_show (wizard->priv->finish_label); + gtk_widget_show (wizard->priv->finish_cancel_button); + gtk_widget_show (wizard->priv->finish_running_box); + + e_spinner_start (E_SPINNER (wizard->priv->finish_spinner)); + + wizard->priv->finish_cancellable = camel_operation_new (); + + g_signal_connect (wizard->priv->finish_cancellable, "status", + G_CALLBACK (collection_account_wizard_update_status_cb), wizard->priv->finish_label); + + simple_result = e_simple_async_result_new (G_OBJECT (wizard), + collection_account_wizard_write_changes_done, NULL, + collection_account_wizard_write_changes_done); + + e_simple_async_result_run_in_thread (simple_result, G_PRIORITY_HIGH_IDLE, + collection_account_wizard_write_changes_thread, wizard->priv->finish_cancellable); + + g_object_unref (simple_result); + + g_object_notify (G_OBJECT (wizard), "can-run"); +} + +static void +collection_account_wizard_finish_cancel_clicked_cb (GtkButton *button, + gpointer user_data) +{ + ECollectionAccountWizard *wizard = user_data; + + g_return_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard)); + + if (wizard->priv->finish_cancellable) + g_cancellable_cancel (wizard->priv->finish_cancellable); +} + +static void +collection_account_wizard_set_registry (ECollectionAccountWizard *wizard, + ESourceRegistry *registry) +{ + g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); + g_return_if_fail (wizard->priv->registry == NULL); + + wizard->priv->registry = g_object_ref (registry); +} + +static void +collection_account_wizard_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_REGISTRY: + collection_account_wizard_set_registry ( + E_COLLECTION_ACCOUNT_WIZARD (object), + g_value_get_object (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +collection_account_wizard_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CAN_RUN: + g_value_set_boolean ( + value, + e_collection_account_wizard_get_can_run ( + E_COLLECTION_ACCOUNT_WIZARD (object))); + return; + + case PROP_REGISTRY: + g_value_set_object ( + value, + e_collection_account_wizard_get_registry ( + E_COLLECTION_ACCOUNT_WIZARD (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +collection_account_wizard_constructed (GObject *object) +{ + ECollectionAccountWizard *wizard = E_COLLECTION_ACCOUNT_WIZARD (object); + GtkBox *hbox, *vbox; + GtkGrid *grid; + GtkWidget *label, *widget, *expander, *scrolled_window; + GtkTreeStore *tree_store; + GSList *workers, *link; + GtkTreeViewColumn *column; + GtkCellRenderer *cell_renderer; + gchar *markup; + + /* Chain up to parent's method. */ + G_OBJECT_CLASS (e_collection_account_wizard_parent_class)->constructed (object); + + g_object_set (object, + "show-border", FALSE, + "show-tabs", FALSE, + NULL); + + wizard->priv->config_lookup = e_config_lookup_new (wizard->priv->registry); + + g_signal_connect_swapped (wizard->priv->config_lookup, "get-source", + G_CALLBACK (collection_account_wizard_get_source_cb), wizard); + + g_signal_connect (wizard->priv->config_lookup, "worker-started", + G_CALLBACK (collection_account_wizard_worker_started_cb), wizard); + + g_signal_connect (wizard->priv->config_lookup, "worker-finished", + G_CALLBACK (collection_account_wizard_worker_finished_cb), wizard); + + /* Lookup page */ + + vbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 6)); + g_object_set (G_OBJECT (vbox), + "hexpand", TRUE, + "halign", GTK_ALIGN_FILL, + "vexpand", TRUE, + "valign", GTK_ALIGN_FILL, + "visible", TRUE, + NULL); + + grid = GTK_GRID (gtk_grid_new ()); + g_object_set (G_OBJECT (grid), + "hexpand", TRUE, + "halign", GTK_ALIGN_FILL, + "vexpand", TRUE, + "valign", GTK_ALIGN_FILL, + "visible", TRUE, + "border-width", 12, + "row-spacing", 6, + "column-spacing", 6, + NULL); + + widget = gtk_frame_new (_("User details")); + g_object_set (G_OBJECT (widget), + "hexpand", FALSE, + "halign", GTK_ALIGN_CENTER, + "vexpand", FALSE, + "valign", GTK_ALIGN_START, + "visible", TRUE, + NULL); + + gtk_container_add (GTK_CONTAINER (widget), GTK_WIDGET (grid)); + + gtk_box_pack_start (vbox, widget, FALSE, FALSE, 0); + + label = gtk_label_new_with_mnemonic (_("_Email Address or User name:")); + g_object_set (G_OBJECT (label), + "hexpand", FALSE, + "halign", GTK_ALIGN_END, + "vexpand", FALSE, + "valign", GTK_ALIGN_CENTER, + "visible", TRUE, + NULL); + + widget = gtk_entry_new (); + g_object_set (G_OBJECT (widget), + "hexpand", FALSE, + "halign", GTK_ALIGN_START, + "vexpand", FALSE, + "valign", GTK_ALIGN_CENTER, + "visible", TRUE, + NULL); + wizard->priv->email_entry = widget; + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget); + + gtk_grid_attach (grid, label, 0, 0, 1, 1); + gtk_grid_attach (grid, widget, 1, 0, 1, 1); + + g_signal_connect_swapped (wizard->priv->email_entry, "changed", + G_CALLBACK (collection_account_wizard_notify_can_run), wizard); + + g_signal_connect_swapped (wizard->priv->email_entry, "changed", + G_CALLBACK (collection_account_wizard_mark_changed), wizard); + + expander = gtk_expander_new_with_mnemonic (_("_Advanced Options")); + gtk_widget_show (expander); + wizard->priv->advanced_expander = expander; + gtk_grid_attach (grid, expander, 0, 1, 2, 1); + + label = gtk_label_new_with_mnemonic (_("_Server:")); + g_object_set (G_OBJECT (label), + "hexpand", FALSE, + "halign", GTK_ALIGN_END, + "vexpand", FALSE, + "valign", GTK_ALIGN_CENTER, + "visible", FALSE, + NULL); + + widget = gtk_entry_new (); + g_object_set (G_OBJECT (widget), + "hexpand", FALSE, + "halign", GTK_ALIGN_START, + "vexpand", FALSE, + "valign", GTK_ALIGN_CENTER, + "visible", FALSE, + NULL); + wizard->priv->servers_entry = widget; + gtk_widget_set_tooltip_text (widget, _("Semicolon (“;”) separated list of servers to look up information for, in addition to the domain of the e-mail address.")); + + g_signal_connect_swapped (wizard->priv->servers_entry, "changed", + G_CALLBACK (collection_account_wizard_mark_changed), wizard); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget); + + gtk_grid_attach (grid, label, 0, 2, 1, 1); + gtk_grid_attach (grid, widget, 1, 2, 1, 1); + + e_binding_bind_property (expander, "expanded", label, "visible", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + e_binding_bind_property (expander, "expanded", widget, "visible", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + + widget = gtk_scrolled_window_new (NULL, NULL); + g_object_set (G_OBJECT (widget), + "hexpand", TRUE, + "halign", GTK_ALIGN_FILL, + "vexpand", TRUE, + "valign", GTK_ALIGN_FILL, + "visible", TRUE, + "hscrollbar-policy", GTK_POLICY_AUTOMATIC, + "vscrollbar-policy", GTK_POLICY_AUTOMATIC, + NULL); + + gtk_box_pack_start (vbox, widget, TRUE, TRUE, 0); + + label = gtk_label_new (""); + g_object_set (G_OBJECT (label), + "hexpand", FALSE, + "halign", GTK_ALIGN_START, + "vexpand", FALSE, + "valign", GTK_ALIGN_CENTER, + "margin-start", 12, + "margin-top", 12, + "visible", TRUE, + "max-width-chars", 120, + "wrap", TRUE, + NULL); + + gtk_box_pack_start (vbox, label, FALSE, FALSE, 0); + + wizard->priv->results_label = label; + + gtk_notebook_append_page (GTK_NOTEBOOK (wizard), GTK_WIDGET (vbox), NULL); + + vbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 4)); + g_object_set (G_OBJECT (vbox), + "hexpand", TRUE, + "halign", GTK_ALIGN_FILL, + "vexpand", FALSE, + "valign", GTK_ALIGN_START, + "visible", TRUE, + NULL); + + gtk_container_add (GTK_CONTAINER (widget), GTK_WIDGET (vbox)); + + workers = e_config_lookup_dup_registered_workers (wizard->priv->config_lookup); + + for (link = workers; link; link = g_slist_next (link)) { + EConfigLookupWorker *worker = link->data; + WorkerData *wd; + + if (!worker) + continue; + + wd = g_new0 (WorkerData, 1); + wd->remember_password = TRUE; + + widget = gtk_check_button_new_with_label (e_config_lookup_worker_get_display_name (worker)); + g_object_set (G_OBJECT (widget), + "hexpand", TRUE, + "halign", GTK_ALIGN_FILL, + "vexpand", FALSE, + "valign", GTK_ALIGN_CENTER, + "margin-start", 12, + "visible", TRUE, + "active", TRUE, + NULL); + wd->enabled_check = widget; + + g_signal_connect_swapped (wd->enabled_check, "toggled", + G_CALLBACK (collection_account_wizard_notify_can_run), wizard); + + g_signal_connect_swapped (wd->enabled_check, "toggled", + G_CALLBACK (collection_account_wizard_mark_changed), wizard); + + gtk_box_pack_start (vbox, widget, TRUE, TRUE, 0); + + widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + g_object_set (G_OBJECT (widget), + "hexpand", FALSE, + "halign", GTK_ALIGN_START, + "vexpand", FALSE, + "valign", GTK_ALIGN_START, + "margin-start", 12, + "visible", TRUE, + NULL); + gtk_box_pack_start (vbox, widget, TRUE, TRUE, 0); + + e_binding_bind_property (wd->enabled_check, "active", widget, "visible", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + + hbox = GTK_BOX (widget); + + /* spacer */ + widget = gtk_label_new (""); + g_object_set (G_OBJECT (widget), + "hexpand", FALSE, + "halign", GTK_ALIGN_START, + "vexpand", FALSE, + "valign", GTK_ALIGN_START, + "margin-start", 12, + "visible", TRUE, + NULL); + gtk_box_pack_start (hbox, widget, FALSE, FALSE, 0); + + widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + g_object_set (G_OBJECT (widget), + "hexpand", FALSE, + "halign", GTK_ALIGN_START, + "vexpand", FALSE, + "valign", GTK_ALIGN_START, + "margin-start", 12, + "visible", FALSE, + NULL); + wd->running_box = widget; + + gtk_box_pack_start (hbox, widget, TRUE, TRUE, 0); + + hbox = GTK_BOX (widget); + + widget = e_spinner_new (); + g_object_set (G_OBJECT (widget), + "hexpand", FALSE, + "halign", GTK_ALIGN_START, + "vexpand", FALSE, + "valign", GTK_ALIGN_CENTER, + "visible", TRUE, + NULL); + wd->spinner = widget; + + gtk_box_pack_start (hbox, widget, FALSE, FALSE, 0); + + label = gtk_label_new (NULL); + g_object_set (G_OBJECT (label), + "hexpand", FALSE, + "halign", GTK_ALIGN_START, + "vexpand", FALSE, + "valign", GTK_ALIGN_CENTER, + "visible", TRUE, + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + wd->running_label = label; + + gtk_box_pack_start (hbox, label, FALSE, FALSE, 0); + + g_signal_connect (wd->running_label, "activate-link", + G_CALLBACK (collection_account_wizard_activate_link_cb), wizard); + + e_binding_bind_property (wd->enabled_check, "sensitive", wd->running_label, "sensitive", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + + widget = e_dialog_button_new_with_icon ("process-stop", _("_Cancel")); + g_object_set (G_OBJECT (widget), + "hexpand", FALSE, + "halign", GTK_ALIGN_START, + "vexpand", FALSE, + "valign", GTK_ALIGN_CENTER, + "visible", TRUE, + NULL); + wd->cancel_button = widget; + + g_signal_connect (wd->cancel_button, "clicked", + G_CALLBACK (collection_account_wizard_worker_cancel_clicked_cb), wd); + + gtk_box_pack_start (hbox, widget, FALSE, FALSE, 0); + + g_hash_table_insert (wizard->priv->workers, g_object_ref (worker), wd); + } + + g_slist_free_full (workers, g_object_unref); + + /* Parts page */ + + vbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 6)); + g_object_set (G_OBJECT (vbox), + "hexpand", TRUE, + "halign", GTK_ALIGN_FILL, + "vexpand", TRUE, + "valign", GTK_ALIGN_FILL, + "visible", TRUE, + "margin-bottom", 12, + NULL); + + label = gtk_label_new (_("Select which parts should be configured:")); + g_object_set (G_OBJECT (label), + "hexpand", FALSE, + "halign", GTK_ALIGN_START, + "vexpand", FALSE, + "valign", GTK_ALIGN_CENTER, + "visible", TRUE, + NULL); + + gtk_box_pack_start (vbox, label, FALSE, FALSE, 0); + + widget = gtk_scrolled_window_new (NULL, NULL); + g_object_set (G_OBJECT (widget), + "hexpand", TRUE, + "halign", GTK_ALIGN_FILL, + "vexpand", TRUE, + "valign", GTK_ALIGN_FILL, + "visible", TRUE, + "margin-bottom", TRUE, + "hscrollbar-policy", GTK_POLICY_AUTOMATIC, + "vscrollbar-policy", GTK_POLICY_AUTOMATIC, + NULL); + + scrolled_window = widget; + + gtk_box_pack_start (vbox, widget, TRUE, TRUE, 0); + + tree_store = gtk_tree_store_new (PART_N_COLUMNS, + G_TYPE_BOOLEAN, /* PART_COLUMN_BOOL_ENABLED */ + G_TYPE_BOOLEAN, /* PART_COLUMN_BOOL_ENABLED_VISIBLE */ + G_TYPE_BOOLEAN, /* PART_COLUMN_BOOL_RADIO */ + G_TYPE_BOOLEAN, /* PART_COLUMN_BOOL_SENSITIVE */ + G_TYPE_BOOLEAN, /* PART_COLUMN_BOOL_IS_COLLECTION_GROUP */ + G_TYPE_BOOLEAN, /* PART_COLUMN_BOOL_ICON_VISIBLE */ + G_TYPE_STRING, /* PART_COLUMN_STRING_ICON_NAME */ + G_TYPE_STRING, /* PART_COLUMN_STRING_DESCRIPTION */ + G_TYPE_STRING, /* PART_COLUMN_STRING_PROTOCOL */ + E_TYPE_CONFIG_LOOKUP_RESULT); /* PART_COLUMN_OBJECT_RESULT */ + + widget = gtk_tree_view_new_with_model (GTK_TREE_MODEL (tree_store)); + g_object_set (G_OBJECT (widget), + "hexpand", TRUE, + "halign", GTK_ALIGN_FILL, + "vexpand", TRUE, + "valign", GTK_ALIGN_FILL, + "visible", TRUE, + NULL); + g_object_unref (tree_store); + wizard->priv->parts_tree_view = GTK_TREE_VIEW (widget); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget); + + gtk_container_add (GTK_CONTAINER (scrolled_window), widget); + + gtk_notebook_append_page (GTK_NOTEBOOK (wizard), GTK_WIDGET (vbox), NULL); + + /* Column: Description */ + + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_expand (column, TRUE); + gtk_tree_view_column_set_title (column, _("Description")); + + cell_renderer = gtk_cell_renderer_toggle_new (); + gtk_tree_view_column_pack_start (column, cell_renderer, FALSE); + + g_signal_connect (cell_renderer, "toggled", + G_CALLBACK (collection_account_wizard_part_enabled_toggled_cb), wizard); + + gtk_tree_view_column_set_attributes (column, cell_renderer, + "sensitive", PART_COLUMN_BOOL_SENSITIVE, + "active", PART_COLUMN_BOOL_ENABLED, + "visible", PART_COLUMN_BOOL_ENABLED_VISIBLE, + "radio", PART_COLUMN_BOOL_RADIO, + NULL); + + cell_renderer = gtk_cell_renderer_pixbuf_new (); + g_object_set (cell_renderer, "stock-size", GTK_ICON_SIZE_MENU, NULL); + gtk_tree_view_column_pack_start (column, cell_renderer, FALSE); + + gtk_tree_view_column_set_attributes (column, cell_renderer, + "sensitive", PART_COLUMN_BOOL_SENSITIVE, + "icon-name", PART_COLUMN_STRING_ICON_NAME, + "visible", PART_COLUMN_BOOL_ICON_VISIBLE, + NULL); + + cell_renderer = gtk_cell_renderer_text_new (); + g_object_set (cell_renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL); + gtk_tree_view_column_pack_start (column, cell_renderer, FALSE); + + gtk_tree_view_column_set_attributes (column, cell_renderer, + "sensitive", PART_COLUMN_BOOL_SENSITIVE, + "markup", PART_COLUMN_STRING_DESCRIPTION, + NULL); + + gtk_tree_view_append_column (wizard->priv->parts_tree_view, column); + gtk_tree_view_set_expander_column (wizard->priv->parts_tree_view, column); + + /* Column: Type */ + + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_expand (column, FALSE); + gtk_tree_view_column_set_title (column, _("Type")); + + cell_renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell_renderer, TRUE); + + gtk_tree_view_column_set_attributes (column, cell_renderer, + "sensitive", PART_COLUMN_BOOL_SENSITIVE, + "text", PART_COLUMN_STRING_PROTOCOL, + NULL); + + gtk_tree_view_append_column (wizard->priv->parts_tree_view, column); + + /* Finish page */ + + grid = GTK_GRID (gtk_grid_new ()); + g_object_set (G_OBJECT (grid), + "hexpand", TRUE, + "halign", GTK_ALIGN_FILL, + "vexpand", TRUE, + "valign", GTK_ALIGN_FILL, + "visible", TRUE, + "column-spacing", 4, + NULL); + markup = g_markup_printf_escaped ("%s", _("Account Information")); + widget = gtk_label_new (markup); + gtk_label_set_use_markup (GTK_LABEL (widget), TRUE); + gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5); + gtk_widget_set_margin_bottom (widget, 12); + gtk_grid_attach (grid, widget, 0, 0, 2, 1); + gtk_widget_show (widget); + g_free (markup); + + label = gtk_label_new_with_mnemonic (_("_Name:")); + gtk_widget_set_margin_left (label, 12); + gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5); + gtk_grid_attach (grid, label, 0, 1, 1, 1); + gtk_widget_show (label); + + widget = gtk_entry_new (); + gtk_widget_set_hexpand (widget, TRUE); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget); + gtk_grid_attach (grid, widget, 1, 1, 1, 1); + gtk_widget_show (widget); + + wizard->priv->display_name_entry = widget; + + g_signal_connect_swapped (wizard->priv->display_name_entry, "changed", + G_CALLBACK (collection_account_wizard_notify_can_run), wizard); + + widget = gtk_label_new ("The above name will be used to identify this account.\nUse for example, “Work” or “Personal”."); + gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5); + gtk_grid_attach (grid, widget, 1, 2, 1, 1); + gtk_widget_show (widget); + + vbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 6)); + g_object_set (G_OBJECT (vbox), + "hexpand", TRUE, + "halign", GTK_ALIGN_FILL, + "vexpand", FALSE, + "valign", GTK_ALIGN_START, + "visible", TRUE, + NULL); + + gtk_box_pack_end (vbox, GTK_WIDGET (grid), FALSE, FALSE, 0); + + hbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4)); + g_object_set (G_OBJECT (hbox), + "hexpand", FALSE, + "halign", GTK_ALIGN_START, + "vexpand", FALSE, + "valign", GTK_ALIGN_START, + "margin-start", 12, + "visible", FALSE, + NULL); + wizard->priv->finish_running_box = GTK_WIDGET (hbox); + + widget = e_spinner_new (); + g_object_set (G_OBJECT (widget), + "hexpand", FALSE, + "halign", GTK_ALIGN_START, + "vexpand", FALSE, + "valign", GTK_ALIGN_CENTER, + "visible", TRUE, + NULL); + wizard->priv->finish_spinner = widget; + + gtk_box_pack_start (hbox, widget, FALSE, FALSE, 0); + + label = gtk_label_new (NULL); + g_object_set (G_OBJECT (label), + "hexpand", FALSE, + "halign", GTK_ALIGN_START, + "vexpand", FALSE, + "valign", GTK_ALIGN_CENTER, + "visible", TRUE, + "max-width-chars", 120, + "wrap", TRUE, + NULL); + wizard->priv->finish_label = label; + + gtk_box_pack_start (hbox, label, FALSE, FALSE, 0); + + widget = e_dialog_button_new_with_icon ("process-stop", _("_Cancel")); + g_object_set (G_OBJECT (widget), + "hexpand", FALSE, + "halign", GTK_ALIGN_START, + "vexpand", FALSE, + "valign", GTK_ALIGN_CENTER, + "visible", TRUE, + NULL); + wizard->priv->finish_cancel_button = widget; + + g_signal_connect (wizard->priv->finish_cancel_button, "clicked", + G_CALLBACK (collection_account_wizard_finish_cancel_clicked_cb), wizard); + + gtk_box_pack_start (hbox, widget, FALSE, FALSE, 0); + + gtk_notebook_append_page (GTK_NOTEBOOK (wizard), GTK_WIDGET (vbox), NULL); + + gtk_notebook_set_current_page (GTK_NOTEBOOK (wizard), 0); +} + +static void +collection_account_wizard_dispose (GObject *object) +{ + ECollectionAccountWizard *wizard = E_COLLECTION_ACCOUNT_WIZARD (object); + gint ii; + + g_clear_object (&wizard->priv->registry); + g_clear_object (&wizard->priv->config_lookup); + g_clear_object (&wizard->priv->finish_cancellable); + + if (wizard->priv->workers) { + g_hash_table_destroy (wizard->priv->workers); + wizard->priv->workers = NULL; + } + + if (wizard->priv->store_passwords) { + g_hash_table_destroy (wizard->priv->store_passwords); + wizard->priv->store_passwords = NULL; + } + + g_warn_if_fail (wizard->priv->running_result == NULL); + + if (wizard->priv->running_result) { + e_simple_async_result_complete_idle (wizard->priv->running_result); + g_clear_object (&wizard->priv->running_result); + } + + for (ii = 0; ii <= E_CONFIG_LOOKUP_RESULT_LAST_KIND; ii++) { + g_clear_object (&wizard->priv->sources[ii]); + } + + /* Chain up to parent's method. */ + G_OBJECT_CLASS (e_collection_account_wizard_parent_class)->dispose (object); +} + +static void +e_collection_account_wizard_class_init (ECollectionAccountWizardClass *klass) +{ + GObjectClass *object_class; + + g_type_class_add_private (klass, sizeof (ECollectionAccountWizardPrivate)); + + object_class = G_OBJECT_CLASS (klass); + object_class->set_property = collection_account_wizard_set_property; + object_class->get_property = collection_account_wizard_get_property; + object_class->constructed = collection_account_wizard_constructed; + object_class->dispose = collection_account_wizard_dispose; + + /** + * ECollectionAccountWizard:registry: + * + * The #ESourceRegistry manages #ESource instances. + * + * Since: 3.28 + **/ + g_object_class_install_property ( + object_class, + PROP_REGISTRY, + g_param_spec_object ( + "registry", + "Registry", + "Data source registry", + E_TYPE_SOURCE_REGISTRY, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * ECollectionAccountWizard:can-run: + * + * Whether can call e_collection_account_wizard_run(). + * See e_collection_account_wizard_get_can_run() for more information. + * + * Since: 3.28 + **/ + g_object_class_install_property ( + object_class, + PROP_CAN_RUN, + g_param_spec_boolean ( + "can-run", + "Can Run", + NULL, + FALSE, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + + /** + * ECollectionAccountWizard::done: + * @uid: an #ESource UID which had been created + * + * Emitted to notify about the wizard being done. + * + * Since: 3.28 + **/ + signals[DONE] = g_signal_new ( + "done", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (ECollectionAccountWizardClass, done), + NULL, NULL, + NULL, + G_TYPE_NONE, 1, G_TYPE_STRING); +} + +static void +e_collection_account_wizard_init (ECollectionAccountWizard *wizard) +{ + gint ii; + + wizard->priv = G_TYPE_INSTANCE_GET_PRIVATE (wizard, E_TYPE_COLLECTION_ACCOUNT_WIZARD, ECollectionAccountWizardPrivate); + wizard->priv->workers = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, worker_data_free); + wizard->priv->store_passwords = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + wizard->priv->running_workers = 0; + + for (ii = 0; ii <= E_CONFIG_LOOKUP_RESULT_LAST_KIND; ii++) { + wizard->priv->sources[ii] = NULL; + } +} + +/** + * e_collection_account_wizard_new: + * @registry: an #ESourceRegistry + * + * Creates a new #ECollectionAccountWizard instance. + * + * Returns: (transfer full): a new #ECollectionAccountWizard + * + * Since: 3.28 + **/ +GtkWidget * +e_collection_account_wizard_new (ESourceRegistry *registry) +{ + g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); + + return g_object_new (E_TYPE_COLLECTION_ACCOUNT_WIZARD, + "registry", registry, + NULL); +} + +/** + * e_collection_account_wizard_get_registry: + * @wizard: an #ECollectionAccountWizard + * + * Returns the #ESourceRegistry passed to e_collection_account_wizard_new(). + * + * Returns: (transfer none): an #ESourceRegistry + * + * Since: 3.28 + **/ +ESourceRegistry * +e_collection_account_wizard_get_registry (ECollectionAccountWizard *wizard) +{ + g_return_val_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard), NULL); + + return wizard->priv->registry; +} + +/** + * e_collection_account_wizard_get_can_run: + * @wizard: an #ECollectionAccountWizard + * + * Returns whether e_collection_account_wizard_run() can be called, that is, + * whether at least one worker is enabled to run and the @wizard is not + * running. + * + * Returns: whether e_collection_account_wizard_run() can be called. + * + * Since: 3.28 + **/ +gboolean +e_collection_account_wizard_get_can_run (ECollectionAccountWizard *wizard) +{ + GHashTableIter iter; + gpointer value; + const gchar *email; + gint current_page; + + g_return_val_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard), FALSE); + + if (wizard->priv->running_workers || + wizard->priv->running_result || + wizard->priv->finish_cancellable) + return FALSE; + + email = gtk_entry_get_text (GTK_ENTRY (wizard->priv->email_entry)); + if (!email || !*email) + return FALSE; + + current_page = gtk_notebook_get_current_page (GTK_NOTEBOOK (wizard)); + + if (current_page == 1) { /* Parts page */ + GtkTreeModel *model; + GtkTreeIter iter; + + model = gtk_tree_view_get_model (wizard->priv->parts_tree_view); + if (gtk_tree_model_get_iter_first (model, &iter)) { + do { + gboolean enabled = FALSE, is_collection_group = FALSE; + + gtk_tree_model_get (model, &iter, + PART_COLUMN_BOOL_ENABLED, &enabled, + PART_COLUMN_BOOL_IS_COLLECTION_GROUP, &is_collection_group, + -1); + + if (enabled && is_collection_group) { + /* Collection is not with radio, verify at least one child is selected */ + GtkTreeIter child; + + if (gtk_tree_model_iter_nth_child (model, &child, &iter, 0)) { + do { + enabled = FALSE; + + gtk_tree_model_get (model, &child, + PART_COLUMN_BOOL_ENABLED, &enabled, + -1); + + if (enabled) + return TRUE; + } while (gtk_tree_model_iter_next (model, &child)); + } + } else if (enabled) { + return TRUE; + } + } while (gtk_tree_model_iter_next (model, &iter)); + } + + return FALSE; + } else if (current_page == 2) { /* Finish page */ + gchar *display_name; + gboolean can_run; + + display_name = g_strdup (gtk_entry_get_text (GTK_ENTRY (wizard->priv->display_name_entry))); + if (!display_name) + return FALSE; + + g_strstrip (display_name); + + can_run = display_name && *display_name; + + g_free (display_name); + + return can_run; + } + + /* Look up page */ + + g_hash_table_iter_init (&iter, wizard->priv->workers); + + while (g_hash_table_iter_next (&iter, NULL, &value)) { + WorkerData *wd = value; + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (wd->enabled_check))) + return TRUE; + } + + return FALSE; +} + +/** + * e_collection_account_wizard_reset: + * @wizard: an #ECollectionAccountWizard + * + * Resets content of the @wizard to the initial state. This might be called + * whenever the widget is going to be shown. + * + * Since: 3.28 + **/ +void +e_collection_account_wizard_reset (ECollectionAccountWizard *wizard) +{ + GtkTreeModel *model; + GHashTableIter iter; + gpointer value; + gint ii; + + g_return_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard)); + + e_collection_account_wizard_abort (wizard); + + g_hash_table_iter_init (&iter, wizard->priv->workers); + + while (g_hash_table_iter_next (&iter, NULL, &value)) { + WorkerData *wd = value; + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wd->enabled_check), TRUE); + gtk_widget_hide (wd->running_box); + e_named_parameters_free (wd->restart_params); + wd->restart_params = NULL; + wd->remember_password = TRUE; + } + + gtk_entry_set_text (GTK_ENTRY (wizard->priv->email_entry), ""); + gtk_entry_set_text (GTK_ENTRY (wizard->priv->servers_entry), ""); + gtk_label_set_text (GTK_LABEL (wizard->priv->results_label), ""); + gtk_entry_set_text (GTK_ENTRY (wizard->priv->display_name_entry), ""); + gtk_expander_set_expanded (GTK_EXPANDER (wizard->priv->advanced_expander), FALSE); + e_config_lookup_clear_results (wizard->priv->config_lookup); + + model = gtk_tree_view_get_model (wizard->priv->parts_tree_view); + gtk_tree_store_clear (GTK_TREE_STORE (model)); + + wizard->priv->changed = FALSE; + + for (ii = 0; ii <= E_CONFIG_LOOKUP_RESULT_LAST_KIND; ii++) { + g_clear_object (&wizard->priv->sources[ii]); + } + + gtk_notebook_set_current_page (GTK_NOTEBOOK (wizard), 0); + + g_object_notify (G_OBJECT (wizard), "can-run"); +} + +/** + * e_collection_account_wizard_next: + * @wizard: an #ECollectionAccountWizard + * + * Instructs the @wizard to advance to the next step. It does nothing + * when there is an ongoing lookup or when the current page cannot + * be advanced. + * + * This can influence e_collection_account_wizard_is_finish_page(). + * + * Returns: %TRUE, when the step had been changed, %FALSE otherwise. + * Note that when this is called on a finish page, then the %TRUE + * means that the @wizard finished all its settings and should be + * closed now. + * + * Since: 3.28 + **/ +gboolean +e_collection_account_wizard_next (ECollectionAccountWizard *wizard) +{ + gboolean changed = FALSE; + const gchar *text; + gint ii; + + g_return_val_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard), FALSE); + + switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (wizard))) { + case 0: /* Lookup page */ + if (wizard->priv->changed || + !e_config_lookup_count_results (wizard->priv->config_lookup)) { + for (ii = 0; ii <= E_CONFIG_LOOKUP_RESULT_LAST_KIND; ii++) { + g_clear_object (&wizard->priv->sources[ii]); + } + + wizard->priv->sources[E_CONFIG_LOOKUP_RESULT_COLLECTION] = e_source_new (NULL, NULL, NULL); + e_source_get_extension (wizard->priv->sources[E_CONFIG_LOOKUP_RESULT_COLLECTION], E_SOURCE_EXTENSION_COLLECTION); + + e_collection_account_wizard_run (wizard, NULL, NULL); + changed = TRUE; + } else if (collection_account_wizard_fill_results (wizard)) { + gtk_notebook_set_current_page (GTK_NOTEBOOK (wizard), 1); + changed = TRUE; + } + break; + case 1: /* Parts page */ + g_warn_if_fail (wizard->priv->sources[E_CONFIG_LOOKUP_RESULT_COLLECTION] != NULL); + + text = gtk_entry_get_text (GTK_ENTRY (wizard->priv->display_name_entry)); + if (!text || !*text) { + gtk_entry_set_text (GTK_ENTRY (wizard->priv->display_name_entry), + gtk_entry_get_text (GTK_ENTRY (wizard->priv->email_entry))); + } + + gtk_notebook_set_current_page (GTK_NOTEBOOK (wizard), 2); + changed = TRUE; + break; + case 2: /* Finish page */ + /* It's an asynchronous operation, just fail or run it */ + collection_account_wizard_save_sources (wizard); + changed = FALSE; + break; + } + + return changed; +} + +/** + * e_collection_account_wizard_prev: + * @wizard: an #ECollectionAccountWizard + * + * Instructs the @wizard to go back to the previous step. + * + * This can influence e_collection_account_wizard_is_finish_page(). + * + * Returns: %TRUE, when the step had been changed, %FALSE otherwise. + * + * Since: 3.28 + **/ +gboolean +e_collection_account_wizard_prev (ECollectionAccountWizard *wizard) +{ + gint current_page; + + g_return_val_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard), FALSE); + + current_page = gtk_notebook_get_current_page (GTK_NOTEBOOK (wizard)); + + if (current_page < 1) + return FALSE; + + gtk_notebook_set_current_page (GTK_NOTEBOOK (wizard), current_page - 1); + + return TRUE; +} + +/** + * e_collection_account_wizard_is_finish_page: + * @wizard: an #ECollectionAccountWizard + * + * Returns: whether the @wizard is at the last page. + * + * Since: 3.28 + **/ +gboolean +e_collection_account_wizard_is_finish_page (ECollectionAccountWizard *wizard) +{ + g_return_val_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard), FALSE); + + return gtk_notebook_get_current_page (GTK_NOTEBOOK (wizard)) == gtk_notebook_get_n_pages (GTK_NOTEBOOK (wizard)) - 1; +} + +/** + * e_collection_account_wizard_run: + * @wizard: an #ECollectionAccountWizard + * @callback: a callback to call, when the run is finished + * @user_data: user data for the @callback + * + * Runs lookup for all enabled lookup workers. Finish the call + * with e_collection_account_wizard_run_finish() from the @callback. + * + * This function can be called only if e_collection_account_wizard_get_can_run() + * returns %TRUE. + * + * Since: 3.28 + **/ +void +e_collection_account_wizard_run (ECollectionAccountWizard *wizard, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GHashTableIter iter; + gpointer key, value; + gboolean any_worker = FALSE; + + g_return_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard)); + g_return_if_fail (e_collection_account_wizard_get_can_run (wizard)); + + wizard->priv->running_result = e_simple_async_result_new (G_OBJECT (wizard), callback, user_data, e_collection_account_wizard_run); + + g_hash_table_iter_init (&iter, wizard->priv->workers); + while (g_hash_table_iter_next (&iter, &key, &value)) { + EConfigLookupWorker *worker = key; + WorkerData *wd = value; + + if (worker && wd && + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (wd->enabled_check))) { + ENamedParameters *params; + + params = e_named_parameters_new_clone (wd->restart_params); + if (!params) + params = e_named_parameters_new (); + e_named_parameters_set (params, E_CONFIG_LOOKUP_PARAM_EMAIL_ADDRESS, gtk_entry_get_text (GTK_ENTRY (wizard->priv->email_entry))); + e_named_parameters_set (params, E_CONFIG_LOOKUP_PARAM_SERVERS, gtk_entry_get_text (GTK_ENTRY (wizard->priv->servers_entry))); + + any_worker = TRUE; + e_config_lookup_run_worker (wizard->priv->config_lookup, worker, params, NULL); + + e_named_parameters_free (params); + } + } + + if (!any_worker) { + e_simple_async_result_complete_idle (wizard->priv->running_result); + g_clear_object (&wizard->priv->running_result); + } +} + +/** + * e_collection_account_wizard_run_finish: + * @wizard: an #ECollectionAccountWizard + * @result: result of the operation + * + * Finishes the wizard run issued by e_collection_account_wizard_run(). + * It doesn't return anything, because everything is handled within + * the @wizard, thus it is provided mainly for consistency with asynchronous API. + * + * Since: 3.28 + **/ +void +e_collection_account_wizard_run_finish (ECollectionAccountWizard *wizard, + GAsyncResult *result) +{ + g_return_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard)); + g_return_if_fail (G_IS_ASYNC_RESULT (result)); + g_return_if_fail (g_async_result_is_tagged (result, e_collection_account_wizard_run)); +} + +/** + * e_collection_account_wizard_abort: + * @wizard: an #ECollectionAccountWizard + * + * Aborts any ongoing operation the @wizard may run. If there is nothing + * running, then does nothing. + * + * Since: 3.28 + **/ +void +e_collection_account_wizard_abort (ECollectionAccountWizard *wizard) +{ + g_return_if_fail (E_IS_COLLECTION_ACCOUNT_WIZARD (wizard)); + + e_config_lookup_cancel_all (wizard->priv->config_lookup); + + if (wizard->priv->finish_cancellable) + g_cancellable_cancel (wizard->priv->finish_cancellable); +} diff --git a/src/e-util/e-config-lookup.c b/src/e-util/e-config-lookup.c index d0eff38..3b68fa6 100644 --- a/src/e-util/e-config-lookup.c +++ b/src/e-util/e-config-lookup.c @@ -277,6 +277,11 @@ config_lookup_dispose (GObject *object) e_config_lookup_cancel_all (config_lookup); + if (config_lookup->priv->pool) { + g_thread_pool_free (config_lookup->priv->pool, TRUE, TRUE); + config_lookup->priv->pool = NULL; + } + g_mutex_lock (&config_lookup->priv->property_lock); g_clear_object (&config_lookup->priv->run_cancellable); @@ -305,7 +310,6 @@ config_lookup_finalize (GObject *object) EConfigLookup *config_lookup = E_CONFIG_LOOKUP (object); g_slist_free_full (config_lookup->priv->results, g_object_unref); - g_thread_pool_free (config_lookup->priv->pool, TRUE, FALSE); g_mutex_clear (&config_lookup->priv->property_lock); /* Chain up to parent's method. */ diff --git a/src/e-util/e-config-lookup.c.config-lookup-crash b/src/e-util/e-config-lookup.c.config-lookup-crash new file mode 100644 index 0000000..d0eff38 --- /dev/null +++ b/src/e-util/e-config-lookup.c.config-lookup-crash @@ -0,0 +1,996 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2017 Red Hat, Inc. (www.redhat.com) + * + * This library 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 library 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 this library. If not, see . + */ + +/** + * SECTION: e-config-lookup + * @include: e-util/e-util.h + * @short_description: Configuration lookup + * + * #EConfigLookup is used to search for configuration of an account, + * which is identified by an e-mail address, server address or such. + * It is an #EExtensible object, where the extensions connect to + * the #EConfigLookup::run signal to run the configuration lookup. + **/ + +#include "evolution-config.h" + +#include +#include +#include + +#include "e-config-lookup-result.h" +#include "e-config-lookup-worker.h" +#include "e-simple-async-result.h" +#include "e-util-enumtypes.h" + +#include "e-config-lookup.h" + +struct _EConfigLookupPrivate { + ESourceRegistry *registry; + + GMutex property_lock; + GSList *workers; /* EConfigLookupWorker * */ + GSList *results; /* EConfigLookupResult * */ + + ESimpleAsyncResult *run_result; + GCancellable *run_cancellable; + GSList *worker_cancellables; /* CamelOperation * */ + + GThreadPool *pool; +}; + +enum { + PROP_0, + PROP_REGISTRY, + PROP_BUSY +}; + +enum { + GET_SOURCE, + WORKER_STARTED, + WORKER_FINISHED, + RESULT_ADDED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +G_DEFINE_TYPE_WITH_CODE (EConfigLookup, e_config_lookup, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL)) + +enum { + EMIT_BUSY = 1 << 0, + EMIT_WORKER_STARTED = 1 << 1, + EMIT_WORKER_FINISHED = 1 << 2 +}; + +typedef struct _EmitData { + EConfigLookup *config_lookup; + EConfigLookupWorker *worker; + guint32 flags; + GCancellable *cancellable; + ENamedParameters *params; + GError *error; +} EmitData; + +static void +emit_data_free (gpointer ptr) +{ + EmitData *ed = ptr; + + if (ed) { + e_named_parameters_free (ed->params); + g_clear_object (&ed->config_lookup); + g_clear_object (&ed->worker); + g_clear_object (&ed->cancellable); + g_clear_error (&ed->error); + g_free (ed); + } +} + +static gboolean +config_lookup_emit_idle_cb (gpointer user_data) +{ + EmitData *ed = user_data; + + g_return_val_if_fail (ed != NULL, FALSE); + g_return_val_if_fail (E_IS_CONFIG_LOOKUP (ed->config_lookup), FALSE); + + if ((ed->flags & EMIT_WORKER_STARTED) != 0) + g_signal_emit (ed->config_lookup, signals[WORKER_STARTED], 0, ed->worker, ed->cancellable); + + if ((ed->flags & EMIT_WORKER_FINISHED) != 0) + g_signal_emit (ed->config_lookup, signals[WORKER_FINISHED], 0, ed->worker, ed->params, ed->error); + + if ((ed->flags & EMIT_BUSY) != 0) + g_object_notify (G_OBJECT (ed->config_lookup), "busy"); + + return FALSE; +} + +static void +config_lookup_schedule_emit_idle (EConfigLookup *config_lookup, + guint32 emit_flags, + EConfigLookupWorker *worker, + GCancellable *cancellable, + const ENamedParameters *params, + const GError *error) +{ + EmitData *ed; + + g_return_if_fail (E_IS_CONFIG_LOOKUP (config_lookup)); + if (worker) + g_return_if_fail (E_IS_CONFIG_LOOKUP_WORKER (worker)); + + ed = g_new0 (EmitData, 1); + ed->config_lookup = g_object_ref (config_lookup); + ed->flags = emit_flags; + ed->worker = worker ? g_object_ref (worker) : NULL; + ed->cancellable = cancellable ? g_object_ref (cancellable) : NULL; + ed->params = params ? e_named_parameters_new_clone (params) : NULL; + ed->error = error ? g_error_copy (error) : NULL; + + g_idle_add_full (G_PRIORITY_HIGH_IDLE, config_lookup_emit_idle_cb, ed, emit_data_free); +} + +typedef struct _ThreadData { + ENamedParameters *params; + EConfigLookupWorker *worker; + GCancellable *cancellable; +} ThreadData; + +static void +config_lookup_thread (gpointer data, + gpointer user_data) +{ + ThreadData *td = data; + EConfigLookup *config_lookup = user_data; + ESimpleAsyncResult *run_result = NULL; + guint32 emit_flags; + ENamedParameters *restart_params = NULL; + GError *error = NULL; + + g_return_if_fail (td != NULL); + g_return_if_fail (td->params != NULL); + g_return_if_fail (E_IS_CONFIG_LOOKUP_WORKER (td->worker)); + g_return_if_fail (G_IS_CANCELLABLE (td->cancellable)); + g_return_if_fail (E_IS_CONFIG_LOOKUP (config_lookup)); + + e_config_lookup_worker_run (td->worker, config_lookup, td->params, &restart_params, td->cancellable, &error); + + g_mutex_lock (&config_lookup->priv->property_lock); + + emit_flags = EMIT_WORKER_FINISHED; + + if (g_slist_find (config_lookup->priv->worker_cancellables, td->cancellable)) { + config_lookup->priv->worker_cancellables = g_slist_remove (config_lookup->priv->worker_cancellables, td->cancellable); + g_object_unref (td->cancellable); + + if (!config_lookup->priv->worker_cancellables) + emit_flags |= EMIT_BUSY; + } + + config_lookup_schedule_emit_idle (config_lookup, emit_flags, td->worker, NULL, restart_params, error); + + if ((emit_flags & EMIT_BUSY) != 0) { + run_result = config_lookup->priv->run_result; + config_lookup->priv->run_result = NULL; + + g_clear_object (&config_lookup->priv->run_cancellable); + } + + g_mutex_unlock (&config_lookup->priv->property_lock); + + if (run_result) { + e_simple_async_result_complete_idle (run_result); + g_object_unref (run_result); + } + + e_named_parameters_free (restart_params); + e_named_parameters_free (td->params); + g_clear_object (&td->worker); + g_clear_object (&td->cancellable); + g_clear_error (&error); + g_free (td); +} + +static void +config_lookup_set_registry (EConfigLookup *config_lookup, + ESourceRegistry *registry) +{ + g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); + g_return_if_fail (config_lookup->priv->registry == NULL); + + config_lookup->priv->registry = g_object_ref (registry); +} + +static void +config_lookup_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_REGISTRY: + config_lookup_set_registry ( + E_CONFIG_LOOKUP (object), + g_value_get_object (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +config_lookup_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_BUSY: + g_value_set_boolean ( + value, + e_config_lookup_get_busy ( + E_CONFIG_LOOKUP (object))); + return; + + case PROP_REGISTRY: + g_value_set_object ( + value, + e_config_lookup_get_registry ( + E_CONFIG_LOOKUP (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +config_lookup_constructed (GObject *object) +{ + /* Chain up to parent's method. */ + G_OBJECT_CLASS (e_config_lookup_parent_class)->constructed (object); + + e_extensible_load_extensions (E_EXTENSIBLE (object)); +} + +static void +config_lookup_dispose (GObject *object) +{ + EConfigLookup *config_lookup = E_CONFIG_LOOKUP (object); + gboolean had_running_workers; + + e_config_lookup_cancel_all (config_lookup); + + g_mutex_lock (&config_lookup->priv->property_lock); + + g_clear_object (&config_lookup->priv->run_cancellable); + + g_slist_free_full (config_lookup->priv->workers, g_object_unref); + config_lookup->priv->workers = NULL; + + had_running_workers = config_lookup->priv->worker_cancellables != NULL; + g_slist_free_full (config_lookup->priv->worker_cancellables, g_object_unref); + config_lookup->priv->worker_cancellables = NULL; + + g_mutex_unlock (&config_lookup->priv->property_lock); + + if (had_running_workers) + g_object_notify (object, "busy"); + + g_clear_object (&config_lookup->priv->registry); + + /* Chain up to parent's method. */ + G_OBJECT_CLASS (e_config_lookup_parent_class)->dispose (object); +} + +static void +config_lookup_finalize (GObject *object) +{ + EConfigLookup *config_lookup = E_CONFIG_LOOKUP (object); + + g_slist_free_full (config_lookup->priv->results, g_object_unref); + g_thread_pool_free (config_lookup->priv->pool, TRUE, FALSE); + g_mutex_clear (&config_lookup->priv->property_lock); + + /* Chain up to parent's method. */ + G_OBJECT_CLASS (e_config_lookup_parent_class)->finalize (object); +} + +static void +e_config_lookup_class_init (EConfigLookupClass *klass) +{ + GObjectClass *object_class; + + g_type_class_add_private (klass, sizeof (EConfigLookupPrivate)); + + object_class = G_OBJECT_CLASS (klass); + object_class->set_property = config_lookup_set_property; + object_class->get_property = config_lookup_get_property; + object_class->constructed = config_lookup_constructed; + object_class->dispose = config_lookup_dispose; + object_class->finalize = config_lookup_finalize; + + /** + * EConfigLookup:registry: + * + * The #ESourceRegistry manages #ESource instances. + * + * Since: 3.26 + **/ + g_object_class_install_property ( + object_class, + PROP_REGISTRY, + g_param_spec_object ( + "registry", + "Registry", + "Data source registry", + E_TYPE_SOURCE_REGISTRY, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * EConfigLookup:busy: + * + * Whether the EConfigLookup has any running workers. + * + * Since: 3.28 + **/ + g_object_class_install_property ( + object_class, + PROP_BUSY, + g_param_spec_boolean ( + "busy", + "Busy", + NULL, + FALSE, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + + /** + * EConfigLookup::get-source: + * @kind: an #EConfigLookupSourceKind + * + * Emitted to get an #ESource of the given @kind. Return %NULL, when not available. + * + * Since: 3.26 + **/ + signals[GET_SOURCE] = g_signal_new ( + "get-source", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (EConfigLookupClass, get_source), + NULL, NULL, + NULL, + G_TYPE_POINTER, 1, + E_TYPE_CONFIG_LOOKUP_SOURCE_KIND); + + /** + * EConfigLookup::worker-started: + * @worker: an #EConfigLookupWorker + * @cancellable: associated #GCancellable for this worker run + * + * Emitted when the @worker is about to start running. + * Corresponding @EConfigLookup::worker-finished is emitted when + * the run is finished. + * + * Note that this signal is always emitted in the main thread. + * + * Since: 3.28 + **/ + signals[WORKER_STARTED] = g_signal_new ( + "worker-started", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EConfigLookupClass, worker_started), + NULL, NULL, + NULL, + G_TYPE_NONE, 2, + E_TYPE_CONFIG_LOOKUP_WORKER, + G_TYPE_CANCELLABLE); + + /** + * EConfigLookup::worker-finished: + * @worker: an #EConfigLookupWorker + * @restart_params: an optional #ENamedParameters to use when the @worker might be restarted + * @error: an optional #GError with an overall result of the run + * + * Emitted when the @worker finished its running. + * + * Note that this signal is always emitted in the main thread. + * + * Since: 3.28 + **/ + signals[WORKER_FINISHED] = g_signal_new ( + "worker-finished", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EConfigLookupClass, worker_finished), + NULL, NULL, + NULL, + G_TYPE_NONE, 3, + E_TYPE_CONFIG_LOOKUP_WORKER, + E_TYPE_NAMED_PARAMETERS, + G_TYPE_ERROR); + + /** + * EConfigLookup::result-added: + * @result: an #EConfigLookupResult + * + * Emitted when a new @result is added to the config lookup. + * + * Note that this signal can be emitted in a worker's dedicated thread. + * + * Since: 3.28 + **/ + signals[RESULT_ADDED] = g_signal_new ( + "result-added", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EConfigLookupClass, result_added), + NULL, NULL, + NULL, + G_TYPE_NONE, 1, + E_TYPE_CONFIG_LOOKUP_RESULT); +} + +static void +e_config_lookup_init (EConfigLookup *config_lookup) +{ + config_lookup->priv = G_TYPE_INSTANCE_GET_PRIVATE (config_lookup, E_TYPE_CONFIG_LOOKUP, EConfigLookupPrivate); + + g_mutex_init (&config_lookup->priv->property_lock); + config_lookup->priv->pool = g_thread_pool_new (config_lookup_thread, config_lookup, 10, FALSE, NULL); +} + +/** + * e_config_lookup_new: + * @registry: an #ESourceRegistry + * + * Creates a new #EConfigLookup instance. + * + * Returns: (transfer full): a new #EConfigLookup + * + * Since: 3.26 + **/ +EConfigLookup * +e_config_lookup_new (ESourceRegistry *registry) +{ + g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); + + return g_object_new (E_TYPE_CONFIG_LOOKUP, + "registry", registry, + NULL); +} + +/** + * e_config_lookup_get_registry: + * @config_lookup: an #EConfigLookup + * + * Returns the #ESourceRegistry passed to e_config_lookup_new(). + * + * Returns: (transfer none): an #ESourceRegistry + * + * Since: 3.26 + **/ +ESourceRegistry * +e_config_lookup_get_registry (EConfigLookup *config_lookup) +{ + g_return_val_if_fail (E_IS_CONFIG_LOOKUP (config_lookup), NULL); + + return config_lookup->priv->registry; +} + +/** + * e_config_lookup_get_source: + * @config_lookup: an #EConfigLookup + * @kind: one of #EConfigLookupSourceKind, except of the %E_CONFIG_LOOKUP_SOURCE_UNKNOWN + * + * Emits the #EConfigLookup::get-source signal and any listener can provide + * the source. The function can return %NULL, when there are no listeners + * or when such source is not available. + * + * Returns: (transfer none) (nullable): an #ESource of the given @kind, or %NULL, if not found + * + * Since: 3.26 + **/ +ESource * +e_config_lookup_get_source (EConfigLookup *config_lookup, + EConfigLookupSourceKind kind) +{ + ESource *source = NULL; + + g_return_val_if_fail (E_IS_CONFIG_LOOKUP (config_lookup), NULL); + + g_signal_emit (config_lookup, signals[GET_SOURCE], 0, kind, &source); + + return source; +} + +/** + * e_config_lookup_get_busy: + * @config_lookup: an #EConfigLookup + * + * Returns whether there's any running worker. They can be cancelled + * with e_config_lookup_cancel_all(). + * + * Returns: whether there's any running worker + * + * Since: 3.28 + **/ +gboolean +e_config_lookup_get_busy (EConfigLookup *config_lookup) +{ + gboolean busy; + + g_return_val_if_fail (E_IS_CONFIG_LOOKUP (config_lookup), FALSE); + + g_mutex_lock (&config_lookup->priv->property_lock); + busy = config_lookup->priv->worker_cancellables != NULL; + g_mutex_unlock (&config_lookup->priv->property_lock); + + return busy; +} + +/** + * e_config_lookup_cancel_all: + * @config_lookup: an #EConfigLookup + * + * Cancels all pending workers. + * + * Since: 3.28 + **/ +void +e_config_lookup_cancel_all (EConfigLookup *config_lookup) +{ + GSList *cancellables; + GCancellable *run_cancellable; + + g_return_if_fail (E_IS_CONFIG_LOOKUP (config_lookup)); + + g_mutex_lock (&config_lookup->priv->property_lock); + cancellables = g_slist_copy_deep (config_lookup->priv->worker_cancellables, (GCopyFunc) g_object_ref, NULL); + run_cancellable = config_lookup->priv->run_cancellable ? g_object_ref (config_lookup->priv->run_cancellable) : NULL; + g_mutex_unlock (&config_lookup->priv->property_lock); + + g_slist_foreach (cancellables, (GFunc) g_cancellable_cancel, NULL); + g_slist_free_full (cancellables, g_object_unref); + + if (run_cancellable) { + g_cancellable_cancel (run_cancellable); + g_object_unref (run_cancellable); + } +} + +/** + * e_config_lookup_register_worker: + * @config_lookup: an #EConfigLookup + * @worker: an #EConfigLookupWorker + * + * Registers a @worker as a worker, which can be run as part of e_config_lookup_run(). + * The function adds its own reference to @worker. + * + * Since: 3.28 + **/ +void +e_config_lookup_register_worker (EConfigLookup *config_lookup, + EConfigLookupWorker *worker) +{ + GSList *existing_worker; + + g_return_if_fail (E_IS_CONFIG_LOOKUP (config_lookup)); + g_return_if_fail (E_IS_CONFIG_LOOKUP_WORKER (worker)); + + g_mutex_lock (&config_lookup->priv->property_lock); + + existing_worker = g_slist_find (config_lookup->priv->workers, worker); + + g_warn_if_fail (existing_worker == NULL); + + if (!existing_worker) + config_lookup->priv->workers = g_slist_prepend (config_lookup->priv->workers, g_object_ref (worker)); + + g_mutex_unlock (&config_lookup->priv->property_lock); +} + +/** + * e_config_lookup_unregister_worker: + * @config_lookup: an #EConfigLookup + * @worker: an #EConfigLookupWorker + * + * Removes a @worker previously registered with e_config_lookup_register_worker(). + * + * Since: 3.28 + **/ +void +e_config_lookup_unregister_worker (EConfigLookup *config_lookup, + EConfigLookupWorker *worker) +{ + GSList *existing_worker; + + g_return_if_fail (E_IS_CONFIG_LOOKUP (config_lookup)); + g_return_if_fail (E_IS_CONFIG_LOOKUP_WORKER (worker)); + + g_mutex_lock (&config_lookup->priv->property_lock); + + existing_worker = g_slist_find (config_lookup->priv->workers, worker); + + g_warn_if_fail (existing_worker != NULL); + + if (existing_worker) { + config_lookup->priv->workers = g_slist_remove (config_lookup->priv->workers, worker); + g_object_unref (worker); + } + + g_mutex_unlock (&config_lookup->priv->property_lock); +} + +/** + * e_config_lookup_dup_registered_workers: + * @config_lookup: an #EConfigLookup + * + * Returns a list of all registered #EConfigLookupWorker objects. + * + * The returned #GSList should be freed with + * g_slist_free_full (workers, g_object_unref); + * when no longer needed. + * + * Returns: (transfer full) (element-type EConfigLookupWorker): a #GSList with all + * workers registered with e_config_lookup_register_worker(). + * + * Since: 3.28 + **/ +GSList * +e_config_lookup_dup_registered_workers (EConfigLookup *config_lookup) +{ + GSList *workers; + + g_return_val_if_fail (E_IS_CONFIG_LOOKUP (config_lookup), NULL); + + g_mutex_lock (&config_lookup->priv->property_lock); + workers = g_slist_copy_deep (config_lookup->priv->workers, (GCopyFunc) g_object_ref, NULL); + g_mutex_unlock (&config_lookup->priv->property_lock); + + return workers; +} + +/** + * e_config_lookup_run: + * @config_lookup: an #EConfigLookup + * @params: an #ENamedParameters with lookup parameters + * @cancellable: an optional #GCancellable, or %NULL + * @callback: a callback to call, when the run is finished + * @user_data: user data for the @callback + * + * Runs configuration lookup asynchronously. Once the run is done, the @callback is called, + * and the call can be finished with e_config_lookup_run_finish(). The @callback is always + * called from the main thread. + * + * Workers can be run individually using e_config_lookup_run_worker(). + * + * Note that there cannot be run two lookups at the same time, thus if it + * happens, then the @callback is called immediately with a %NULL result. + * + * Since: 3.26 + **/ +void +e_config_lookup_run (EConfigLookup *config_lookup, + const ENamedParameters *params, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSList *workers, *link; + + g_return_if_fail (E_IS_CONFIG_LOOKUP (config_lookup)); + g_return_if_fail (params != NULL); + + g_mutex_lock (&config_lookup->priv->property_lock); + + if (config_lookup->priv->run_result) { + g_mutex_unlock (&config_lookup->priv->property_lock); + + if (callback) + callback (G_OBJECT (config_lookup), NULL, user_data); + return; + } + + g_slist_free_full (config_lookup->priv->results, g_object_unref); + config_lookup->priv->results = NULL; + + if (cancellable) + g_object_ref (cancellable); + else + cancellable = g_cancellable_new (); + + config_lookup->priv->run_result = e_simple_async_result_new (G_OBJECT (config_lookup), callback, user_data, e_config_lookup_run); + config_lookup->priv->run_cancellable = cancellable; + + workers = g_slist_copy_deep (config_lookup->priv->workers, (GCopyFunc) g_object_ref, NULL); + + g_mutex_unlock (&config_lookup->priv->property_lock); + + if (workers) { + for (link = workers; link; link = g_slist_next (link)) { + EConfigLookupWorker *worker = link->data; + + e_config_lookup_run_worker (config_lookup, worker, params, cancellable); + } + + g_slist_free_full (workers, g_object_unref); + } else { + ESimpleAsyncResult *run_result; + + g_mutex_lock (&config_lookup->priv->property_lock); + + run_result = config_lookup->priv->run_result; + config_lookup->priv->run_result = NULL; + + g_clear_object (&config_lookup->priv->run_cancellable); + + g_mutex_unlock (&config_lookup->priv->property_lock); + + if (run_result) { + e_simple_async_result_complete_idle (run_result); + g_object_unref (run_result); + } + } +} + +/** + * e_config_lookup_run_finish: + * @config_lookup: an #EConfigLookup + * @result: result of the operation + * + * Finishes the configuration lookup previously run by e_config_lookup_run(). + * It's expected that the extensions may fail, thus it doesn't return + * anything and is provided mainly for consistency with asynchronous API. + * + * Since: 3.26 + **/ +void +e_config_lookup_run_finish (EConfigLookup *config_lookup, + GAsyncResult *result) +{ + g_return_if_fail (E_IS_CONFIG_LOOKUP (config_lookup)); + g_return_if_fail (G_IS_ASYNC_RESULT (result)); + g_return_if_fail (g_async_result_is_tagged (result, e_config_lookup_run)); +} + +/** + * e_config_lookup_run_worker: + * @config_lookup: an #EConfigLookup + * @worker: an #EConfigLookupWorker to run in a dedicated thread + * @params: an #ENamedParameters with lookup parameters + * @cancellable: an optional #GCancellable, or %NULL + * + * Creates a new thread and runs @worker in it. When the @cancellable is %NULL, + * then there's creates a new #CamelOperation, which either proxies currently + * running lookup or the newly created cancellable is completely independent. + * + * This function can be called while there's an ongoing configuration lookup, but + * also when the @worker is restarted. + * + * Since: 3.28 + **/ +void +e_config_lookup_run_worker (EConfigLookup *config_lookup, + EConfigLookupWorker *worker, + const ENamedParameters *params, + GCancellable *cancellable) +{ + ThreadData *td; + + g_return_if_fail (E_IS_CONFIG_LOOKUP (config_lookup)); + g_return_if_fail (E_IS_CONFIG_LOOKUP_WORKER (worker)); + g_return_if_fail (params != NULL); + + td = g_new0 (ThreadData, 1); + td->params = e_named_parameters_new_clone (params); + td->worker = g_object_ref (worker); + + g_mutex_lock (&config_lookup->priv->property_lock); + + if (cancellable) + td->cancellable = camel_operation_new_proxy (cancellable); + else if (config_lookup->priv->run_cancellable) + td->cancellable = camel_operation_new_proxy (config_lookup->priv->run_cancellable); + else + td->cancellable = camel_operation_new (); + + camel_operation_push_message (td->cancellable, "%s", _("Running…")); + config_lookup->priv->worker_cancellables = g_slist_prepend (config_lookup->priv->worker_cancellables, g_object_ref (td->cancellable)); + + config_lookup_schedule_emit_idle (config_lookup, EMIT_WORKER_STARTED | + (!config_lookup->priv->worker_cancellables->next ? EMIT_BUSY : 0), + worker, td->cancellable, NULL, NULL); + + g_thread_pool_push (config_lookup->priv->pool, td, NULL); + + g_mutex_unlock (&config_lookup->priv->property_lock); +} + +/** + * e_config_lookup_add_result: + * @config_lookup: an #EConfigLookup + * @result: (transfer full): an #EConfigLookupResult + * + * Adds a new @result in a list of known configuration lookup results. + * The @config_lookup assumes ownership of the @result and frees it + * when no longer needed. + * + * The list of results can be obtained with e_config_lookup_dup_results(). + * + * Since: 3.26 + **/ +void +e_config_lookup_add_result (EConfigLookup *config_lookup, + EConfigLookupResult *result) +{ + g_return_if_fail (E_IS_CONFIG_LOOKUP (config_lookup)); + g_return_if_fail (E_IS_CONFIG_LOOKUP_RESULT (result)); + + g_mutex_lock (&config_lookup->priv->property_lock); + + config_lookup->priv->results = g_slist_prepend (config_lookup->priv->results, result); + + g_mutex_unlock (&config_lookup->priv->property_lock); + + g_signal_emit (config_lookup, signals[RESULT_ADDED], 0, result); +} + +/** + * e_config_lookup_count_results: + * @config_lookup: an #EConfigLookup + * + * Returns: how many results had been added already. + * + * Since: 3.28 + **/ +gint +e_config_lookup_count_results (EConfigLookup *config_lookup) +{ + gint n_results; + + g_return_val_if_fail (E_IS_CONFIG_LOOKUP (config_lookup), -1); + + g_mutex_lock (&config_lookup->priv->property_lock); + + n_results = g_slist_length (config_lookup->priv->results); + + g_mutex_unlock (&config_lookup->priv->property_lock); + + return n_results; +} + +/** + * e_config_lookup_dup_results: + * @config_lookup: an #EConfigLookup + * @kind: an #EConfigLookupResultKind to filter the results with + * @protocol: (nullable): optional protocol to filter the results with, or %NULL + * + * Returns a #GSList with #EConfigLookupResult objects satisfying + * the @kind and @protocol filtering conditions. To receive all + * gathered results use %E_CONFIG_LOOKUP_RESULT_UNKNOWN for @kind + * and %NULL for the @protocol. + * + * Free the returned #GSList with + * g_slist_free_full (results, g_object_unref); + * when no longer needed. + * + * Returns: (transfer full) (element-type EConfigLookupResult): a #GSList + * with results satisfying the @kind and @protocol filtering conditions. + * + * Since: 3.28 + **/ +GSList * +e_config_lookup_dup_results (EConfigLookup *config_lookup, + EConfigLookupResultKind kind, + const gchar *protocol) +{ + GSList *results = NULL, *link; + + g_return_val_if_fail (E_IS_CONFIG_LOOKUP (config_lookup), NULL); + + g_mutex_lock (&config_lookup->priv->property_lock); + + for (link = config_lookup->priv->results; link; link = g_slist_next (link)) { + EConfigLookupResult *result = link->data; + + if (!E_IS_CONFIG_LOOKUP_RESULT (result)) + continue; + + if (kind != E_CONFIG_LOOKUP_RESULT_UNKNOWN && + kind != e_config_lookup_result_get_kind (result)) + continue; + + if (protocol && + g_strcmp0 (protocol, e_config_lookup_result_get_protocol (result)) != 0) + continue; + + results = g_slist_prepend (results, g_object_ref (result)); + } + + g_mutex_unlock (&config_lookup->priv->property_lock); + + return results; +} + +/** + * e_config_lookup_clear_results: + * @config_lookup: an #EConfigLookup + * + * Frees all gathered results. This might be usually called before + * starting new custom lookup. The e_config_lookup_run() frees + * all results automatically. + * + * Since: 3.28 + **/ +void +e_config_lookup_clear_results (EConfigLookup *config_lookup) +{ + g_return_if_fail (E_IS_CONFIG_LOOKUP (config_lookup)); + + g_mutex_lock (&config_lookup->priv->property_lock); + + g_slist_free_full (config_lookup->priv->results, g_object_unref); + config_lookup->priv->results = NULL; + + g_mutex_unlock (&config_lookup->priv->property_lock); +} + +/** + * e_config_lookup_encode_certificate_trust: + * @response: an #ETrustPromptResponse to encode + * + * Encodes @response to a string. This can be decoded back to enum + * with e_config_lookup_decode_certificate_trust(). + * + * Returns: string representation of @response. + * + * Since: 3.28 + **/ +const gchar * +e_config_lookup_encode_certificate_trust (ETrustPromptResponse response) +{ + return e_enum_to_string (E_TYPE_TRUST_PROMPT_RESPONSE, response); +} + +/** + * e_config_lookup_decode_certificate_trust: + * @value: a text value to decode + * + * Decodes text @value to #ETrustPromptResponse, previously encoded + * with e_config_lookup_encode_certificate_trust(). + * + * Returns: an #ETrustPromptResponse corresponding to @value. + * + * Since: 3.28 + **/ +ETrustPromptResponse +e_config_lookup_decode_certificate_trust (const gchar *value) +{ + gint decoded; + + if (!value || + !e_enum_from_string (E_TYPE_TRUST_PROMPT_RESPONSE, value, &decoded)) + decoded = E_TRUST_PROMPT_RESPONSE_UNKNOWN; + + return decoded; +}