/* * e-contact-photo-source.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-contact-photo-source.h" #define E_CONTACT_PHOTO_SOURCE_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_CONTACT_PHOTO_SOURCE, EContactPhotoSourcePrivate)) typedef struct _AsyncContext AsyncContext; struct _EContactPhotoSourcePrivate { EClientCache *client_cache; ESource *source; }; struct _AsyncContext { EBookClient *client; gchar *query_string; GInputStream *stream; GCancellable *cancellable; gint priority; }; enum { PROP_0, PROP_CLIENT_CACHE, PROP_SOURCE }; /* Forward Declarations */ static void e_contact_photo_source_interface_init (EPhotoSourceInterface *interface); G_DEFINE_DYNAMIC_TYPE_EXTENDED ( EContactPhotoSource, e_contact_photo_source, G_TYPE_OBJECT, 0, G_IMPLEMENT_INTERFACE_DYNAMIC ( E_TYPE_PHOTO_SOURCE, e_contact_photo_source_interface_init)) static void async_context_free (AsyncContext *async_context) { g_clear_object (&async_context->client); g_free (async_context->query_string); g_clear_object (&async_context->stream); g_clear_object (&async_context->cancellable); g_slice_free (AsyncContext, async_context); } static EContactPhoto * contact_photo_source_extract_photo (EContact *contact, gint *out_priority) { EContactPhoto *photo; photo = e_contact_get (contact, E_CONTACT_PHOTO); *out_priority = G_PRIORITY_HIGH; if (photo == NULL) { photo = e_contact_get (contact, E_CONTACT_LOGO); *out_priority = G_PRIORITY_LOW; } return photo; } static void contact_photo_source_get_photo_thread (GSimpleAsyncResult *simple, GObject *source_object, GCancellable *cancellable) { AsyncContext *async_context; GSList *slist = NULL; GSList *slink; GError *error = NULL; async_context = g_simple_async_result_get_op_res_gpointer (simple); e_book_client_get_contacts_sync ( async_context->client, async_context->query_string, &slist, cancellable, &error); if (error != NULL) { g_warn_if_fail (slist == NULL); g_simple_async_result_take_error (simple, error); return; } /* See if any of the contacts have a photo. */ for (slink = slist; slink != NULL; slink = g_slist_next (slink)) { EContact *contact = E_CONTACT (slink->data); GInputStream *stream = NULL; EContactPhoto *photo; photo = contact_photo_source_extract_photo ( contact, &async_context->priority); if (photo == NULL) continue; /* Stream takes ownership of the inlined data. */ if (photo->type == E_CONTACT_PHOTO_TYPE_INLINED) { stream = g_memory_input_stream_new_from_data ( photo->data.inlined.data, photo->data.inlined.length, (GDestroyNotify) g_free); photo->data.inlined.data = NULL; photo->data.inlined.length = 0; } else { GFileInputStream *file_stream; GFile *file; file = g_file_new_for_uri (photo->data.uri); /* Disregard errors and proceed as * though the contact has no photo. */ /* XXX Return type should have been GInputStream. */ file_stream = g_file_read (file, cancellable, NULL); if (file_stream != NULL) stream = G_INPUT_STREAM (file_stream); g_object_unref (file); } e_contact_photo_free (photo); /* Stop on the first input stream. */ if (stream != NULL) { async_context->stream = g_object_ref (stream); g_object_unref (stream); break; } } g_slist_free_full (slist, (GDestroyNotify) g_object_unref); } static void contact_photo_source_get_client_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GSimpleAsyncResult *simple; AsyncContext *async_context; EClient *client; GError *error = NULL; simple = G_SIMPLE_ASYNC_RESULT (user_data); async_context = g_simple_async_result_get_op_res_gpointer (simple); 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) { async_context->client = g_object_ref (client); /* The rest of the operation we can run from a * worker thread to keep the logic flow simple. */ g_simple_async_result_run_in_thread ( simple, contact_photo_source_get_photo_thread, G_PRIORITY_DEFAULT, async_context->cancellable); g_object_unref (client); } else { g_simple_async_result_take_error (simple, error); g_simple_async_result_complete_in_idle (simple); } g_object_unref (simple); } static void contact_photo_source_set_client_cache (EContactPhotoSource *photo_source, EClientCache *client_cache) { g_return_if_fail (E_IS_CLIENT_CACHE (client_cache)); g_return_if_fail (photo_source->priv->client_cache == NULL); photo_source->priv->client_cache = g_object_ref (client_cache); } static void contact_photo_source_set_source (EContactPhotoSource *photo_source, ESource *source) { g_return_if_fail (E_IS_SOURCE (source)); g_return_if_fail (photo_source->priv->source == NULL); photo_source->priv->source = g_object_ref (source); } static void contact_photo_source_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_CLIENT_CACHE: contact_photo_source_set_client_cache ( E_CONTACT_PHOTO_SOURCE (object), g_value_get_object (value)); return; case PROP_SOURCE: contact_photo_source_set_source ( E_CONTACT_PHOTO_SOURCE (object), g_value_get_object (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void contact_photo_source_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_CLIENT_CACHE: g_value_take_object ( value, e_contact_photo_source_ref_client_cache ( E_CONTACT_PHOTO_SOURCE (object))); return; case PROP_SOURCE: g_value_take_object ( value, e_contact_photo_source_ref_source ( E_CONTACT_PHOTO_SOURCE (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void contact_photo_source_dispose (GObject *object) { EContactPhotoSourcePrivate *priv; priv = E_CONTACT_PHOTO_SOURCE_GET_PRIVATE (object); g_clear_object (&priv->client_cache); g_clear_object (&priv->source); /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_contact_photo_source_parent_class)->dispose (object); } static void contact_photo_source_get_photo (EPhotoSource *photo_source, const gchar *email_address, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *simple; AsyncContext *async_context; EClientCache *client_cache; ESourceRegistry *registry; EBookQuery *book_query; ESource *source; book_query = e_book_query_field_test ( E_CONTACT_EMAIL, E_BOOK_QUERY_IS, email_address); async_context = g_slice_new0 (AsyncContext); async_context->query_string = e_book_query_to_string (book_query); if (G_IS_CANCELLABLE (cancellable)) async_context->cancellable = g_object_ref (cancellable); e_book_query_unref (book_query); simple = g_simple_async_result_new ( G_OBJECT (photo_source), callback, user_data, contact_photo_source_get_photo); g_simple_async_result_set_check_cancellable (simple, cancellable); g_simple_async_result_set_op_res_gpointer ( simple, async_context, (GDestroyNotify) async_context_free); client_cache = e_contact_photo_source_ref_client_cache ( E_CONTACT_PHOTO_SOURCE (photo_source)); registry = e_client_cache_ref_registry (client_cache); source = e_contact_photo_source_ref_source ( E_CONTACT_PHOTO_SOURCE (photo_source)); if (e_source_registry_check_enabled (registry, source)) { /* Obtain the EClient asynchronously. If an instance needs * to be created, it's more likely created in a thread with * a main loop so signal emissions can work. */ e_client_cache_get_client ( client_cache, source, E_SOURCE_EXTENSION_ADDRESS_BOOK, cancellable, contact_photo_source_get_client_cb, g_object_ref (simple)); } else { /* Return no result if the source is disabled. */ g_simple_async_result_complete_in_idle (simple); } g_object_unref (client_cache); g_object_unref (registry); g_object_unref (source); g_object_unref (simple); } static gboolean contact_photo_source_get_photo_finish (EPhotoSource *photo_source, GAsyncResult *result, GInputStream **out_stream, gint *out_priority, GError **error) { GSimpleAsyncResult *simple; AsyncContext *async_context; g_return_val_if_fail ( g_simple_async_result_is_valid ( result, G_OBJECT (photo_source), contact_photo_source_get_photo), FALSE); simple = G_SIMPLE_ASYNC_RESULT (result); async_context = g_simple_async_result_get_op_res_gpointer (simple); if (g_simple_async_result_propagate_error (simple, error)) return FALSE; if (async_context->stream != NULL) { *out_stream = g_object_ref (async_context->stream); if (out_priority != NULL) *out_priority = async_context->priority; } else { *out_stream = NULL; } return TRUE; } static void e_contact_photo_source_class_init (EContactPhotoSourceClass *class) { GObjectClass *object_class; g_type_class_add_private (class, sizeof (EContactPhotoSourcePrivate)); object_class = G_OBJECT_CLASS (class); object_class->set_property = contact_photo_source_set_property; object_class->get_property = contact_photo_source_get_property; object_class->dispose = contact_photo_source_dispose; 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_object_class_install_property ( object_class, PROP_SOURCE, g_param_spec_object ( "source", "Source", "An address book source", E_TYPE_SOURCE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } static void e_contact_photo_source_class_finalize (EContactPhotoSourceClass *class) { } static void e_contact_photo_source_interface_init (EPhotoSourceInterface *interface) { interface->get_photo = contact_photo_source_get_photo; interface->get_photo_finish = contact_photo_source_get_photo_finish; } static void e_contact_photo_source_init (EContactPhotoSource *photo_source) { photo_source->priv = E_CONTACT_PHOTO_SOURCE_GET_PRIVATE (photo_source); } void e_contact_photo_source_type_register (GTypeModule *type_module) { /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration * function, so we have to wrap it with a public function in * order to register types from a separate compilation unit. */ e_contact_photo_source_register_type (type_module); } EPhotoSource * e_contact_photo_source_new (EClientCache *client_cache, ESource *source) { g_return_val_if_fail (E_IS_CLIENT_CACHE (client_cache), NULL); g_return_val_if_fail (E_IS_SOURCE (source), NULL); return g_object_new ( E_TYPE_CONTACT_PHOTO_SOURCE, "client-cache", client_cache, "source", source, NULL); } EClientCache * e_contact_photo_source_ref_client_cache (EContactPhotoSource *photo_source) { g_return_val_if_fail (E_IS_CONTACT_PHOTO_SOURCE (photo_source), NULL); return g_object_ref (photo_source->priv->client_cache); } ESource * e_contact_photo_source_ref_source (EContactPhotoSource *photo_source) { g_return_val_if_fail (E_IS_CONTACT_PHOTO_SOURCE (photo_source), NULL); return g_object_ref (photo_source->priv->source); }