/*
* 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 <http://www.gnu.org/licenses/>
*
*/
#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);
}