From c03ac675117f81caa051df681a3f763c66714317 Mon Sep 17 00:00:00 2001 From: Matthew Barnes Date: Mon, 25 Feb 2013 14:41:07 -0500 Subject: Add EPhotoCache. Caches contact photos by email address. Replaces the disastrous implementation in e-mail-utils.c. --- 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 + 4 files changed, 1026 insertions(+) create mode 100644 e-util/e-photo-cache.c create mode 100644 e-util/e-photo-cache.h (limited to 'e-util') diff --git a/e-util/Makefile.am b/e-util/Makefile.am index f802218db3..3eca4435d7 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 0000000000..b7398c0bcf --- /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 + * + */ + +/** + * 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 +#include + +#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 0000000000..8114e93385 --- /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 + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef E_PHOTO_CACHE_H +#define E_PHOTO_CACHE_H + +#include +#include + +/* 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 6b3934fa8d..cbdc77c3b2 100644 --- a/e-util/e-util.h +++ b/e-util/e-util.h @@ -129,6 +129,7 @@ #include #include #include +#include #include #include #include -- cgit