/* * e-client-selector.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 * */ /** * SECTION: e-client-selector * @include: e-util/e-util.h * @short_description: Tree view of #EClient instances * * #EClientSelector extends the functionality of #ESourceSelector by * utilizing an #EClientCache to display status information about the * backends associated with the displayed data sources. **/ #include "e-client-selector.h" #define E_CLIENT_SELECTOR_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_CLIENT_SELECTOR, EClientSelectorPrivate)) struct _EClientSelectorPrivate { EClientCache *client_cache; gulong backend_died_handler_id; gulong client_created_handler_id; gulong client_notify_online_handler_id; }; enum { PROP_0, PROP_CLIENT_CACHE }; G_DEFINE_TYPE ( EClientSelector, e_client_selector, E_TYPE_SOURCE_SELECTOR) static void client_selector_update_status_icon_cb (GtkTreeViewColumn *column, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data) { GtkWidget *tree_view; EClient *client; const gchar *icon_name = NULL; tree_view = gtk_tree_view_column_get_tree_view (column); client = e_client_selector_ref_cached_client_by_iter ( E_CLIENT_SELECTOR (tree_view), iter); if (client != NULL) { if (e_client_is_online (client)) icon_name = "network-idle-symbolic"; else icon_name = "network-offline-symbolic"; g_object_unref (client); } else { ESource *source; /* No client... did the backend die? */ source = e_source_selector_ref_source_by_iter ( E_SOURCE_SELECTOR (tree_view), iter); if (source != NULL) { gboolean dead_backend; dead_backend = e_client_selector_is_backend_dead ( E_CLIENT_SELECTOR (tree_view), source); if (dead_backend) icon_name = "network-error-symbolic"; g_object_unref (source); } } if (icon_name != NULL) { GIcon *icon; /* Use fallbacks if symbolic icons are not available. */ icon = g_themed_icon_new_with_default_fallbacks (icon_name); g_object_set (renderer, "gicon", icon, NULL); g_object_unref (icon); } else { g_object_set (renderer, "gicon", NULL, NULL); } } static void client_selector_update_row (EClientSelector *selector, EClient *client) { ESource *source; source = e_client_get_source (client); e_source_selector_update_row (E_SOURCE_SELECTOR (selector), source); } static void client_selector_backend_died_cb (EClientCache *client_cache, EClient *client, EAlert *alert, EClientSelector *selector) { client_selector_update_row (selector, client); } static void client_selector_client_created_cb (EClientCache *client_cache, EClient *client, EClientSelector *selector) { client_selector_update_row (selector, client); } static void client_selector_client_notify_cb (EClientCache *client_cache, EClient *client, GParamSpec *pspec, EClientSelector *selector) { client_selector_update_row (selector, client); } static void client_selector_prefetch_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { EClient *client; /* We don't care about errors here. this is just to try and * get the EClient instances we'll need cached ahead of time. */ client = e_client_selector_get_client_finish ( E_CLIENT_SELECTOR (source_object), result, NULL); g_clear_object (&client); } static void client_selector_set_client_cache (EClientSelector *selector, EClientCache *client_cache) { g_return_if_fail (E_IS_CLIENT_CACHE (client_cache)); g_return_if_fail (selector->priv->client_cache == NULL); selector->priv->client_cache = g_object_ref (client_cache); } static void client_selector_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_CLIENT_CACHE: client_selector_set_client_cache ( E_CLIENT_SELECTOR (object), g_value_get_object (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void client_selector_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_CLIENT_CACHE: g_value_take_object ( value, e_client_selector_ref_client_cache ( E_CLIENT_SELECTOR (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void client_selector_dispose (GObject *object) { EClientSelectorPrivate *priv; priv = E_CLIENT_SELECTOR_GET_PRIVATE (object); if (priv->backend_died_handler_id > 0) { g_signal_handler_disconnect ( priv->client_cache, priv->backend_died_handler_id); priv->backend_died_handler_id = 0; } if (priv->client_created_handler_id > 0) { g_signal_handler_disconnect ( priv->client_cache, priv->client_created_handler_id); priv->client_created_handler_id = 0; } if (priv->client_notify_online_handler_id > 0) { g_signal_handler_disconnect ( priv->client_cache, priv->client_notify_online_handler_id); priv->client_notify_online_handler_id = 0; } g_clear_object (&priv->client_cache); /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_client_selector_parent_class)->dispose (object); } static void client_selector_constructed (GObject *object) { EClientSelector *selector; EClientCache *client_cache; ESourceRegistry *registry; GtkTreeView *tree_view; GtkTreeViewColumn *column; GtkCellRenderer *renderer; const gchar *extension_name; GList *list, *link; gulong handler_id; selector = E_CLIENT_SELECTOR (object); client_cache = e_client_selector_ref_client_cache (selector); /* Chain up to parent's constructed() method. */ G_OBJECT_CLASS (e_client_selector_parent_class)->constructed (object); /* Append an icon to hint at backend status. */ tree_view = GTK_TREE_VIEW (object); column = gtk_tree_view_column_new (); gtk_tree_view_append_column (tree_view, column); renderer = gtk_cell_renderer_pixbuf_new (); gtk_tree_view_column_pack_start (column, renderer, FALSE); gtk_tree_view_column_set_cell_data_func ( column, renderer, client_selector_update_status_icon_cb, NULL, (GDestroyNotify) NULL); /* Listen for signals that may change the icon. */ handler_id = g_signal_connect ( client_cache, "backend-died", G_CALLBACK (client_selector_backend_died_cb), selector); selector->priv->backend_died_handler_id = handler_id; handler_id = g_signal_connect ( client_cache, "client-created", G_CALLBACK (client_selector_client_created_cb), selector); selector->priv->client_created_handler_id = handler_id; handler_id = g_signal_connect ( client_cache, "client-notify::online", G_CALLBACK (client_selector_client_notify_cb), selector); selector->priv->client_notify_online_handler_id = handler_id; g_object_unref (client_cache); /* Pre-fetch EClient instances for all relevant ESources. * This is just to try and make sure they get cache ahead * of time, so we need not worry about reporting errors. */ registry = e_source_selector_get_registry ( E_SOURCE_SELECTOR (selector)); extension_name = e_source_selector_get_extension_name ( E_SOURCE_SELECTOR (selector)); list = e_source_registry_list_sources (registry, extension_name); for (link = list; link != NULL; link = g_list_next (link)) { ESource *source = E_SOURCE (link->data); ESourceExtension *extension; extension = e_source_get_extension (source, extension_name); /* If selectable, skip unselected sources. */ if (E_IS_SOURCE_SELECTABLE (extension)) { ESourceSelectable *selectable; selectable = E_SOURCE_SELECTABLE (extension); if (!e_source_selectable_get_selected (selectable)) continue; } e_client_selector_get_client ( selector, source, NULL, client_selector_prefetch_cb, NULL); } g_list_free_full (list, (GDestroyNotify) g_object_unref); } static void e_client_selector_class_init (EClientSelectorClass *class) { GObjectClass *object_class; g_type_class_add_private (class, sizeof (EClientSelectorPrivate)); object_class = G_OBJECT_CLASS (class); object_class->set_property = client_selector_set_property; object_class->get_property = client_selector_get_property; object_class->dispose = client_selector_dispose; object_class->constructed = client_selector_constructed; /** * EClientSelector:client-cache: * * Cache of shared #EClient instances. **/ g_object_class_install_property ( object_class, PROP_CLIENT_CACHE, g_param_spec_object ( "client-cache", "Client Cache", "Cache of shared EClient instances", E_TYPE_CLIENT_CACHE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); } static void e_client_selector_init (EClientSelector *selector) { selector->priv = E_CLIENT_SELECTOR_GET_PRIVATE (selector); } /** * e_client_selector_new: * @client_cache: an #EClientCache * @extension_name: the name of an #ESource extension * * Displays a list of sources having an extension named @extension_name, * along with status information about the backends associated with them. * The sources are grouped by backend or groupware account, which are * described by the parent source. * * Returns: a new #EClientSelector **/ GtkWidget * e_client_selector_new (EClientCache *client_cache, const gchar *extension_name) { ESourceRegistry *registry; GtkWidget *widget; g_return_val_if_fail (E_IS_CLIENT_CACHE (client_cache), NULL); g_return_val_if_fail (extension_name != NULL, NULL); registry = e_client_cache_ref_registry (client_cache); widget = g_object_new ( E_TYPE_CLIENT_SELECTOR, "client-cache", client_cache, "extension-name", extension_name, "registry", registry, NULL); g_object_unref (registry); return widget; } /** * e_client_selector_ref_client_cache: * @selector: an #EClientSelector * * Returns the #EClientCache passed to e_client_selector_new(). * * The returned #EClientCache is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Returns: an #EClientCache **/ EClientCache * e_client_selector_ref_client_cache (EClientSelector *selector) { g_return_val_if_fail (E_IS_CLIENT_SELECTOR (selector), NULL); return g_object_ref (selector->priv->client_cache); } /** * e_client_selector_get_client_sync: * @selector: an #ESourceSelector * @source: an #ESource * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Obtains a shared #EClient instance for @source, or else creates a new * #EClient instance to be shared. * * The #ESourceSelector:extension-name property determines the type of * #EClient to obtain. See e_client_cache_get_client_sync() for a list * of valid extension names. * * If a request for the same @source and #ESourceSelector:extension-name * is already in progress when this function is called, this request will * "piggyback" on the in-progress request such that they will both succeed * or fail simultaneously. * * Unreference the returned #EClient with g_object_unref() when finished * with it. If an error occurs, the function will set @error and return * %NULL. * * Returns: an #EClient, or %NULL **/ EClient * e_client_selector_get_client_sync (EClientSelector *selector, ESource *source, GCancellable *cancellable, GError **error) { EAsyncClosure *closure; GAsyncResult *result; EClient *client; g_return_val_if_fail (E_IS_CLIENT_SELECTOR (selector), NULL); g_return_val_if_fail (E_IS_SOURCE (source), NULL); closure = e_async_closure_new (); e_client_selector_get_client ( selector, source, cancellable, e_async_closure_callback, closure); result = e_async_closure_wait (closure); client = e_client_selector_get_client_finish ( selector, result, error); e_async_closure_free (closure); return client; } /* Helper for e_client_selector_get_client() */ static void client_selector_get_client_done_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { EClient *client; GSimpleAsyncResult *simple; GError *error = NULL; simple = G_SIMPLE_ASYNC_RESULT (user_data); client = e_client_cache_get_client_finish ( E_CLIENT_CACHE (source_object), result, &error); /* Sanity check. */ g_return_if_fail ( ((client != NULL) && (error == NULL)) || ((client == NULL) && (error != NULL))); if (client != NULL) { g_simple_async_result_set_op_res_gpointer ( simple, g_object_ref (client), (GDestroyNotify) g_object_unref); g_object_unref (client); } if (error != NULL) g_simple_async_result_take_error (simple, error); g_simple_async_result_complete (simple); g_object_unref (simple); } /** * e_client_selector_get_client: * @selector: an #ESourceSelector * @source: an #ESource * @cancellable: optional #GCancellable object, or %NULL * @callback: a #GAsyncReadyCallback to call when the request is satisfied * @user_data: data to pass to the callback function * * Asynchronously obtains a shared #EClient instance for @source, or else * creates a new #EClient instance to be shared. * * The #ESourceSelector:extension-name property determines the type of * #EClient to obtain. See e_client_cache_get_client_sync() for a list * of valid extension names. * * If a request for the same @source and #ESourceSelector:extension-name * is already in progress when this function is called, this request will * "piggyback" on the in-progress request such that they will both succeed * or fail simultaneously. * * When the operation is finished, @callback will be called. You can * then call e_client_selector_get_client_finish() to get the result of * the operation. **/ void e_client_selector_get_client (EClientSelector *selector, ESource *source, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { EClientCache *client_cache; GSimpleAsyncResult *simple; const gchar *extension_name; g_return_if_fail (E_IS_CLIENT_SELECTOR (selector)); g_return_if_fail (E_IS_SOURCE (source)); simple = g_simple_async_result_new ( G_OBJECT (selector), callback, user_data, e_client_selector_get_client); g_simple_async_result_set_check_cancellable (simple, cancellable); extension_name = e_source_selector_get_extension_name ( E_SOURCE_SELECTOR (selector)); client_cache = e_client_selector_ref_client_cache (selector); e_client_cache_get_client ( client_cache, source, extension_name, cancellable, client_selector_get_client_done_cb, g_object_ref (simple)); g_object_unref (client_cache); g_object_unref (simple); } /** * e_client_selector_get_client_finish: * @selector: an #EClientSelector * @result: a #GAsyncResult * @error: return location for a #GError, or %NULL * * Finishes the operation started with e_client_selector_get_client(). * * Unreference the returned #EClient with g_object_unref() when finished * with it. If an error occurred, the function will set @error and return * %NULL. * * Returns: an #EClient, or %NULL **/ EClient * e_client_selector_get_client_finish (EClientSelector *selector, GAsyncResult *result, GError **error) { GSimpleAsyncResult *simple; EClient *client; g_return_val_if_fail ( g_simple_async_result_is_valid ( result, G_OBJECT (selector), e_client_selector_get_client), NULL); simple = G_SIMPLE_ASYNC_RESULT (result); client = g_simple_async_result_get_op_res_gpointer (simple); if (g_simple_async_result_propagate_error (simple, error)) return NULL; g_return_val_if_fail (client != NULL, NULL); return g_object_ref (client); } /** * e_client_selector_ref_cached_client: * @selector: an #EClientSelector * @source: an #ESource * * Returns a shared #EClient instance for @source and the value of * #ESourceSelector:extension-name if such an instance is already cached, * or else %NULL. This function does not create a new #EClient instance, * and therefore does not block. * * The returned #EClient is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Returns: an #EClient, or %NULL **/ EClient * e_client_selector_ref_cached_client (EClientSelector *selector, ESource *source) { EClient *client; EClientCache *client_cache; const gchar *extension_name; g_return_val_if_fail (E_IS_CLIENT_SELECTOR (selector), NULL); g_return_val_if_fail (E_IS_SOURCE (source), NULL); extension_name = e_source_selector_get_extension_name ( E_SOURCE_SELECTOR (selector)); client_cache = e_client_selector_ref_client_cache (selector); client = e_client_cache_ref_cached_client ( client_cache, source, extension_name); g_object_unref (client_cache); return client; } /** * e_client_selector_ref_cached_client_by_iter: * @selector: an #EClientSelector * @iter: a #GtkTreeIter * * Returns a shared #EClient instance for the #ESource in the tree model * row pointed to by @iter and the value of #ESourceSelector:extension-name * if such an instance is already cached, or else %NULL. This function does * not create a new #EClient instance, and therefore does not block. * * The returned #EClient is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Returns: an #EClient, or %NULL **/ EClient * e_client_selector_ref_cached_client_by_iter (EClientSelector *selector, GtkTreeIter *iter) { EClient *client = NULL; ESource *source; g_return_val_if_fail (E_IS_CLIENT_SELECTOR (selector), NULL); g_return_val_if_fail (iter != NULL, NULL); source = e_source_selector_ref_source_by_iter ( E_SOURCE_SELECTOR (selector), iter); if (source != NULL) { client = e_client_selector_ref_cached_client ( selector, source); g_object_unref (source); } return client; } /** * e_client_selector_is_backend_dead: * @selector: an #EClientSelector * @source: an #ESource * * Returns %TRUE if an #EClient instance for @source and the value of * #ESourceSelector:extension-name was recently discarded after having * emitted a #EClient:backend-died signal, and a replacement #EClient * instance has not yet been created. * * Returns: whether the backend for @source died **/ gboolean e_client_selector_is_backend_dead (EClientSelector *selector, ESource *source) { EClientCache *client_cache; const gchar *extension_name; gboolean dead_backend; g_return_val_if_fail (E_IS_CLIENT_SELECTOR (selector), FALSE); g_return_val_if_fail (E_IS_SOURCE (source), FALSE); extension_name = e_source_selector_get_extension_name ( E_SOURCE_SELECTOR (selector)); client_cache = e_client_selector_ref_client_cache (selector); dead_backend = e_client_cache_is_backend_dead ( client_cache, source, extension_name); g_object_unref (client_cache); return dead_backend; }