[evolution] Add EPhotoCache.



commit c03ac675117f81caa051df681a3f763c66714317
Author: Matthew Barnes <mbarnes redhat com>
Date:   Mon Feb 25 14:41:07 2013 -0500

    Add EPhotoCache.
    
    Caches contact photos by email address.
    
    Replaces the disastrous implementation in e-mail-utils.c.

 doc/reference/libeutil/libeutil-docs.sgml    |    1 +
 doc/reference/libeutil/libeutil-sections.txt |   25 +
 doc/reference/libeutil/libeutil.types        |    1 +
 e-util/Makefile.am                           |    2 +
 e-util/e-photo-cache.c                       |  928 ++++++++++++++++++++++++++
 e-util/e-photo-cache.h                       |   95 +++
 e-util/e-util.h                              |    1 +
 7 files changed, 1053 insertions(+), 0 deletions(-)
---
diff --git a/doc/reference/libeutil/libeutil-docs.sgml b/doc/reference/libeutil/libeutil-docs.sgml
index 3ec7742..468fb98 100644
--- a/doc/reference/libeutil/libeutil-docs.sgml
+++ b/doc/reference/libeutil/libeutil-docs.sgml
@@ -246,6 +246,7 @@
     <xi:include href="xml/e-name-selector.xml"/>
     <xi:include href="xml/e-online-button.xml"/>
     <xi:include href="xml/e-paned.xml"/>
+    <xi:include href="xml/e-photo-cache.xml"/>
     <xi:include href="xml/e-picture-gallery.xml"/>
     <xi:include href="xml/e-poolv.xml"/>
     <xi:include href="xml/e-popup-action.xml"/>
diff --git a/doc/reference/libeutil/libeutil-sections.txt b/doc/reference/libeutil/libeutil-sections.txt
index ca98eb5..bbd1467 100644
--- a/doc/reference/libeutil/libeutil-sections.txt
+++ b/doc/reference/libeutil/libeutil-sections.txt
@@ -2418,6 +2418,31 @@ e_passwords_ask_password
 </SECTION>
 
 <SECTION>
+<FILE>e-photo-cache</FILE>
+<TITLE>EPhotoCache</TITLE>
+EPhotoCache
+e_photo_cache_new
+e_photo_cache_ref_client_cache
+e_photo_cache_get_local_only
+e_photo_cache_set_local_only
+e_photo_cache_get_photo_sync
+e_photo_cache_get_photo
+e_photo_cache_get_photo_finish
+e_photo_cache_remove
+<SUBSECTION Standard>
+E_PHOTO_CACHE
+E_IS_PHOTO_CACHE
+E_TYPE_PHOTO_CACHE
+E_PHOTO_CACHE_CLASS
+E_IS_PHOTO_CACHE_CLASS
+E_PHOTO_CACHE_GET_CLASS
+EPhotoCacheClass
+e_photo_cache_get_type
+<SUBSECTION Private>
+EPhotoCachePrivate
+</SECTION>
+
+<SECTION>
 <FILE>e-picture-gallery</FILE>
 <TITLE>EPictureGallery</TITLE>
 EPictureGallery
diff --git a/doc/reference/libeutil/libeutil.types b/doc/reference/libeutil/libeutil.types
index bfbbfd9..fdf7494 100644
--- a/doc/reference/libeutil/libeutil.types
+++ b/doc/reference/libeutil/libeutil.types
@@ -89,6 +89,7 @@ e_name_selector_list_get_type
 e_name_selector_model_get_type
 e_online_button_get_type
 e_paned_get_type
+e_photo_cache_get_type
 e_picture_gallery_get_type
 e_plugin_get_type
 e_plugin_hook_get_type
diff --git a/e-util/Makefile.am b/e-util/Makefile.am
index f802218..3eca443 100644
--- a/e-util/Makefile.am
+++ b/e-util/Makefile.am
@@ -208,6 +208,7 @@ eutilinclude_HEADERS =  \
        e-online-button.h \
        e-paned.h \
        e-passwords.h \
+       e-photo-cache.h \
        e-picture-gallery.h \
        e-plugin-ui.h \
        e-plugin.h \
@@ -454,6 +455,7 @@ libeutil_la_SOURCES = \
        e-online-button.c \
        e-paned.c \
        e-passwords.c \
+       e-photo-cache.c \
        e-picture-gallery.c \
        e-plugin-ui.c \
        e-plugin.c \
diff --git a/e-util/e-photo-cache.c b/e-util/e-photo-cache.c
new file mode 100644
index 0000000..b7398c0
--- /dev/null
+++ b/e-util/e-photo-cache.c
@@ -0,0 +1,928 @@
+/*
+ * e-photo-cache.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/>
+ *
+ */
+
+/**
+ * SECTION: e-photo-cache
+ * @include: e-util/e-util.h
+ * @short_description: Search for photos by email address
+ *
+ * #EPhotoCache helps search for contact photo or logo images associated
+ * with an email address.
+ *
+ * A limited internal cache is employed to speed up searches for recently
+ * searched email addresses.  The exact caching semantics are private and
+ * subject to change.
+ **/
+
+#include "e-photo-cache.h"
+
+#include <string.h>
+#include <libebackend/libebackend.h>
+
+#define E_PHOTO_CACHE_GET_PRIVATE(obj) \
+       (G_TYPE_INSTANCE_GET_PRIVATE \
+       ((obj), E_TYPE_PHOTO_CACHE, EPhotoCachePrivate))
+
+/* How many email addresses we track at once, regardless of whether
+ * the email address has a photo.  As new cache entries are added, we
+ * discard the least recently accessed entries to keep the cache size
+ * within the limit. */
+#define MAX_CACHE_SIZE 20
+
+typedef struct _AsyncContext AsyncContext;
+typedef struct _PhotoData PhotoData;
+
+struct _EPhotoCachePrivate {
+       EClientCache *client_cache;
+       gboolean local_only;
+
+       GHashTable *photo_ht;
+       GQueue photo_ht_keys;
+       GMutex photo_ht_lock;
+};
+
+struct _AsyncContext {
+       gchar *email_address;
+       GInputStream *input_stream;
+};
+
+struct _PhotoData {
+       volatile gint ref_count;
+       GMutex lock;
+       EContactPhoto *photo;
+       gboolean photo_is_set;
+};
+
+enum {
+       PROP_0,
+       PROP_CLIENT_CACHE,
+       PROP_LOCAL_ONLY
+};
+
+G_DEFINE_TYPE_WITH_CODE (
+       EPhotoCache,
+       e_photo_cache,
+       G_TYPE_OBJECT,
+       G_IMPLEMENT_INTERFACE (
+               E_TYPE_EXTENSIBLE, NULL))
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+       g_free (async_context->email_address);
+
+       if (async_context->input_stream != NULL)
+               g_object_unref (async_context->input_stream);
+
+       g_slice_free (AsyncContext, async_context);
+}
+
+static PhotoData *
+photo_data_new (void)
+{
+       PhotoData *photo_data;
+
+       photo_data = g_slice_new0 (PhotoData);
+       photo_data->ref_count = 1;
+
+       g_mutex_init (&photo_data->lock);
+
+       return photo_data;
+}
+
+static PhotoData *
+photo_data_ref (PhotoData *photo_data)
+{
+       g_return_val_if_fail (photo_data != NULL, NULL);
+       g_return_val_if_fail (photo_data->ref_count > 0, NULL);
+
+       g_atomic_int_inc (&photo_data->ref_count);
+
+       return photo_data;
+}
+
+static void
+photo_data_unref (PhotoData *photo_data)
+{
+       g_return_if_fail (photo_data != NULL);
+       g_return_if_fail (photo_data->ref_count > 0);
+
+       if (g_atomic_int_dec_and_test (&photo_data->ref_count)) {
+               if (photo_data->photo != NULL)
+                       e_contact_photo_free (photo_data->photo);
+
+               g_mutex_clear (&photo_data->lock);
+
+               g_slice_free (PhotoData, photo_data);
+       }
+}
+
+static gboolean
+photo_data_dup_photo (PhotoData *photo_data,
+                      EContactPhoto **out_photo)
+{
+       gboolean photo_is_set;
+
+       g_return_val_if_fail (out_photo != NULL, FALSE);
+
+       g_mutex_lock (&photo_data->lock);
+
+       if (photo_data->photo != NULL)
+               *out_photo = e_contact_photo_copy (photo_data->photo);
+       else
+               *out_photo = NULL;
+
+       photo_is_set = photo_data->photo_is_set;
+
+       g_mutex_unlock (&photo_data->lock);
+
+       return photo_is_set;
+}
+
+static void
+photo_data_set_photo (PhotoData *photo_data,
+                      EContactPhoto *photo)
+{
+       g_mutex_lock (&photo_data->lock);
+
+       if (photo_data->photo != NULL) {
+               e_contact_photo_free (photo_data->photo);
+               photo_data->photo = NULL;
+       }
+
+       if (photo != NULL)
+               photo_data->photo = e_contact_photo_copy (photo);
+
+       photo_data->photo_is_set = TRUE;
+
+       g_mutex_unlock (&photo_data->lock);
+}
+
+static gchar *
+photo_ht_normalize_key (const gchar *email_address)
+{
+       gchar *lowercase_email_address;
+       gchar *collation_key;
+
+       lowercase_email_address = g_utf8_strdown (email_address, -1);
+       collation_key = g_utf8_collate_key (lowercase_email_address, -1);
+       g_free (lowercase_email_address);
+
+       return collation_key;
+}
+
+static PhotoData *
+photo_ht_lookup (EPhotoCache *photo_cache,
+                 const gchar *email_address)
+{
+       GHashTable *photo_ht;
+       GQueue *photo_ht_keys;
+       PhotoData *photo_data;
+       gchar *key;
+
+       g_return_val_if_fail (email_address != NULL, NULL);
+
+       photo_ht = photo_cache->priv->photo_ht;
+       photo_ht_keys = &photo_cache->priv->photo_ht_keys;
+
+       key = photo_ht_normalize_key (email_address);
+
+       g_mutex_lock (&photo_cache->priv->photo_ht_lock);
+
+       photo_data = g_hash_table_lookup (photo_ht, key);
+
+       if (photo_data != NULL) {
+               GList *link;
+
+               photo_data_ref (photo_data);
+
+               /* Move the key to the head of the MRU queue. */
+               link = g_queue_find_custom (
+                       photo_ht_keys, key,
+                       (GCompareFunc) strcmp);
+               if (link != NULL) {
+                       g_queue_unlink (photo_ht_keys, link);
+                       g_queue_push_head_link (photo_ht_keys, link);
+               }
+       } else {
+               photo_data = photo_data_new ();
+
+               g_hash_table_insert (
+                       photo_ht, g_strdup (key),
+                       photo_data_ref (photo_data));
+
+               /* Push the key to the head of the MRU queue. */
+               g_queue_push_head (photo_ht_keys, g_strdup (key));
+
+               /* Trim the cache if necessary. */
+               while (g_queue_get_length (photo_ht_keys) > MAX_CACHE_SIZE) {
+                       gchar *oldest_key;
+
+                       oldest_key = g_queue_pop_tail (photo_ht_keys);
+                       g_hash_table_remove (photo_ht, oldest_key);
+                       g_free (oldest_key);
+               }
+       }
+
+       /* Hash table and queue sizes should be equal at all times. */
+       g_warn_if_fail (
+               g_hash_table_size (photo_ht) ==
+               g_queue_get_length (photo_ht_keys));
+
+       g_mutex_unlock (&photo_cache->priv->photo_ht_lock);
+
+       g_free (key);
+
+       return photo_data;
+}
+
+static gboolean
+photo_ht_remove (EPhotoCache *photo_cache,
+                 const gchar *email_address)
+{
+       GHashTable *photo_ht;
+       GQueue *photo_ht_keys;
+       gchar *key;
+       gboolean removed = FALSE;
+
+       g_return_val_if_fail (email_address != NULL, FALSE);
+
+       photo_ht = photo_cache->priv->photo_ht;
+       photo_ht_keys = &photo_cache->priv->photo_ht_keys;
+
+       key = photo_ht_normalize_key (email_address);
+
+       g_mutex_lock (&photo_cache->priv->photo_ht_lock);
+
+       if (g_hash_table_remove (photo_ht, key)) {
+               GList *link;
+
+               link = g_queue_find_custom (
+                       photo_ht_keys, key,
+                       (GCompareFunc) strcmp);
+               if (link != NULL) {
+                       g_free (link->data);
+                       g_queue_delete_link (photo_ht_keys, link);
+                       removed = TRUE;
+               }
+       }
+
+       /* Hash table and queue sizes should be equal at all times. */
+       g_warn_if_fail (
+               g_hash_table_size (photo_ht) ==
+               g_queue_get_length (photo_ht_keys));
+
+       g_mutex_unlock (&photo_cache->priv->photo_ht_lock);
+
+       g_free (key);
+
+       return removed;
+}
+
+static void
+photo_ht_remove_all (EPhotoCache *photo_cache)
+{
+       GHashTable *photo_ht;
+       GQueue *photo_ht_keys;
+
+       photo_ht = photo_cache->priv->photo_ht;
+       photo_ht_keys = &photo_cache->priv->photo_ht_keys;
+
+       g_mutex_lock (&photo_cache->priv->photo_ht_lock);
+
+       g_hash_table_remove_all (photo_ht);
+
+       while (!g_queue_is_empty (photo_ht_keys))
+               g_free (g_queue_pop_head (photo_ht_keys));
+
+       g_mutex_unlock (&photo_cache->priv->photo_ht_lock);
+}
+
+static EContactPhoto *
+photo_cache_extract_photo (EContact *contact)
+{
+       EContactPhoto *photo;
+
+       photo = e_contact_get (contact, E_CONTACT_PHOTO);
+       if (photo == NULL)
+               photo = e_contact_get (contact, E_CONTACT_LOGO);
+
+       return photo;
+}
+
+static GList *
+photo_cache_list_searchable_sources (EPhotoCache *photo_cache)
+{
+       EClientCache *client_cache;
+       ESourceRegistry *registry;
+       GList *list;
+
+       client_cache = e_photo_cache_ref_client_cache (photo_cache);
+       registry = e_client_cache_ref_registry (client_cache);
+
+       if (e_photo_cache_get_local_only (photo_cache)) {
+               ESource *source;
+
+               source = e_source_registry_ref_builtin_address_book (registry);
+               list = g_list_prepend (NULL, g_object_ref (source));
+               g_object_unref (source);
+       } else {
+               list = e_source_registry_list_sources (
+                       registry, E_SOURCE_EXTENSION_ADDRESS_BOOK);
+       }
+
+       g_object_unref (client_cache);
+       g_object_unref (registry);
+
+       return list;
+}
+
+static gboolean
+photo_cache_find_contacts (EPhotoCache *photo_cache,
+                           const gchar *email_address,
+                           GCancellable *cancellable,
+                           GQueue *out_contacts,
+                           GError **error)
+{
+       EClientCache *client_cache;
+       EBookQuery *book_query;
+       GList *list, *link;
+       gchar *book_query_string;
+       gboolean success = TRUE;
+
+       book_query = e_book_query_field_test (
+               E_CONTACT_EMAIL, E_BOOK_QUERY_IS, email_address);
+       book_query_string = e_book_query_to_string (book_query);
+       e_book_query_unref (book_query);
+
+       client_cache = e_photo_cache_ref_client_cache (photo_cache);
+
+       list = photo_cache_list_searchable_sources (photo_cache);
+
+       for (link = list; link != NULL; link = g_list_next (link)) {
+               ESource *source = E_SOURCE (link->data);
+               EClient *client;
+               GSList *contact_list = NULL;
+
+               /* Skip disabled sources. */
+               if (!e_source_get_enabled (source))
+                       continue;
+
+               client = e_client_cache_get_client_sync (
+                       client_cache, source,
+                       E_SOURCE_EXTENSION_ADDRESS_BOOK,
+                       cancellable, error);
+
+               if (client == NULL) {
+                       success = FALSE;
+                       break;
+               }
+
+               success = e_book_client_get_contacts_sync (
+                       E_BOOK_CLIENT (client), book_query_string,
+                       &contact_list, cancellable, error);
+
+               g_object_unref (client);
+
+               if (!success) {
+                       g_warn_if_fail (contact_list == NULL);
+                       break;
+               }
+
+               while (contact_list != NULL) {
+                       EContact *contact;
+
+                       /* Transfer ownership to queue. */
+                       contact = E_CONTACT (contact_list->data);
+                       g_queue_push_tail (out_contacts, contact);
+
+                       contact_list = g_slist_delete_link (
+                               contact_list, contact_list);
+               }
+       }
+
+       g_list_free_full (list, (GDestroyNotify) g_object_unref);
+
+       g_object_unref (client_cache);
+
+       g_free (book_query_string);
+
+       return success;
+}
+
+static GInputStream *
+photo_cache_new_stream_from_photo (EContactPhoto *photo,
+                                   GCancellable *cancellable,
+                                   GError **error)
+{
+       GInputStream *stream = NULL;
+
+       /* 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);
+
+               /* XXX Return type should have been GInputStream. */
+               file_stream = g_file_read (file, cancellable, error);
+               if (file_stream != NULL)
+                       stream = G_INPUT_STREAM (file_stream);
+
+               g_object_unref (file);
+       }
+
+       return stream;
+}
+
+static void
+photo_cache_set_client_cache (EPhotoCache *photo_cache,
+                              EClientCache *client_cache)
+{
+       g_return_if_fail (E_IS_CLIENT_CACHE (client_cache));
+       g_return_if_fail (photo_cache->priv->client_cache == NULL);
+
+       photo_cache->priv->client_cache = g_object_ref (client_cache);
+}
+
+static void
+photo_cache_set_property (GObject *object,
+                          guint property_id,
+                          const GValue *value,
+                          GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_CLIENT_CACHE:
+                       photo_cache_set_client_cache (
+                               E_PHOTO_CACHE (object),
+                               g_value_get_object (value));
+                       return;
+
+               case PROP_LOCAL_ONLY:
+                       e_photo_cache_set_local_only (
+                               E_PHOTO_CACHE (object),
+                               g_value_get_boolean (value));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+photo_cache_get_property (GObject *object,
+                          guint property_id,
+                          GValue *value,
+                          GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_CLIENT_CACHE:
+                       g_value_take_object (
+                               value,
+                               e_photo_cache_ref_client_cache (
+                               E_PHOTO_CACHE (object)));
+                       return;
+
+               case PROP_LOCAL_ONLY:
+                       g_value_set_boolean (
+                               value,
+                               e_photo_cache_get_local_only (
+                               E_PHOTO_CACHE (object)));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+photo_cache_dispose (GObject *object)
+{
+       EPhotoCachePrivate *priv;
+
+       priv = E_PHOTO_CACHE_GET_PRIVATE (object);
+
+       g_clear_object (&priv->client_cache);
+
+       photo_ht_remove_all (E_PHOTO_CACHE (object));
+
+       /* Chain up to parent's dispose() method. */
+       G_OBJECT_CLASS (e_photo_cache_parent_class)->dispose (object);
+}
+
+static void
+photo_cache_finalize (GObject *object)
+{
+       EPhotoCachePrivate *priv;
+
+       priv = E_PHOTO_CACHE_GET_PRIVATE (object);
+
+       g_hash_table_destroy (priv->photo_ht);
+
+       /* Chain up to parent's finalize() method. */
+       G_OBJECT_CLASS (e_photo_cache_parent_class)->finalize (object);
+}
+
+static void
+photo_cache_constructed (GObject *object)
+{
+       /* Chain up to parent's constructed() method. */
+       G_OBJECT_CLASS (e_photo_cache_parent_class)->constructed (object);
+
+       e_extensible_load_extensions (E_EXTENSIBLE (object));
+}
+
+static void
+e_photo_cache_class_init (EPhotoCacheClass *class)
+{
+       GObjectClass *object_class;
+
+       g_type_class_add_private (class, sizeof (EPhotoCachePrivate));
+
+       object_class = G_OBJECT_CLASS (class);
+       object_class->set_property = photo_cache_set_property;
+       object_class->get_property = photo_cache_get_property;
+       object_class->dispose = photo_cache_dispose;
+       object_class->finalize = photo_cache_finalize;
+       object_class->constructed = photo_cache_constructed;
+
+       /**
+        * EPhotoCache: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));
+
+       /**
+        * EPhotoCache:local-only:
+        *
+        * Whether to restrict searches to the built-in address book.
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_LOCAL_ONLY,
+               g_param_spec_boolean (
+                       "local-only",
+                       "Local Only",
+                       "Whether to restruct searches "
+                       "to the built-in address book",
+                       FALSE,
+                       G_PARAM_READWRITE |
+                       G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_photo_cache_init (EPhotoCache *photo_cache)
+{
+       GHashTable *photo_ht;
+
+       photo_ht = g_hash_table_new_full (
+               (GHashFunc) g_str_hash,
+               (GEqualFunc) g_str_equal,
+               (GDestroyNotify) g_free,
+               (GDestroyNotify) photo_data_unref);
+
+       photo_cache->priv = E_PHOTO_CACHE_GET_PRIVATE (photo_cache);
+       photo_cache->priv->photo_ht = photo_ht;
+
+       g_mutex_init (&photo_cache->priv->photo_ht_lock);
+}
+
+/**
+ * e_photo_cache_new:
+ * @client_cache: an #EClientCache
+ *
+ * Creates a new #EPhotoCache instance.
+ *
+ * Returns: an #EPhotoCache
+ **/
+EPhotoCache *
+e_photo_cache_new (EClientCache *client_cache)
+{
+       g_return_val_if_fail (E_IS_CLIENT_CACHE (client_cache), NULL);
+
+       return g_object_new (
+               E_TYPE_PHOTO_CACHE,
+               "client-cache", client_cache, NULL);
+}
+
+/**
+ * e_photo_cache_ref_client_cache:
+ * @photo_cache: an #EPhotoCache
+ *
+ * Returns the #EClientCache passed to e_photo_cache_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_photo_cache_ref_client_cache (EPhotoCache *photo_cache)
+{
+       g_return_val_if_fail (E_IS_PHOTO_CACHE (photo_cache), NULL);
+
+       return g_object_ref (photo_cache->priv->client_cache);
+}
+
+/**
+ * e_photo_cache_get_local_only:
+ * @photo_cache: an #EPhotoCache
+ *
+ * Returns whether to limit photo searches to the built-in ("local")
+ * address book returned by e_source_registry_ref_builtin_address_book().
+ *
+ * If this property is %FALSE then all enabled address books are searched.
+ *
+ * Returns: whether to search only the built-in address book
+ **/
+gboolean
+e_photo_cache_get_local_only (EPhotoCache *photo_cache)
+{
+       g_return_val_if_fail (E_IS_PHOTO_CACHE (photo_cache), FALSE);
+
+       return photo_cache->priv->local_only;
+}
+
+/**
+ * e_photo_cache_set_local_only:
+ * @photo_cache: an #EPhotoCache
+ * @local_only: whether to search only the built-in address book
+ *
+ * Sets whether to limit photo searches to the built-in ("local")
+ * address book returned by e_source_registry_ref_builtin_address_book().
+ *
+ * If this property is %FALSE then all enabled address books are searched.
+ **/
+void
+e_photo_cache_set_local_only (EPhotoCache *photo_cache,
+                              gboolean local_only)
+{
+       g_return_if_fail (E_IS_PHOTO_CACHE (photo_cache));
+
+       photo_cache->priv->local_only = local_only;
+
+       /* Reset the cache. */
+       photo_ht_remove_all (photo_cache);
+
+       g_object_notify (G_OBJECT (photo_cache), "local-only");
+}
+
+/**
+ * e_photo_cache_get_photo_sync:
+ * @photo_cache: an #EPhotoCache
+ * @email_address: an email address
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @out_stream: return location for a #GInputStream, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Searches enabled address books (subject to the #EPhotoCache:local-only
+ * preference) for a contact photo or logo associated with @email_address.
+ *
+ * If a match is found, a #GInputStream from which to read image data is
+ * returned through the @out_stream return location.  If no match is found,
+ * the @out_stream return location is set to %NULL.
+ *
+ * The return value indicates whether the search completed successfully,
+ * not whether a match was found.  If an error occurs, the function will
+ * set @error and return %FALSE.
+ *
+ * Returns: whether the search completed successfully
+ **/
+gboolean
+e_photo_cache_get_photo_sync (EPhotoCache *photo_cache,
+                              const gchar *email_address,
+                              GCancellable *cancellable,
+                              GInputStream **out_stream,
+                              GError **error)
+{
+       EContactPhoto *photo = NULL;
+       EClientCache *client_cache;
+       GQueue queue = G_QUEUE_INIT;
+       PhotoData *photo_data;
+       gboolean success = TRUE;
+
+       g_return_val_if_fail (E_IS_PHOTO_CACHE (photo_cache), FALSE);
+       g_return_val_if_fail (email_address != NULL, FALSE);
+
+       client_cache = e_photo_cache_ref_client_cache (photo_cache);
+
+       /* Try the cache first. */
+       photo_data = photo_ht_lookup (photo_cache, email_address);
+       if (photo_data_dup_photo (photo_data, &photo))
+               goto exit;
+
+       /* Find contacts with a matching email address. */
+       success = photo_cache_find_contacts (
+               photo_cache, email_address,
+               cancellable, &queue, error);
+       if (!success) {
+               g_warn_if_fail (g_queue_is_empty (&queue));
+               goto exit;
+       }
+
+       /* Extract the first available photo from contacts. */
+       while (!g_queue_is_empty (&queue)) {
+               EContact *contact;
+
+               contact = g_queue_pop_head (&queue);
+               if (photo == NULL)
+                       photo = photo_cache_extract_photo (contact);
+               g_object_unref (contact);
+       }
+
+       /* Passing a NULL photo here is fine.  We want to cache not
+        * only the photo itself, but whether a photo was found for
+        * this email address. */
+       photo_data_set_photo (photo_data, photo);
+
+exit:
+       photo_data_unref (photo_data);
+
+       g_object_unref (client_cache);
+
+       /* Try opening an input stream to the photo data. */
+       if (photo != NULL) {
+               GInputStream *stream;
+
+               stream = photo_cache_new_stream_from_photo (
+                       photo, cancellable, error);
+
+               success = (stream != NULL);
+
+               if (stream != NULL) {
+                       if (out_stream != NULL)
+                               *out_stream = g_object_ref (stream);
+                       g_object_unref (stream);
+               }
+
+               e_contact_photo_free (photo);
+       }
+
+       return success;
+}
+
+/* Helper for e_photo_cache_get_photo() */
+static void
+photo_cache_get_photo_thread (GSimpleAsyncResult *simple,
+                              GObject *source_object,
+                              GCancellable *cancellable)
+{
+       AsyncContext *async_context;
+       GError *error = NULL;
+
+       async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+       e_photo_cache_get_photo_sync (
+               E_PHOTO_CACHE (source_object),
+               async_context->email_address,
+               cancellable,
+               &async_context->input_stream,
+               &error);
+
+       if (error != NULL)
+               g_simple_async_result_take_error (simple, error);
+}
+
+/**
+ * e_photo_cache_get_photo:
+ * @photo_cache: an #EPhotoCache
+ * @email_address: an email address
+ * @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 searches enabled address books (subject to the
+ * #EPhotoCache:local-only preference) for a contact photo or logo
+ * associated with @email_address.
+ *
+ * When the operation is finished, @callback will be called.  You can then
+ * call e_photo_cache_get_photo_finish() to get the result of the operation.
+ **/
+void
+e_photo_cache_get_photo (EPhotoCache *photo_cache,
+                         const gchar *email_address,
+                         GCancellable *cancellable,
+                         GAsyncReadyCallback callback,
+                         gpointer user_data)
+{
+       GSimpleAsyncResult *simple;
+       AsyncContext *async_context;
+
+       g_return_if_fail (E_IS_PHOTO_CACHE (photo_cache));
+       g_return_if_fail (email_address != NULL);
+
+       async_context = g_slice_new0 (AsyncContext);
+       async_context->email_address = g_strdup (email_address);
+
+       simple = g_simple_async_result_new (
+               G_OBJECT (photo_cache), callback,
+               user_data, e_photo_cache_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);
+
+       g_simple_async_result_run_in_thread (
+               simple, photo_cache_get_photo_thread,
+               G_PRIORITY_DEFAULT, cancellable);
+
+       g_object_unref (simple);
+}
+
+/**
+ * e_photo_cache_get_photo_finish:
+ * @photo_cache: an #EPhotoCache
+ * @result: a #GAsyncResult
+ * @out_stream: return location for a #GInputStream, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with e_photo_cache_get_photo().
+ *
+ * If a match was found, a #GInputStream from which to read image data is
+ * returned through the @out_photo return location.  If no match was found,
+ * the @out_stream return location is set to %NULL.
+ *
+ * The return value indicates whether the search completed successfully,
+ * not whether a match was found.  If an error occurred, the function will
+ * set @error and return %FALSE.
+ *
+ * Returns: whether the search completed successfully
+ **/
+gboolean
+e_photo_cache_get_photo_finish (EPhotoCache *photo_cache,
+                                GAsyncResult *result,
+                                GInputStream **out_stream,
+                                GError **error)
+{
+       GSimpleAsyncResult *simple;
+       AsyncContext *async_context;
+
+       g_return_val_if_fail (
+               g_simple_async_result_is_valid (
+               result, G_OBJECT (photo_cache),
+               e_photo_cache_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 (out_stream != NULL) {
+               *out_stream = async_context->input_stream;
+               async_context->input_stream = NULL;
+       }
+
+       return TRUE;
+}
+
+/**
+ * e_photo_cache_remove:
+ * @photo_cache: an #EPhotoCache
+ * @email_address: an email address
+ *
+ * Removes the cache entry for @email_address, if such an entry exists.
+ **/
+gboolean
+e_photo_cache_remove (EPhotoCache *photo_cache,
+                      const gchar *email_address)
+{
+       g_return_val_if_fail (E_IS_PHOTO_CACHE (photo_cache), FALSE);
+       g_return_val_if_fail (email_address != NULL, FALSE);
+
+       return photo_ht_remove (photo_cache, email_address);
+}
+
diff --git a/e-util/e-photo-cache.h b/e-util/e-photo-cache.h
new file mode 100644
index 0000000..8114e93
--- /dev/null
+++ b/e-util/e-photo-cache.h
@@ -0,0 +1,95 @@
+/*
+ * e-photo-cache.h
+ *
+ * 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/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_PHOTO_CACHE_H
+#define E_PHOTO_CACHE_H
+
+#include <libebook/libebook.h>
+#include <e-util/e-client-cache.h>
+
+/* Standard GObject macros */
+#define E_TYPE_PHOTO_CACHE \
+       (e_photo_cache_get_type ())
+#define E_PHOTO_CACHE(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_PHOTO_CACHE, EPhotoCache))
+#define E_PHOTO_CACHE_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_PHOTO_CACHE, EPhotoCacheClass))
+#define E_IS_PHOTO_CACHE(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_PHOTO_CACHE))
+#define E_IS_PHOTO_CACHE_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_PHOTO_CACHE))
+#define E_PHOTO_CACHE_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_PHOTO_CACHE, EPhotoCacheClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EPhotoCache EPhotoCache;
+typedef struct _EPhotoCacheClass EPhotoCacheClass;
+typedef struct _EPhotoCachePrivate EPhotoCachePrivate;
+
+/**
+ * EPhotoCache:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ **/
+struct _EPhotoCache {
+       GObject parent;
+       EPhotoCachePrivate *priv;
+};
+
+struct _EPhotoCacheClass {
+       GObjectClass parent_class;
+};
+
+GType          e_photo_cache_get_type          (void) G_GNUC_CONST;
+EPhotoCache *  e_photo_cache_new               (EClientCache *client_cache);
+EClientCache * e_photo_cache_ref_client_cache  (EPhotoCache *photo_cache);
+gboolean       e_photo_cache_get_local_only    (EPhotoCache *photo_cache);
+void           e_photo_cache_set_local_only    (EPhotoCache *photo_cache,
+                                                gboolean local_only);
+gboolean       e_photo_cache_get_photo_sync    (EPhotoCache *photo_cache,
+                                                const gchar *email_address,
+                                                GCancellable *cancellable,
+                                                GInputStream **out_stream,
+                                                GError **error);
+void           e_photo_cache_get_photo         (EPhotoCache *photo_cache,
+                                                const gchar *email_address,
+                                                GCancellable *cancellable,
+                                                GAsyncReadyCallback callback,
+                                                gpointer user_data);
+gboolean       e_photo_cache_get_photo_finish  (EPhotoCache *photo_cache,
+                                                GAsyncResult *result,
+                                                GInputStream **out_stream,
+                                                GError **error);
+gboolean       e_photo_cache_remove            (EPhotoCache *photo_cache,
+                                                const gchar *email_address);
+
+G_END_DECLS
+
+#endif /* E_PHOTO_CACHE_H */
+
diff --git a/e-util/e-util.h b/e-util/e-util.h
index 6b3934f..cbdc77c 100644
--- a/e-util/e-util.h
+++ b/e-util/e-util.h
@@ -129,6 +129,7 @@
 #include <e-util/e-online-button.h>
 #include <e-util/e-paned.h>
 #include <e-util/e-passwords.h>
+#include <e-util/e-photo-cache.h>
 #include <e-util/e-picture-gallery.h>
 #include <e-util/e-plugin-ui.h>
 #include <e-util/e-plugin.h>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]