/* * e-mail-account-store.c * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the program; if not, see * */ #include "e-mail-account-store.h" #include #include #include #include #include #include #define E_MAIL_ACCOUNT_STORE_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_MAIL_ACCOUNT_STORE, EMailAccountStorePrivate)) typedef struct _IndexItem IndexItem; struct _EMailAccountStorePrivate { CamelService *default_service; GHashTable *service_index; gchar *sort_order_filename; gpointer session; /* weak pointer */ guint busy_count; }; struct _IndexItem { CamelService *service; GtkTreeRowReference *reference; gulong notify_handler_id; }; enum { PROP_0, PROP_BUSY, PROP_DEFAULT_SERVICE, PROP_SESSION }; enum { SERVICE_ADDED, SERVICE_REMOVED, SERVICE_ENABLED, SERVICE_DISABLED, SERVICES_REORDERED, REMOVE_REQUESTED, ENABLE_REQUESTED, DISABLE_REQUESTED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; /* Forward Declarations */ static void e_mail_account_store_interface_init (GtkTreeModelIface *interface); G_DEFINE_TYPE_WITH_CODE ( EMailAccountStore, e_mail_account_store, GTK_TYPE_LIST_STORE, G_IMPLEMENT_INTERFACE ( GTK_TYPE_TREE_MODEL, e_mail_account_store_interface_init) G_IMPLEMENT_INTERFACE ( E_TYPE_EXTENSIBLE, NULL)) static void index_item_free (IndexItem *item) { g_signal_handler_disconnect ( item->service, item->notify_handler_id); g_object_unref (item->service); gtk_tree_row_reference_free (item->reference); g_slice_free (IndexItem, item); } static gboolean mail_account_store_get_iter (EMailAccountStore *store, CamelService *service, GtkTreeIter *iter) { IndexItem *item; GtkTreeModel *model; GtkTreePath *path; gboolean iter_set; g_return_val_if_fail (service != NULL, FALSE); item = g_hash_table_lookup (store->priv->service_index, service); if (item == NULL) return FALSE; if (!gtk_tree_row_reference_valid (item->reference)) return FALSE; model = gtk_tree_row_reference_get_model (item->reference); path = gtk_tree_row_reference_get_path (item->reference); iter_set = gtk_tree_model_get_iter (model, iter, path); gtk_tree_path_free (path); return iter_set; } static gint mail_account_store_default_compare (CamelService *service_a, CamelService *service_b, EMailAccountStore *store) { const gchar *display_name_a; const gchar *display_name_b; const gchar *uid_a; const gchar *uid_b; uid_a = camel_service_get_uid (service_a); uid_b = camel_service_get_uid (service_b); /* Check for special cases first. */ if (g_str_equal (uid_a, E_MAIL_SESSION_LOCAL_UID)) return -1; else if (g_str_equal (uid_b, E_MAIL_SESSION_LOCAL_UID)) return 1; else if (g_str_equal (uid_a, E_MAIL_SESSION_VFOLDER_UID)) return 1; else if (g_str_equal (uid_b, E_MAIL_SESSION_VFOLDER_UID)) return -1; /* Otherwise sort them alphabetically. */ display_name_a = camel_service_get_display_name (service_a); display_name_b = camel_service_get_display_name (service_b); if (display_name_a == NULL) display_name_a = ""; if (display_name_b == NULL) display_name_b = ""; return g_utf8_collate (display_name_a, display_name_b); } static void mail_account_store_update_row (EMailAccountStore *store, CamelService *service, GtkTreeIter *iter) { CamelProvider *provider; gboolean is_default; const gchar *backend_name; const gchar *display_name; is_default = (service == store->priv->default_service); display_name = camel_service_get_display_name (service); provider = camel_service_get_provider (service); backend_name = (provider != NULL) ? provider->protocol : NULL; gtk_list_store_set ( GTK_LIST_STORE (store), iter, E_MAIL_ACCOUNT_STORE_COLUMN_DEFAULT, is_default, E_MAIL_ACCOUNT_STORE_COLUMN_BACKEND_NAME, backend_name, E_MAIL_ACCOUNT_STORE_COLUMN_DISPLAY_NAME, display_name, -1); } struct ServiceNotifyCbData { EMailAccountStore *store; CamelService *service; }; static void service_notify_cb_data_free (gpointer ptr) { struct ServiceNotifyCbData *data = ptr; g_clear_object (&data->store); g_clear_object (&data->service); g_slice_free (struct ServiceNotifyCbData, data); } static gboolean mail_account_store_service_notify_idle_cb (gpointer user_data) { struct ServiceNotifyCbData *data = user_data; GtkTreeIter iter; g_return_val_if_fail (data != NULL, FALSE); if (mail_account_store_get_iter (data->store, data->service, &iter)) mail_account_store_update_row (data->store, data->service, &iter); return FALSE; } static void mail_account_store_service_notify_cb (CamelService *service, GParamSpec *pspec, EMailAccountStore *store) { struct ServiceNotifyCbData *data; data = g_slice_new0 (struct ServiceNotifyCbData); data->store = g_object_ref (store); data->service = g_object_ref (service); g_idle_add_full ( G_PRIORITY_DEFAULT_IDLE, mail_account_store_service_notify_idle_cb, data, service_notify_cb_data_free); } static void mail_account_store_remove_source_cb (ESource *source, GAsyncResult *result, EMailAccountStore *store) { GError *error = NULL; /* FIXME EMailAccountStore should implement EAlertSink. */ if (!e_source_remove_finish (source, result, &error)) { g_warning ("%s: %s", G_STRFUNC, error->message); g_error_free (error); } g_return_if_fail (store->priv->busy_count > 0); store->priv->busy_count--; g_object_notify (G_OBJECT (store), "busy"); g_object_unref (store); } static void mail_account_store_write_source_cb (ESource *source, GAsyncResult *result, EMailAccountStore *store) { GError *error = NULL; /* FIXME EMailAccountStore should implement EAlertSink. */ if (!e_source_write_finish (source, result, &error)) { g_warning ("%s: %s", G_STRFUNC, error->message); g_error_free (error); } g_return_if_fail (store->priv->busy_count > 0); store->priv->busy_count--; g_object_notify (G_OBJECT (store), "busy"); g_object_unref (store); } static void mail_account_store_clean_index (EMailAccountStore *store) { GQueue trash = G_QUEUE_INIT; GHashTable *hash_table; GHashTableIter iter; gpointer key, value; hash_table = store->priv->service_index; g_hash_table_iter_init (&iter, hash_table); /* Remove index items with invalid GtkTreeRowReferences. */ while (g_hash_table_iter_next (&iter, &key, &value)) { IndexItem *item = value; if (!gtk_tree_row_reference_valid (item->reference)) g_queue_push_tail (&trash, key); } while ((key = g_queue_pop_head (&trash)) != NULL) g_hash_table_remove (hash_table, key); } static void mail_account_store_update_index (EMailAccountStore *store, GtkTreePath *path, GtkTreeIter *iter) { CamelService *service = NULL; GHashTable *hash_table; GtkTreeModel *model; IndexItem *item; model = GTK_TREE_MODEL (store); hash_table = store->priv->service_index; gtk_tree_model_get ( model, iter, E_MAIL_ACCOUNT_STORE_COLUMN_SERVICE, &service, -1); if (service == NULL) return; item = g_hash_table_lookup (hash_table, service); if (item == NULL) { item = g_slice_new0 (IndexItem); item->service = g_object_ref (service); item->notify_handler_id = g_signal_connect ( service, "notify", G_CALLBACK ( mail_account_store_service_notify_cb), store); g_hash_table_insert (hash_table, item->service, item); } /* Update the row reference so the IndexItem will survive * drag-and-drop (new row is inserted, old row is deleted). */ gtk_tree_row_reference_free (item->reference); item->reference = gtk_tree_row_reference_new (model, path); g_object_unref (service); } static void mail_account_store_set_session (EMailAccountStore *store, EMailSession *session) { g_return_if_fail (E_IS_MAIL_SESSION (session)); g_return_if_fail (store->priv->session == NULL); store->priv->session = session; g_object_add_weak_pointer ( G_OBJECT (store->priv->session), &store->priv->session); } static void mail_account_store_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_DEFAULT_SERVICE: e_mail_account_store_set_default_service ( E_MAIL_ACCOUNT_STORE (object), g_value_get_object (value)); return; case PROP_SESSION: mail_account_store_set_session ( E_MAIL_ACCOUNT_STORE (object), g_value_get_object (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void mail_account_store_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_BUSY: g_value_set_boolean ( value, e_mail_account_store_get_busy ( E_MAIL_ACCOUNT_STORE (object))); return; case PROP_DEFAULT_SERVICE: g_value_set_object ( value, e_mail_account_store_get_default_service ( E_MAIL_ACCOUNT_STORE (object))); return; case PROP_SESSION: g_value_set_object ( value, e_mail_account_store_get_session ( E_MAIL_ACCOUNT_STORE (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void mail_account_store_dispose (GObject *object) { EMailAccountStorePrivate *priv; priv = E_MAIL_ACCOUNT_STORE_GET_PRIVATE (object); if (priv->session != NULL) { g_object_remove_weak_pointer ( G_OBJECT (priv->session), &priv->session); priv->session = NULL; } if (priv->default_service != NULL) { g_object_unref (priv->default_service); priv->default_service = NULL; } g_hash_table_remove_all (priv->service_index); /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_mail_account_store_parent_class)->dispose (object); } static void mail_account_store_finalize (GObject *object) { EMailAccountStorePrivate *priv; priv = E_MAIL_ACCOUNT_STORE_GET_PRIVATE (object); g_warn_if_fail (priv->busy_count == 0); g_hash_table_destroy (priv->service_index); g_free (priv->sort_order_filename); /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (e_mail_account_store_parent_class)->finalize (object); } static void mail_account_store_constructed (GObject *object) { EMailAccountStore *store; EMailSession *session; ESourceRegistry *registry; const gchar *config_dir; /* Chain up to parent's constructed() method. */ G_OBJECT_CLASS (e_mail_account_store_parent_class)->constructed (object); store = E_MAIL_ACCOUNT_STORE (object); session = e_mail_account_store_get_session (store); registry = e_mail_session_get_registry (session); /* Bind the default mail account ESource to our default * CamelService, with help from some transform functions. */ g_object_bind_property_full ( registry, "default-mail-account", store, "default-service", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE, e_binding_transform_source_to_service, e_binding_transform_service_to_source, session, (GDestroyNotify) NULL); config_dir = mail_session_get_config_dir (); /* XXX Should we take the filename as a constructor property? */ store->priv->sort_order_filename = g_build_filename ( config_dir, "sortorder.ini", NULL); e_extensible_load_extensions (E_EXTENSIBLE (object)); } static void mail_account_store_service_added (EMailAccountStore *store, CamelService *service) { /* Placeholder so subclasses can safely chain up. */ } static void mail_account_store_service_removed (EMailAccountStore *store, CamelService *service) { EMailSession *session; MailFolderCache *cache; ESourceRegistry *registry; ESource *source; const gchar *uid; session = e_mail_account_store_get_session (store); cache = e_mail_session_get_folder_cache (session); mail_folder_cache_service_removed (cache, service); uid = camel_service_get_uid (service); registry = e_mail_session_get_registry (session); source = e_source_registry_ref_source (registry, uid); /* If this ESource is part of a collection, we need to remove * the entire collection. Check the ESource and its ancestors * for a collection extension and remove the containing source. */ if (source != NULL) { ESource *collection; collection = e_source_registry_find_extension ( registry, source, E_SOURCE_EXTENSION_COLLECTION); if (collection != NULL) { g_object_unref (source); source = collection; } } if (source != NULL && e_source_get_removable (source)) { store->priv->busy_count++; g_object_notify (G_OBJECT (store), "busy"); /* XXX Should this be cancellable? */ e_source_remove ( source, NULL, (GAsyncReadyCallback) mail_account_store_remove_source_cb, g_object_ref (store)); g_object_unref (source); } } static void mail_account_store_service_enabled (EMailAccountStore *store, CamelService *service) { EMailSession *session; MailFolderCache *cache; ESourceRegistry *registry; ESource *source; const gchar *uid; session = e_mail_account_store_get_session (store); cache = e_mail_session_get_folder_cache (session); mail_folder_cache_service_enabled (cache, service); uid = camel_service_get_uid (service); registry = e_mail_session_get_registry (session); source = e_source_registry_ref_source (registry, uid); /* Locate the identity source referenced in the [Mail Account] * extension. We want to keep its enabled state synchronized * with the account's enabled state. (Need to do this before * we swap the mail account ESource for a collection below.) */ if (source != NULL) { ESource *identity = NULL; ESourceMailAccount *extension; const gchar *extension_name; extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT; extension = e_source_get_extension (source, extension_name); uid = e_source_mail_account_get_identity_uid (extension); if (uid != NULL) identity = e_source_registry_ref_source (registry, uid); if (identity != NULL && e_source_get_writable (identity)) { e_source_set_enabled (identity, TRUE); store->priv->busy_count++; g_object_notify (G_OBJECT (store), "busy"); /* XXX Should this be cancellable? */ e_source_write ( identity, NULL, (GAsyncReadyCallback) mail_account_store_write_source_cb, g_object_ref (store)); g_object_unref (identity); } } /* If this ESource is part of a collection, we need to enable * the entire collection. Check the ESource and its ancestors * for a collection extension and enable the containing source. */ if (source != NULL) { ESource *collection; collection = e_source_registry_find_extension ( registry, source, E_SOURCE_EXTENSION_COLLECTION); if (collection != NULL) { g_object_unref (source); source = collection; } } if (source != NULL && e_source_get_writable (source)) { e_source_set_enabled (source, TRUE); store->priv->busy_count++; g_object_notify (G_OBJECT (store), "busy"); /* XXX Should this be cancellable? */ e_source_write ( source, NULL, (GAsyncReadyCallback) mail_account_store_write_source_cb, g_object_ref (store)); g_object_unref (source); } } static void mail_account_store_service_disabled (EMailAccountStore *store, CamelService *service) { EMailSession *session; MailFolderCache *cache; ESourceRegistry *registry; ESource *source; const gchar *uid; session = e_mail_account_store_get_session (store); cache = e_mail_session_get_folder_cache (session); mail_folder_cache_service_disabled (cache, service); uid = camel_service_get_uid (service); registry = e_mail_session_get_registry (session); source = e_source_registry_ref_source (registry, uid); /* Locate the identity source referenced in the [Mail Account] * extension. We want to keep its enabled state synchronized * with the account's enabled state. (Need to do this before * we swap the mail account ESource for a collection below.) */ if (source != NULL) { ESource *identity = NULL; ESourceMailAccount *extension; const gchar *extension_name; extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT; extension = e_source_get_extension (source, extension_name); uid = e_source_mail_account_get_identity_uid (extension); if (uid != NULL) identity = e_source_registry_ref_source (registry, uid); if (identity != NULL && e_source_get_writable (identity)) { e_source_set_enabled (identity, FALSE); store->priv->busy_count++; g_object_notify (G_OBJECT (store), "busy"); /* XXX Should this be cancellable? */ e_source_write ( identity, NULL, (GAsyncReadyCallback) mail_account_store_write_source_cb, g_object_ref (store)); g_object_unref (identity); } } /* If this ESource is part of a collection, we need to disable * the entire collection. Check the ESource and its ancestors * for a collection extension and disable the containing source. */ if (source != NULL) { ESource *collection; collection = e_source_registry_find_extension ( registry, source, E_SOURCE_EXTENSION_COLLECTION); if (collection != NULL) { g_object_unref (source); source = collection; } } if (source != NULL && e_source_get_writable (source)) { e_source_set_enabled (source, FALSE); store->priv->busy_count++; g_object_notify (G_OBJECT (store), "busy"); /* XXX Should this be cancellable? */ e_source_write ( source, NULL, (GAsyncReadyCallback) mail_account_store_write_source_cb, g_object_ref (store)); g_object_unref (source); } } static void mail_account_store_services_reordered (EMailAccountStore *store, gboolean default_restored) { /* XXX Should this be made asynchronous? */ GError *error = NULL; if (default_restored) { const gchar *filename; filename = store->priv->sort_order_filename; if (g_file_test (filename, G_FILE_TEST_EXISTS)) g_unlink (filename); return; } if (!e_mail_account_store_save_sort_order (store, &error)) { g_warning ("%s: %s", G_STRFUNC, error->message); g_error_free (error); } } static gboolean mail_account_store_remove_requested (EMailAccountStore *store, GtkWindow *parent_window, CamelService *service) { gint response; /* FIXME Need to use "mail:ask-delete-account-with-proxies" if the * mail account has proxies. But this is groupwise-specific * and doesn't belong here anyway. Think of a better idea. */ response = e_alert_run_dialog_for_args ( parent_window, "mail:ask-delete-account", NULL); return (response == GTK_RESPONSE_YES); } static gboolean mail_account_store_enable_requested (EMailAccountStore *store, GtkWindow *parent_window, CamelService *service) { return TRUE; } static gboolean mail_account_store_disable_requested (EMailAccountStore *store, GtkWindow *parent_window, CamelService *service) { /* FIXME Need to check whether the account has proxies and run a * "mail:ask-delete-proxy-accounts" alert dialog, but this * is groupwise-specific and doesn't belong here anyway. * Think of a better idea. */ return TRUE; } static void mail_account_store_row_changed (GtkTreeModel *tree_model, GtkTreePath *path, GtkTreeIter *iter) { EMailAccountStore *store; /* Neither GtkTreeModel nor GtkListStore implements * this method, so there is nothing to chain up to. */ store = E_MAIL_ACCOUNT_STORE (tree_model); mail_account_store_update_index (store, path, iter); } static void mail_account_store_row_inserted (GtkTreeModel *tree_model, GtkTreePath *path, GtkTreeIter *iter) { EMailAccountStore *store; /* Neither GtkTreeModel nor GtkListStore implements * this method, so there is nothing to chain up to. */ store = E_MAIL_ACCOUNT_STORE (tree_model); mail_account_store_update_index (store, path, iter); } static gboolean mail_account_store_true_proceed (GSignalInvocationHint *ihint, GValue *return_accumulator, const GValue *handler_return, gpointer not_used) { gboolean proceed; proceed = g_value_get_boolean (handler_return); g_value_set_boolean (return_accumulator, proceed); return proceed; } static void e_mail_account_store_class_init (EMailAccountStoreClass *class) { GObjectClass *object_class; g_type_class_add_private (class, sizeof (EMailAccountStorePrivate)); object_class = G_OBJECT_CLASS (class); object_class->set_property = mail_account_store_set_property; object_class->get_property = mail_account_store_get_property; object_class->dispose = mail_account_store_dispose; object_class->finalize = mail_account_store_finalize; object_class->constructed = mail_account_store_constructed; class->service_added = mail_account_store_service_added; class->service_removed = mail_account_store_service_removed; class->service_enabled = mail_account_store_service_enabled; class->service_disabled = mail_account_store_service_disabled; class->services_reordered = mail_account_store_services_reordered; class->remove_requested = mail_account_store_remove_requested; class->enable_requested = mail_account_store_enable_requested; class->disable_requested = mail_account_store_disable_requested; g_object_class_install_property ( object_class, PROP_BUSY, g_param_spec_boolean ( "busy", "Busy", "Whether async operations are in progress", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_DEFAULT_SERVICE, g_param_spec_object ( "default-service", "Default Service", "Default mail store", CAMEL_TYPE_SERVICE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_SESSION, g_param_spec_object ( "session", "Session", "Mail session", E_TYPE_MAIL_SESSION, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); signals[SERVICE_ADDED] = g_signal_new ( "service-added", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EMailAccountStoreClass, service_added), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, CAMEL_TYPE_SERVICE); signals[SERVICE_REMOVED] = g_signal_new ( "service-removed", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EMailAccountStoreClass, service_removed), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, CAMEL_TYPE_SERVICE); signals[SERVICE_ENABLED] = g_signal_new ( "service-enabled", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EMailAccountStoreClass, service_enabled), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, CAMEL_TYPE_SERVICE); signals[SERVICE_DISABLED] = g_signal_new ( "service-disabled", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EMailAccountStoreClass, service_disabled), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, CAMEL_TYPE_SERVICE); signals[SERVICES_REORDERED] = g_signal_new ( "services-reordered", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EMailAccountStoreClass, services_reordered), NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); signals[REMOVE_REQUESTED] = g_signal_new ( "remove-requested", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EMailAccountStoreClass, remove_requested), mail_account_store_true_proceed, NULL, e_marshal_BOOLEAN__OBJECT_OBJECT, G_TYPE_BOOLEAN, 2, GTK_TYPE_WINDOW, CAMEL_TYPE_SERVICE); signals[ENABLE_REQUESTED] = g_signal_new ( "enable-requested", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EMailAccountStoreClass, enable_requested), mail_account_store_true_proceed, NULL, e_marshal_BOOLEAN__OBJECT_OBJECT, G_TYPE_BOOLEAN, 2, GTK_TYPE_WINDOW, CAMEL_TYPE_SERVICE); signals[DISABLE_REQUESTED] = g_signal_new ( "disable-requested", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EMailAccountStoreClass, disable_requested), mail_account_store_true_proceed, NULL, e_marshal_BOOLEAN__OBJECT_OBJECT, G_TYPE_BOOLEAN, 2, GTK_TYPE_WINDOW, CAMEL_TYPE_SERVICE); } static void e_mail_account_store_interface_init (GtkTreeModelIface *interface) { interface->row_changed = mail_account_store_row_changed; interface->row_inserted = mail_account_store_row_inserted; } static void e_mail_account_store_init (EMailAccountStore *store) { GType types[E_MAIL_ACCOUNT_STORE_NUM_COLUMNS]; GHashTable *service_index; gint ii = 0; service_index = g_hash_table_new_full ( (GHashFunc) g_direct_hash, (GEqualFunc) g_direct_equal, (GDestroyNotify) NULL, (GDestroyNotify) index_item_free); store->priv = E_MAIL_ACCOUNT_STORE_GET_PRIVATE (store); store->priv->service_index = service_index; types[ii++] = CAMEL_TYPE_SERVICE; /* COLUMN_SERVICE */ types[ii++] = G_TYPE_BOOLEAN; /* COLUMN_BUILTIN */ types[ii++] = G_TYPE_BOOLEAN; /* COLUMN_ENABLED */ types[ii++] = G_TYPE_BOOLEAN; /* COLUMN_DEFAULT */ types[ii++] = G_TYPE_STRING; /* COLUMN_BACKEND_NAME */ types[ii++] = G_TYPE_STRING; /* COLUMN_DISPLAY_NAME */ types[ii++] = G_TYPE_STRING; /* COLUMN_ICON_NAME */ types[ii++] = G_TYPE_BOOLEAN; /* COLUMN_ONLINE_ACCOUNT */ types[ii++] = G_TYPE_BOOLEAN; /* COLUMN_ENABLED_VISIBLE */ g_assert (ii == E_MAIL_ACCOUNT_STORE_NUM_COLUMNS); gtk_list_store_set_column_types ( GTK_LIST_STORE (store), G_N_ELEMENTS (types), types); } EMailAccountStore * e_mail_account_store_new (EMailSession *session) { g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL); return g_object_new ( E_TYPE_MAIL_ACCOUNT_STORE, "session", session, NULL); } void e_mail_account_store_clear (EMailAccountStore *store) { g_return_if_fail (E_IS_MAIL_ACCOUNT_STORE (store)); gtk_list_store_clear (GTK_LIST_STORE (store)); g_hash_table_remove_all (store->priv->service_index); } gboolean e_mail_account_store_get_busy (EMailAccountStore *store) { g_return_val_if_fail (E_IS_MAIL_ACCOUNT_STORE (store), FALSE); return (store->priv->busy_count > 0); } EMailSession * e_mail_account_store_get_session (EMailAccountStore *store) { g_return_val_if_fail (E_IS_MAIL_ACCOUNT_STORE (store), NULL); return E_MAIL_SESSION (store->priv->session); } CamelService * e_mail_account_store_get_default_service (EMailAccountStore *store) { g_return_val_if_fail (E_IS_MAIL_ACCOUNT_STORE (store), NULL); return store->priv->default_service; } void e_mail_account_store_set_default_service (EMailAccountStore *store, CamelService *service) { GtkTreeModel *model; GtkTreeIter iter; gboolean iter_set; g_return_if_fail (E_IS_MAIL_ACCOUNT_STORE (store)); if (service == store->priv->default_service) return; if (service != NULL) { g_return_if_fail (CAMEL_IS_SERVICE (service)); g_object_ref (service); } if (store->priv->default_service != NULL) g_object_unref (store->priv->default_service); store->priv->default_service = service; model = GTK_TREE_MODEL (store); iter_set = gtk_tree_model_get_iter_first (model, &iter); while (iter_set) { CamelService *candidate; gtk_tree_model_get ( model, &iter, E_MAIL_ACCOUNT_STORE_COLUMN_SERVICE, &candidate, -1); gtk_list_store_set ( GTK_LIST_STORE (model), &iter, E_MAIL_ACCOUNT_STORE_COLUMN_DEFAULT, service == candidate, -1); g_object_unref (candidate); iter_set = gtk_tree_model_iter_next (model, &iter); } g_object_notify (G_OBJECT (store), "default-service"); } void e_mail_account_store_add_service (EMailAccountStore *store, CamelService *service) { EMailSession *session; ESourceRegistry *registry; ESource *collection; ESource *source; GtkTreeIter iter; const gchar *filename; const gchar *icon_name = NULL; const gchar *uid; gboolean builtin; gboolean enabled; gboolean online_account = FALSE; gboolean enabled_visible = TRUE; g_return_if_fail (E_IS_MAIL_ACCOUNT_STORE (store)); g_return_if_fail (CAMEL_IS_SERVICE (service)); /* Avoid duplicate services in the account store. */ if (mail_account_store_get_iter (store, service, &iter)) g_return_if_reached (); uid = camel_service_get_uid (service); builtin = (g_strcmp0 (uid, E_MAIL_SESSION_LOCAL_UID) == 0) || (g_strcmp0 (uid, E_MAIL_SESSION_VFOLDER_UID) == 0); session = e_mail_account_store_get_session (store); registry = e_mail_session_get_registry (session); source = e_source_registry_ref_source (registry, uid); g_return_if_fail (source != NULL); /* If this ESource is part of a collection, we need to * pick up the enabled state for the entire collection. * Check the ESource and its ancestors for a collection * extension and read from the containing source. */ collection = e_source_registry_find_extension ( registry, source, E_SOURCE_EXTENSION_COLLECTION); if (collection != NULL) { const gchar *extension_name; enabled = e_source_get_enabled (collection); /* Check for GNOME Online Accounts linkage. */ extension_name = E_SOURCE_EXTENSION_GOA; if (e_source_has_extension (collection, extension_name)) { online_account = TRUE; enabled_visible = FALSE; /* Provided by gnome-control-center-data. */ icon_name = "goa-panel"; } /* Check for Ubuntu Online Accounts linkage. */ extension_name = E_SOURCE_EXTENSION_UOA; if (e_source_has_extension (collection, extension_name)) { online_account = TRUE; enabled_visible = FALSE; /* Provided by gnome-control-center-signon. */ icon_name = "credentials-preferences"; } g_object_unref (collection); } else { enabled = e_source_get_enabled (source); } g_object_unref (source); /* Where do we insert new services now that accounts can be * reordered? This is just a simple policy I came up with. * It's certainly subject to debate and tweaking. * * Always insert new services in row 0 initially. Then test * for the presence of the sort order file. If present, the * user has messed around with the ordering so leave the new * service at row 0. If not present, services are sorted in * their default order. So re-apply the default order using * e_mail_account_store_reorder_services(store, NULL) so the * new service moves to its proper default position. */ gtk_list_store_prepend (GTK_LIST_STORE (store), &iter); gtk_list_store_set ( GTK_LIST_STORE (store), &iter, E_MAIL_ACCOUNT_STORE_COLUMN_SERVICE, service, E_MAIL_ACCOUNT_STORE_COLUMN_BUILTIN, builtin, E_MAIL_ACCOUNT_STORE_COLUMN_ENABLED, enabled, E_MAIL_ACCOUNT_STORE_COLUMN_ICON_NAME, icon_name, E_MAIL_ACCOUNT_STORE_COLUMN_ONLINE_ACCOUNT, online_account, E_MAIL_ACCOUNT_STORE_COLUMN_ENABLED_VISIBLE, enabled_visible, -1); /* This populates the rest of the columns. */ mail_account_store_update_row (store, service, &iter); /* No need to connect to "service-added" emissions since it's * always immediately followed by either "service-enabled" or * "service-disabled" in MailFolderCache */ g_signal_emit (store, signals[SERVICE_ADDED], 0, service); if (enabled) g_signal_emit (store, signals[SERVICE_ENABLED], 0, service); else g_signal_emit (store, signals[SERVICE_DISABLED], 0, service); filename = store->priv->sort_order_filename; if (!g_file_test (filename, G_FILE_TEST_EXISTS)) e_mail_account_store_reorder_services (store, NULL); } void e_mail_account_store_remove_service (EMailAccountStore *store, GtkWindow *parent_window, CamelService *service) { GtkTreeIter iter; gboolean proceed = TRUE; g_return_if_fail (E_IS_MAIL_ACCOUNT_STORE (store)); g_return_if_fail (CAMEL_IS_SERVICE (service)); /* XXX Our service_removed() class method calls e_source_remove(), * which causes the registry service to emit a "source-removed" * signal. But since other applications may also induce signal * emissions from the registry service, EMailUISession handles * "source-removed" by calling this function. So quietly break * the cycle if we don't find the service in our tree model. */ if (!mail_account_store_get_iter (store, service, &iter)) return; /* If no parent window was given, skip the request signal. */ if (GTK_IS_WINDOW (parent_window)) g_signal_emit ( store, signals[REMOVE_REQUESTED], 0, parent_window, service, &proceed); if (proceed) { g_object_ref (service); gtk_list_store_remove (GTK_LIST_STORE (store), &iter); mail_account_store_clean_index (store); g_signal_emit (store, signals[SERVICE_REMOVED], 0, service); g_object_unref (service); } } void e_mail_account_store_enable_service (EMailAccountStore *store, GtkWindow *parent_window, CamelService *service) { GtkTreeIter iter; gboolean proceed = FALSE; g_return_if_fail (E_IS_MAIL_ACCOUNT_STORE (store)); g_return_if_fail (CAMEL_IS_SERVICE (service)); if (!mail_account_store_get_iter (store, service, &iter)) g_return_if_reached (); /* If no parent window was given, skip the request signal. */ if (GTK_IS_WINDOW (parent_window)) g_signal_emit ( store, signals[ENABLE_REQUESTED], 0, parent_window, service, &proceed); if (proceed) { gtk_list_store_set ( GTK_LIST_STORE (store), &iter, E_MAIL_ACCOUNT_STORE_COLUMN_ENABLED, TRUE, -1); g_signal_emit (store, signals[SERVICE_ENABLED], 0, service); } } void e_mail_account_store_disable_service (EMailAccountStore *store, GtkWindow *parent_window, CamelService *service) { GtkTreeIter iter; gboolean proceed = FALSE; g_return_if_fail (E_IS_MAIL_ACCOUNT_STORE (store)); g_return_if_fail (CAMEL_IS_SERVICE (service)); if (!mail_account_store_get_iter (store, service, &iter)) g_return_if_reached (); /* If no parent window was given, skip the request signal. */ if (GTK_IS_WINDOW (parent_window)) g_signal_emit ( store, signals[DISABLE_REQUESTED], 0, parent_window, service, &proceed); if (proceed) { gtk_list_store_set ( GTK_LIST_STORE (store), &iter, E_MAIL_ACCOUNT_STORE_COLUMN_ENABLED, FALSE, -1); g_signal_emit (store, signals[SERVICE_DISABLED], 0, service); } } void e_mail_account_store_queue_services (EMailAccountStore *store, GQueue *out_queue) { GtkTreeModel *tree_model; GtkTreeIter iter; gboolean iter_set; gint column; g_return_if_fail (E_IS_MAIL_ACCOUNT_STORE (store)); g_return_if_fail (out_queue != NULL); tree_model = GTK_TREE_MODEL (store); iter_set = gtk_tree_model_get_iter_first (tree_model, &iter); while (iter_set) { GValue value = G_VALUE_INIT; column = E_MAIL_ACCOUNT_STORE_COLUMN_SERVICE; gtk_tree_model_get_value (tree_model, &iter, column, &value); g_queue_push_tail (out_queue, g_value_get_object (&value)); g_value_unset (&value); iter_set = gtk_tree_model_iter_next (tree_model, &iter); } } void e_mail_account_store_queue_enabled_services (EMailAccountStore *store, GQueue *out_queue) { GtkTreeModel *tree_model; GtkTreeIter iter; gboolean iter_set; gint column; g_return_if_fail (E_IS_MAIL_ACCOUNT_STORE (store)); g_return_if_fail (out_queue != NULL); tree_model = GTK_TREE_MODEL (store); iter_set = gtk_tree_model_get_iter_first (tree_model, &iter); while (iter_set) { GValue value = G_VALUE_INIT; gboolean enabled; column = E_MAIL_ACCOUNT_STORE_COLUMN_ENABLED; gtk_tree_model_get_value (tree_model, &iter, column, &value); enabled = g_value_get_boolean (&value); g_value_unset (&value); if (enabled) { column = E_MAIL_ACCOUNT_STORE_COLUMN_SERVICE; gtk_tree_model_get_value (tree_model, &iter, column, &value); g_queue_push_tail (out_queue, g_value_get_object (&value)); g_value_unset (&value); } iter_set = gtk_tree_model_iter_next (tree_model, &iter); } } gboolean e_mail_account_store_have_enabled_service (EMailAccountStore *store, GType service_type) { GtkTreeModel *tree_model; GtkTreeIter iter; gboolean iter_set; gint column; gboolean found = FALSE; g_return_val_if_fail (E_IS_MAIL_ACCOUNT_STORE (store), FALSE); tree_model = GTK_TREE_MODEL (store); iter_set = gtk_tree_model_get_iter_first (tree_model, &iter); while (iter_set && !found) { GValue value = G_VALUE_INIT; gboolean enabled; column = E_MAIL_ACCOUNT_STORE_COLUMN_ENABLED; gtk_tree_model_get_value (tree_model, &iter, column, &value); enabled = g_value_get_boolean (&value); g_value_unset (&value); if (enabled) { CamelService *service; column = E_MAIL_ACCOUNT_STORE_COLUMN_SERVICE; gtk_tree_model_get_value (tree_model, &iter, column, &value); service = g_value_get_object (&value); found = service && G_TYPE_CHECK_INSTANCE_TYPE (service, service_type); g_value_unset (&value); } iter_set = gtk_tree_model_iter_next (tree_model, &iter); } return found; } void e_mail_account_store_reorder_services (EMailAccountStore *store, GQueue *ordered_services) { GQueue *current_order = NULL; GQueue *default_order = NULL; GtkTreeModel *tree_model; gboolean use_default_order; GList *head, *link; gint *new_order; gint n_children; gint new_pos = 0; g_return_if_fail (E_IS_MAIL_ACCOUNT_STORE (store)); tree_model = GTK_TREE_MODEL (store); n_children = gtk_tree_model_iter_n_children (tree_model, NULL); /* Treat NULL queues and empty queues the same. */ if (ordered_services != NULL && g_queue_is_empty (ordered_services)) ordered_services = NULL; /* If the length of the custom ordering disagrees with the * number of rows in the store, revert to default ordering. */ if (ordered_services != NULL) { if (g_queue_get_length (ordered_services) != n_children) ordered_services = NULL; } use_default_order = (ordered_services == NULL); /* Build a queue of CamelServices in the order they appear in * the list store. We'll use this to construct the mapping to * pass to gtk_list_store_reorder(). */ current_order = g_queue_new (); e_mail_account_store_queue_services (store, current_order); /* If a custom ordering was not given, revert to default. */ if (use_default_order) { default_order = g_queue_copy (current_order); g_queue_sort ( default_order, (GCompareDataFunc) mail_account_store_default_compare, store); ordered_services = default_order; } new_order = g_new0 (gint, n_children); head = g_queue_peek_head_link (ordered_services); for (link = head; link != NULL; link = g_list_next (link)) { GList *matching_link; gint old_pos; matching_link = g_queue_find (current_order, link->data); if (matching_link == NULL || matching_link->data == NULL) break; old_pos = g_queue_link_index (current_order, matching_link); matching_link->data = NULL; new_order[new_pos++] = old_pos; } if (new_pos == n_children) { gtk_list_store_reorder (GTK_LIST_STORE (store), new_order); g_signal_emit ( store, signals[SERVICES_REORDERED], 0, use_default_order); } g_free (new_order); if (current_order != NULL) g_queue_free (current_order); if (default_order != NULL) g_queue_free (default_order); } gint e_mail_account_store_compare_services (EMailAccountStore *store, CamelService *service_a, CamelService *service_b) { GtkTreeModel *model; GtkTreePath *path_a; GtkTreePath *path_b; GtkTreeIter iter_a; GtkTreeIter iter_b; gboolean iter_a_set; gboolean iter_b_set; gint result; g_return_val_if_fail (E_IS_MAIL_ACCOUNT_STORE (store), -1); g_return_val_if_fail (CAMEL_IS_SERVICE (service_a), -1); g_return_val_if_fail (CAMEL_IS_SERVICE (service_b), -1); /* XXX This is horribly inefficient but should be * over a small enough set to not be noticable. */ iter_a_set = mail_account_store_get_iter (store, service_a, &iter_a); iter_b_set = mail_account_store_get_iter (store, service_b, &iter_b); if (!iter_a_set && !iter_b_set) return 0; if (!iter_a_set) return -1; if (!iter_b_set) return 1; model = GTK_TREE_MODEL (store); path_a = gtk_tree_model_get_path (model, &iter_a); path_b = gtk_tree_model_get_path (model, &iter_b); result = gtk_tree_path_compare (path_a, path_b); gtk_tree_path_free (path_a); gtk_tree_path_free (path_b); return result; } gboolean e_mail_account_store_load_sort_order (EMailAccountStore *store, GError **error) { GQueue service_queue = G_QUEUE_INIT; EMailSession *session; GKeyFile *key_file; const gchar *filename; gchar **service_uids; gboolean success = TRUE; gsize ii, length; g_return_val_if_fail (E_IS_MAIL_ACCOUNT_STORE (store), FALSE); session = e_mail_account_store_get_session (store); key_file = g_key_file_new (); filename = store->priv->sort_order_filename; if (g_file_test (filename, G_FILE_TEST_EXISTS)) success = g_key_file_load_from_file ( key_file, filename, G_KEY_FILE_NONE, error); if (!success) { g_key_file_free (key_file); return FALSE; } /* If the key is not present, length is set to zero. */ service_uids = g_key_file_get_string_list ( key_file, "Accounts", "SortOrder", &length, NULL); for (ii = 0; ii < length; ii++) { CamelService *service; service = camel_session_ref_service ( CAMEL_SESSION (session), service_uids[ii]); if (service != NULL) g_queue_push_tail (&service_queue, service); } e_mail_account_store_reorder_services (store, &service_queue); while (!g_queue_is_empty (&service_queue)) g_object_unref (g_queue_pop_head (&service_queue)); g_strfreev (service_uids); g_key_file_free (key_file); return TRUE; } gboolean e_mail_account_store_save_sort_order (EMailAccountStore *store, GError **error) { GKeyFile *key_file; GtkTreeModel *model; GtkTreeIter iter; const gchar **service_uids; const gchar *filename; gchar *contents; gboolean iter_set; gboolean success; gsize length; gsize ii = 0; g_return_val_if_fail (E_IS_MAIL_ACCOUNT_STORE (store), FALSE); model = GTK_TREE_MODEL (store); length = gtk_tree_model_iter_n_children (model, NULL); /* Empty store, nothing to save. */ if (length == 0) return TRUE; service_uids = g_new0 (const gchar *, length); iter_set = gtk_tree_model_get_iter_first (model, &iter); while (iter_set) { GValue value = G_VALUE_INIT; const gint column = E_MAIL_ACCOUNT_STORE_COLUMN_SERVICE; CamelService *service; gtk_tree_model_get_value (model, &iter, column, &value); service = g_value_get_object (&value); service_uids[ii++] = camel_service_get_uid (service); g_value_unset (&value); iter_set = gtk_tree_model_iter_next (model, &iter); } key_file = g_key_file_new (); filename = store->priv->sort_order_filename; g_key_file_set_string_list ( key_file, "Accounts", "SortOrder", service_uids, length); contents = g_key_file_to_data (key_file, &length, NULL); success = g_file_set_contents (filename, contents, length, error); g_free (contents); g_key_file_free (key_file); g_free (service_uids); return success; }