aboutsummaryrefslogblamecommitdiffstats
path: root/modules/contact-photos/e-contact-photo-source.c
blob: e124046cee7fe27ac174e5abd25a6363db8b3b9f (plain) (tree)































                                                                             
                            

                             
                                  
























                                                                           
                                                

                                                
                                                     

























                                                                  
                                    



                             

                                                                           
                                         
                                      





                                                                 
                       


















































                                                                            










                                                           
 



























                                                                           




























































































                                                                               

                                   
                               
                        






                                                                          


                                                                        










                                                                            
























                                                                           































































































































                                                                               
/*
 * 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);
}