diff options
author | Matthew Barnes <mbarnes@redhat.com> | 2013-04-23 08:28:14 +0800 |
---|---|---|
committer | Matthew Barnes <mbarnes@redhat.com> | 2013-04-24 08:52:20 +0800 |
commit | 3bf02daa08271ae5201008ee362a045b217c6a7a (patch) | |
tree | 9b84d47b39575d7fa423e8a0ff121ebe472ae2ec /e-util | |
parent | 822fcbbd0fa5cfb69d03b689571a20a52c97ae12 (diff) | |
download | gsoc2013-evolution-3bf02daa08271ae5201008ee362a045b217c6a7a.tar.gz gsoc2013-evolution-3bf02daa08271ae5201008ee362a045b217c6a7a.tar.zst gsoc2013-evolution-3bf02daa08271ae5201008ee362a045b217c6a7a.zip |
Reimplement EPhotoCache to use EPhotoSource.
Reimplement EPhotoCache to delegate the actual photo fetching to
EPhotoSources. When a photo is requested for a given email address,
all available EPhotoSources are dispatched concurrently and a photo
input stream is selected from the result set.
This also utilizes EDataCapture, which is affixed to the returned
GInputStream to capture and cache photo data for an email address.
New functions:
e_photo_cache_add_photo_source()
e_photo_cache_list_photo_sources()
e_photo_cache_remove_photo_source()
e_photo_cache_add_photo()
Renamed functions:
e_photo_cache_remove() --> e_photo_cache_remove_photo()
Diffstat (limited to 'e-util')
-rw-r--r-- | e-util/e-photo-cache.c | 923 | ||||
-rw-r--r-- | e-util/e-photo-cache.h | 15 |
2 files changed, 658 insertions, 280 deletions
diff --git a/e-util/e-photo-cache.c b/e-util/e-photo-cache.c index 9ea0cb7ab2..42bad99eb6 100644 --- a/e-util/e-photo-cache.c +++ b/e-util/e-photo-cache.c @@ -21,12 +21,11 @@ * @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. + * #EPhotoCache finds photos 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. + * A limited internal cache is employed to speed up frequently searched + * email addresses. The exact caching semantics are private and subject + * to change. **/ #include "e-photo-cache.h" @@ -34,37 +33,73 @@ #include <string.h> #include <libebackend/libebackend.h> +#include <e-util/e-data-capture.h> + #define E_PHOTO_CACHE_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_PHOTO_CACHE, EPhotoCachePrivate)) +/* How long (in seconds) to hold out for a hit from the highest + * priority photo source, after which we settle for what we have. */ +#define ASYNC_TIMEOUT_SECONDS 3.0 + /* 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 +#define ERROR_IS_CANCELLED(error) \ + (g_error_matches ((error), G_IO_ERROR, G_IO_ERROR_CANCELLED)) + typedef struct _AsyncContext AsyncContext; +typedef struct _AsyncSubtask AsyncSubtask; +typedef struct _DataCaptureClosure DataCaptureClosure; typedef struct _PhotoData PhotoData; struct _EPhotoCachePrivate { EClientCache *client_cache; + GMainContext *main_context; GHashTable *photo_ht; GQueue photo_ht_keys; GMutex photo_ht_lock; + + GHashTable *sources_ht; + GMutex sources_ht_lock; }; struct _AsyncContext { + GMutex lock; + GTimer *timer; + GHashTable *subtasks; + GQueue results; + GInputStream *stream; + GConverter *data_capture; + + GCancellable *cancellable; + gulong cancelled_handler_id; +}; + +struct _AsyncSubtask { + volatile gint ref_count; + EPhotoSource *photo_source; + GSimpleAsyncResult *simple; + GCancellable *cancellable; + GInputStream *stream; + gint priority; + GError *error; +}; + +struct _DataCaptureClosure { + GWeakRef photo_cache; gchar *email_address; - GInputStream *input_stream; }; struct _PhotoData { volatile gint ref_count; GMutex lock; - EContactPhoto *photo; - gboolean photo_is_set; + GBytes *bytes; }; enum { @@ -72,6 +107,9 @@ enum { PROP_CLIENT_CACHE }; +/* Forward Declarations */ +static void async_context_cancel_subtasks (AsyncContext *async_context); + G_DEFINE_TYPE_WITH_CODE ( EPhotoCache, e_photo_cache, @@ -79,27 +117,296 @@ G_DEFINE_TYPE_WITH_CODE ( G_IMPLEMENT_INTERFACE ( E_TYPE_EXTENSIBLE, NULL)) +static AsyncSubtask * +async_subtask_new (EPhotoSource *photo_source, + GSimpleAsyncResult *simple) +{ + AsyncSubtask *async_subtask; + + async_subtask = g_slice_new0 (AsyncSubtask); + async_subtask->ref_count = 1; + async_subtask->photo_source = g_object_ref (photo_source); + async_subtask->simple = g_object_ref (simple); + async_subtask->cancellable = g_cancellable_new (); + async_subtask->priority = G_PRIORITY_DEFAULT; + + return async_subtask; +} + +static AsyncSubtask * +async_subtask_ref (AsyncSubtask *async_subtask) +{ + g_return_val_if_fail (async_subtask != NULL, NULL); + g_return_val_if_fail (async_subtask->ref_count > 0, NULL); + + g_atomic_int_inc (&async_subtask->ref_count); + + return async_subtask; +} + +static void +async_subtask_unref (AsyncSubtask *async_subtask) +{ + g_return_if_fail (async_subtask != NULL); + g_return_if_fail (async_subtask->ref_count > 0); + + if (g_atomic_int_dec_and_test (&async_subtask->ref_count)) { + + /* Ignore cancellations. */ + if (ERROR_IS_CANCELLED (async_subtask->error)) + g_clear_error (&async_subtask->error); + + /* Leave a breadcrumb on the console + * about unpropagated subtask errors. */ + if (async_subtask->error != NULL) { + g_warning ( + "%s: Unpropagated error in %s subtask: %s", + __FILE__, + G_OBJECT_TYPE_NAME ( + async_subtask->photo_source), + async_subtask->error->message); + g_error_free (async_subtask->error); + } + + g_clear_object (&async_subtask->photo_source); + g_clear_object (&async_subtask->simple); + g_clear_object (&async_subtask->cancellable); + g_clear_object (&async_subtask->stream); + + g_slice_free (AsyncSubtask, async_subtask); + } +} + +static void +async_subtask_cancel (AsyncSubtask *async_subtask) +{ + g_return_if_fail (async_subtask != NULL); + + g_cancellable_cancel (async_subtask->cancellable); +} + +static gint +async_subtask_compare (gconstpointer a, + gconstpointer b) +{ + const AsyncSubtask *subtask_a = a; + const AsyncSubtask *subtask_b = b; + + /* Without error is always less than with error. */ + + if (subtask_a->error != NULL && subtask_b->error != NULL) + return 0; + + if (subtask_a->error == NULL && subtask_b->error != NULL) + return -1; + + if (subtask_a->error != NULL && subtask_b->error == NULL) + return 1; + + if (subtask_a->priority == subtask_b->priority) + return 0; + + return (subtask_a->priority < subtask_b->priority) ? -1 : 1; +} + +static void +async_subtask_complete (AsyncSubtask *async_subtask) +{ + GSimpleAsyncResult *simple; + AsyncContext *async_context; + gboolean cancel_subtasks = FALSE; + gdouble seconds_elapsed; + + simple = async_subtask->simple; + async_context = g_simple_async_result_get_op_res_gpointer (simple); + + g_mutex_lock (&async_context->lock); + + seconds_elapsed = g_timer_elapsed (async_context->timer, NULL); + + /* Discard successfully completed subtasks with no match found. + * Keep failed subtasks around so we have a GError to propagate + * if we need one, but those go on the end of the queue. */ + + if (async_subtask->stream != NULL) { + g_queue_insert_sorted ( + &async_context->results, + async_subtask_ref (async_subtask), + (GCompareDataFunc) async_subtask_compare, + NULL); + + /* If enough seconds have elapsed, just take the highest + * priority input stream we have. Cancel the unfinished + * subtasks and let them complete with an error. */ + if (seconds_elapsed > ASYNC_TIMEOUT_SECONDS) + cancel_subtasks = TRUE; + + } else if (async_subtask->error != NULL) { + g_queue_push_tail ( + &async_context->results, + async_subtask_ref (async_subtask)); + } + + g_hash_table_remove (async_context->subtasks, async_subtask); + + if (g_hash_table_size (async_context->subtasks) > 0) { + /* Let the remaining subtasks finish. */ + goto exit; + } + + /* The queue should be ordered now such that subtasks + * with input streams are before subtasks with errors. + * So just evaluate the first subtask on the queue. */ + + async_subtask = g_queue_pop_head (&async_context->results); + + if (async_subtask != NULL) { + if (async_subtask->stream != NULL) { + async_context->stream = + g_converter_input_stream_new ( + async_subtask->stream, + async_context->data_capture); + } + + if (async_subtask->error != NULL) { + g_simple_async_result_take_error ( + simple, async_subtask->error); + async_subtask->error = NULL; + } + + async_subtask_unref (async_subtask); + } + + g_simple_async_result_complete_in_idle (simple); + +exit: + g_mutex_unlock (&async_context->lock); + + if (cancel_subtasks) { + /* Call this after the mutex is unlocked. */ + async_context_cancel_subtasks (async_context); + } +} + +static void +async_context_cancelled_cb (GCancellable *cancellable, + AsyncContext *async_context) +{ + async_context_cancel_subtasks (async_context); +} + +static AsyncContext * +async_context_new (EDataCapture *data_capture, + GCancellable *cancellable) +{ + AsyncContext *async_context; + + async_context = g_slice_new0 (AsyncContext); + g_mutex_init (&async_context->lock); + async_context->timer = g_timer_new (); + + async_context->subtasks = g_hash_table_new_full ( + (GHashFunc) g_direct_hash, + (GEqualFunc) g_direct_equal, + (GDestroyNotify) async_subtask_unref, + (GDestroyNotify) NULL); + + async_context->data_capture = g_object_ref (data_capture); + + if (G_IS_CANCELLABLE (cancellable)) { + gulong handler_id; + + async_context->cancellable = g_object_ref (cancellable); + + handler_id = g_cancellable_connect ( + async_context->cancellable, + G_CALLBACK (async_context_cancelled_cb), + async_context, + (GDestroyNotify) NULL); + async_context->cancelled_handler_id = handler_id; + } + + return async_context; +} + static void async_context_free (AsyncContext *async_context) { - g_free (async_context->email_address); + /* Do this first so the callback won't fire + * while we're dismantling the AsyncContext. */ + if (async_context->cancelled_handler_id > 0) + g_cancellable_disconnect ( + async_context->cancellable, + async_context->cancelled_handler_id); + + g_mutex_clear (&async_context->lock); + g_timer_destroy (async_context->timer); + + g_hash_table_destroy (async_context->subtasks); - if (async_context->input_stream != NULL) - g_object_unref (async_context->input_stream); + g_clear_object (&async_context->stream); + g_clear_object (&async_context->data_capture); + g_clear_object (&async_context->cancellable); g_slice_free (AsyncContext, async_context); } +static void +async_context_cancel_subtasks (AsyncContext *async_context) +{ + GQueue queue; + AsyncSubtask *async_subtask; + + /* Copy subtasks to a secondary GQueue to avoid invoking + * "cancelled" signal handlers while the mutex is locked. */ + g_mutex_lock (&async_context->lock); + queue.head = g_hash_table_get_keys (async_context->subtasks); + queue.tail = g_list_last (queue.head); + queue.length = g_list_length (queue.head); + g_queue_foreach (&queue, (GFunc) async_subtask_ref, NULL); + g_mutex_unlock (&async_context->lock); + + /* Cancel all subtasks. */ + while ((async_subtask = g_queue_pop_head (&queue)) != NULL) { + async_subtask_cancel (async_subtask); + async_subtask_unref (async_subtask); + } +} + +static DataCaptureClosure * +data_capture_closure_new (EPhotoCache *photo_cache, + const gchar *email_address) +{ + DataCaptureClosure *closure; + + closure = g_slice_new0 (DataCaptureClosure); + g_weak_ref_set (&closure->photo_cache, photo_cache); + closure->email_address = g_strdup (email_address); + + return closure; +} + +static void +data_capture_closure_free (DataCaptureClosure *closure) +{ + g_weak_ref_set (&closure->photo_cache, NULL); + g_free (closure->email_address); + + g_slice_free (DataCaptureClosure, closure); +} + static PhotoData * -photo_data_new (void) +photo_data_new (GBytes *bytes) { PhotoData *photo_data; photo_data = g_slice_new0 (PhotoData); photo_data->ref_count = 1; - g_mutex_init (&photo_data->lock); + if (bytes != NULL) + photo_data->bytes = g_bytes_ref (bytes); + return photo_data; } @@ -121,52 +428,41 @@ photo_data_unref (PhotoData *photo_data) 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); - + if (photo_data->bytes != NULL) + g_bytes_unref (photo_data->bytes); g_slice_free (PhotoData, photo_data); } } -static gboolean -photo_data_dup_photo (PhotoData *photo_data, - EContactPhoto **out_photo) +static GBytes * +photo_data_ref_bytes (PhotoData *photo_data) { - gboolean photo_is_set; - - g_return_val_if_fail (out_photo != NULL, FALSE); + GBytes *bytes = NULL; 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; + if (photo_data->bytes != NULL) + bytes = g_bytes_ref (photo_data->bytes); g_mutex_unlock (&photo_data->lock); - return photo_is_set; + return bytes; } static void -photo_data_set_photo (PhotoData *photo_data, - EContactPhoto *photo) +photo_data_set_bytes (PhotoData *photo_data, + GBytes *bytes) { g_mutex_lock (&photo_data->lock); - if (photo_data->photo != NULL) { - e_contact_photo_free (photo_data->photo); - photo_data->photo = NULL; + if (photo_data->bytes != NULL) { + g_bytes_unref (photo_data->bytes); + photo_data->bytes = NULL; } - if (photo != NULL) - photo_data->photo = e_contact_photo_copy (photo); - - photo_data->photo_is_set = TRUE; + if (bytes != NULL) + photo_data->bytes = g_bytes_ref (bytes); g_mutex_unlock (&photo_data->lock); } @@ -184,16 +480,17 @@ photo_ht_normalize_key (const gchar *email_address) return collation_key; } -static PhotoData * -photo_ht_lookup (EPhotoCache *photo_cache, - const gchar *email_address) +static void +photo_ht_insert (EPhotoCache *photo_cache, + const gchar *email_address, + GBytes *bytes) { GHashTable *photo_ht; GQueue *photo_ht_keys; PhotoData *photo_data; gchar *key; - g_return_val_if_fail (email_address != NULL, NULL); + g_return_if_fail (email_address != NULL); photo_ht = photo_cache->priv->photo_ht; photo_ht_keys = &photo_cache->priv->photo_ht_keys; @@ -207,7 +504,10 @@ photo_ht_lookup (EPhotoCache *photo_cache, if (photo_data != NULL) { GList *link; - photo_data_ref (photo_data); + /* Replace the old photo data if we have new photo + * data, otherwise leave the old photo data alone. */ + if (bytes != NULL) + photo_data_set_bytes (photo_data, bytes); /* Move the key to the head of the MRU queue. */ link = g_queue_find_custom ( @@ -218,7 +518,7 @@ photo_ht_lookup (EPhotoCache *photo_cache, g_queue_push_head_link (photo_ht_keys, link); } } else { - photo_data = photo_data_new (); + photo_data = photo_data_new (bytes); g_hash_table_insert ( photo_ht, g_strdup (key), @@ -235,6 +535,8 @@ photo_ht_lookup (EPhotoCache *photo_cache, g_hash_table_remove (photo_ht, oldest_key); g_free (oldest_key); } + + photo_data_unref (photo_data); } /* Hash table and queue sizes should be equal at all times. */ @@ -245,8 +547,48 @@ photo_ht_lookup (EPhotoCache *photo_cache, g_mutex_unlock (&photo_cache->priv->photo_ht_lock); g_free (key); +} - return photo_data; +static gboolean +photo_ht_lookup (EPhotoCache *photo_cache, + const gchar *email_address, + GInputStream **out_stream) +{ + GHashTable *photo_ht; + PhotoData *photo_data; + gboolean found = FALSE; + gchar *key; + + g_return_val_if_fail (email_address != NULL, FALSE); + g_return_val_if_fail (out_stream != NULL, FALSE); + + photo_ht = photo_cache->priv->photo_ht; + + 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) { + GBytes *bytes; + + bytes = photo_data_ref_bytes (photo_data); + if (bytes != NULL) { + *out_stream = + g_memory_input_stream_new_from_bytes (bytes); + g_bytes_unref (bytes); + } else { + *out_stream = NULL; + } + found = TRUE; + } + + g_mutex_unlock (&photo_cache->priv->photo_ht_lock); + + g_free (key); + + return found; } static gboolean @@ -311,140 +653,38 @@ photo_ht_remove_all (EPhotoCache *photo_cache) 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 gboolean -photo_cache_find_contacts (EPhotoCache *photo_cache, - const gchar *email_address, - GCancellable *cancellable, - GQueue *out_contacts, - GError **error) +static void +photo_cache_data_captured_cb (EDataCapture *data_capture, + GBytes *bytes, + DataCaptureClosure *closure) { - EClientCache *client_cache; - ESourceRegistry *registry; - EBookQuery *book_query; - GList *list, *link; - const gchar *extension_name; - 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); - registry = e_client_cache_ref_registry (client_cache); - - extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK; - list = e_source_registry_list_enabled (registry, extension_name); - - for (link = list; link != NULL; link = g_list_next (link)) { - ESource *source = E_SOURCE (link->data); - EClient *client; - GSList *contact_list = NULL; - GError *local_error = NULL; - - client = e_client_cache_get_client_sync ( - client_cache, - source, extension_name, - cancellable, &local_error); - - if (local_error != NULL) { - g_warn_if_fail (client == NULL); - if (g_queue_is_empty (out_contacts)) { - g_propagate_error (error, local_error); - success = FALSE; - } else { - /* Clear the error if we already - * have matching contacts queued. */ - g_clear_error (&local_error); - } - break; - } - - e_book_client_get_contacts_sync ( - E_BOOK_CLIENT (client), book_query_string, - &contact_list, cancellable, &local_error); - - g_object_unref (client); - - if (local_error != NULL) { - g_warn_if_fail (contact_list == NULL); - if (g_queue_is_empty (out_contacts)) { - g_propagate_error (error, local_error); - success = FALSE; - } else { - /* Clear the error if we already - * have matching contacts queued. */ - g_clear_error (&local_error); - } - break; - } + EPhotoCache *photo_cache; - while (contact_list != NULL) { - EContact *contact; + photo_cache = g_weak_ref_get (&closure->photo_cache); - /* 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); - } + if (photo_cache != NULL) { + e_photo_cache_add_photo ( + photo_cache, closure->email_address, bytes); + g_object_unref (photo_cache); } - - g_list_free_full (list, (GDestroyNotify) g_object_unref); - - g_object_unref (client_cache); - g_object_unref (registry); - - g_free (book_query_string); - - return success; } -static GInputStream * -photo_cache_new_stream_from_photo (EContactPhoto *photo, - GCancellable *cancellable, - GError **error) +static void +photo_cache_async_subtask_done_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) { - GInputStream *stream = NULL; + AsyncSubtask *async_subtask = user_data; - /* 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; + e_photo_source_get_photo_finish ( + E_PHOTO_SOURCE (source_object), + result, + &async_subtask->stream, + &async_subtask->priority, + &async_subtask->error); - } 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; + async_subtask_complete (async_subtask); + async_subtask_unref (async_subtask); } static void @@ -514,8 +754,13 @@ photo_cache_finalize (GObject *object) priv = E_PHOTO_CACHE_GET_PRIVATE (object); + g_main_context_unref (priv->main_context); + g_hash_table_destroy (priv->photo_ht); + g_mutex_lock (&priv->photo_ht_lock); + g_mutex_lock (&priv->sources_ht_lock); + /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (e_photo_cache_parent_class)->finalize (object); } @@ -565,6 +810,7 @@ static void e_photo_cache_init (EPhotoCache *photo_cache) { GHashTable *photo_ht; + GHashTable *sources_ht; photo_ht = g_hash_table_new_full ( (GHashFunc) g_str_hash, @@ -572,10 +818,19 @@ e_photo_cache_init (EPhotoCache *photo_cache) (GDestroyNotify) g_free, (GDestroyNotify) photo_data_unref); + sources_ht = g_hash_table_new_full ( + (GHashFunc) g_direct_hash, + (GEqualFunc) g_direct_equal, + (GDestroyNotify) g_object_unref, + (GDestroyNotify) NULL); + photo_cache->priv = E_PHOTO_CACHE_GET_PRIVATE (photo_cache); + photo_cache->priv->main_context = g_main_context_ref_thread_default (); photo_cache->priv->photo_ht = photo_ht; + photo_cache->priv->sources_ht = sources_ht; g_mutex_init (&photo_cache->priv->photo_ht_lock); + g_mutex_init (&photo_cache->priv->sources_ht_lock); } /** @@ -616,6 +871,146 @@ e_photo_cache_ref_client_cache (EPhotoCache *photo_cache) } /** + * e_photo_cache_add_photo_source: + * @photo_cache: an #EPhotoCache + * @photo_source: an #EPhotoSource + * + * Adds @photo_source as a potential source of photos. + **/ +void +e_photo_cache_add_photo_source (EPhotoCache *photo_cache, + EPhotoSource *photo_source) +{ + GHashTable *sources_ht; + + g_return_if_fail (E_IS_PHOTO_CACHE (photo_cache)); + g_return_if_fail (E_IS_PHOTO_SOURCE (photo_source)); + + sources_ht = photo_cache->priv->sources_ht; + + g_mutex_lock (&photo_cache->priv->sources_ht_lock); + + g_hash_table_add (sources_ht, g_object_ref (photo_source)); + + g_mutex_unlock (&photo_cache->priv->sources_ht_lock); +} + +/** + * e_photo_cache_list_photo_sources: + * @photo_cache: an #EPhotoCache + * + * Returns a list of photo sources for @photo_cache. + * + * The sources returned in the list are referenced for thread-safety. + * They must each be unreferenced with g_object_unref() when finished + * with them. Free the returned list itself with g_list_free(). + * + * An easy way to free the list property in one step is as follows: + * + * |[ + * g_list_free_full (list, g_object_unref); + * ]| + * + * Returns: a sorted list of photo sources + **/ +GList * +e_photo_cache_list_photo_sources (EPhotoCache *photo_cache) +{ + GHashTable *sources_ht; + GList *list; + + g_return_val_if_fail (E_IS_PHOTO_CACHE (photo_cache), NULL); + + sources_ht = photo_cache->priv->sources_ht; + + g_mutex_lock (&photo_cache->priv->sources_ht_lock); + + list = g_hash_table_get_keys (sources_ht); + g_list_foreach (list, (GFunc) g_object_ref, NULL); + + g_mutex_unlock (&photo_cache->priv->sources_ht_lock); + + return list; +} + +/** + * e_photo_cache_remove_photo_source: + * @photo_cache: an #EPhotoCache + * @photo_source: an #EPhotoSource + * + * Removes @photo_source as a potential source of photos. + * + * Returns: %TRUE if @photo_source was found and removed, %FALSE if not + **/ +gboolean +e_photo_cache_remove_photo_source (EPhotoCache *photo_cache, + EPhotoSource *photo_source) +{ + GHashTable *sources_ht; + gboolean removed; + + g_return_val_if_fail (E_IS_PHOTO_CACHE (photo_cache), FALSE); + g_return_val_if_fail (E_IS_PHOTO_SOURCE (photo_source), FALSE); + + sources_ht = photo_cache->priv->sources_ht; + + g_mutex_lock (&photo_cache->priv->sources_ht_lock); + + removed = g_hash_table_remove (sources_ht, photo_source); + + g_mutex_unlock (&photo_cache->priv->sources_ht_lock); + + return removed; +} + +/** + * e_photo_cache_add_photo: + * @photo_cache: an #EPhotoCache + * @email_address: an email address + * @bytes: a #GBytes containing photo data, or %NULL + * + * Adds a cache entry for @email_address, such that subsequent photo requests + * for @email_address will yield a #GMemoryInputStream loaded with @bytes + * without consulting available photo sources. + * + * The @bytes argument can also be %NULL to indicate no photo is available for + * @email_address. Subsequent photo requests for @email_address will yield no + * input stream. + * + * The entry may be removed without notice however, subject to @photo_cache's + * internal caching policy. + **/ +void +e_photo_cache_add_photo (EPhotoCache *photo_cache, + const gchar *email_address, + GBytes *bytes) +{ + g_return_if_fail (E_IS_PHOTO_CACHE (photo_cache)); + g_return_if_fail (email_address != NULL); + + photo_ht_insert (photo_cache, email_address, bytes); +} + +/** + * e_photo_cache_remove_photo: + * @photo_cache: an #EPhotoCache + * @email_address: an email address + * + * Removes the cache entry for @email_address, if such an entry exists. + * + * Returns: %TRUE if a cache entry was found and removed + **/ +gboolean +e_photo_cache_remove_photo (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); +} + +/** * e_photo_cache_get_photo_sync: * @photo_cache: an #EPhotoCache * @email_address: an email address @@ -623,8 +1018,8 @@ e_photo_cache_ref_client_cache (EPhotoCache *photo_cache) * @out_stream: return location for a #GInputStream, or %NULL * @error: return location for a #GError, or %NULL * - * Searches enabled address books for a contact photo or logo associated - * with @email_address. + * Searches available photo sources for a photo 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, @@ -643,94 +1038,26 @@ e_photo_cache_get_photo_sync (EPhotoCache *photo_cache, 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); + EAsyncClosure *closure; + GAsyncResult *result; + gboolean success; - /* 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; - } + closure = e_async_closure_new (); - /* Extract the first available photo from contacts. */ - while (!g_queue_is_empty (&queue)) { - EContact *contact; + e_photo_cache_get_photo ( + photo_cache, email_address, cancellable, + e_async_closure_callback, closure); - contact = g_queue_pop_head (&queue); - if (photo == NULL) - photo = photo_cache_extract_photo (contact); - g_object_unref (contact); - } + result = e_async_closure_wait (closure); - /* 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); + success = e_photo_cache_get_photo_finish ( + photo_cache, result, out_stream, error); -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); - } + e_async_closure_free (closure); 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 @@ -739,8 +1066,8 @@ photo_cache_get_photo_thread (GSimpleAsyncResult *simple, * @callback: a #GAsyncReadyCallback to call when the request is satisfied * @user_data: data to pass to the callback function * - * Asynchronously searches enabled address books for a contact photo or logo - * associated with @email_address. + * Asynchronously searches available photo sources for a photo 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. @@ -754,12 +1081,24 @@ e_photo_cache_get_photo (EPhotoCache *photo_cache, { GSimpleAsyncResult *simple; AsyncContext *async_context; + EDataCapture *data_capture; + GInputStream *stream = NULL; + GList *list, *link; 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); + /* This will be used to eavesdrop on the resulting input stream + * for the purpose of adding the photo data to the photo cache. */ + data_capture = e_data_capture_new (photo_cache->priv->main_context); + + g_signal_connect_data ( + data_capture, "finished", + G_CALLBACK (photo_cache_data_captured_cb), + data_capture_closure_new (photo_cache, email_address), + (GClosureNotify) data_capture_closure_free, 0); + + async_context = async_context_new (data_capture, cancellable); simple = g_simple_async_result_new ( G_OBJECT (photo_cache), callback, @@ -770,11 +1109,54 @@ e_photo_cache_get_photo (EPhotoCache *photo_cache, 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); + /* Check if we have this email address already cached. */ + if (photo_ht_lookup (photo_cache, email_address, &stream)) { + async_context->stream = stream; /* takes ownership */ + g_simple_async_result_complete_in_idle (simple); + goto exit; + } + + list = e_photo_cache_list_photo_sources (photo_cache); + if (list == NULL) { + g_simple_async_result_complete_in_idle (simple); + goto exit; + } + + g_mutex_lock (&async_context->lock); + + /* Dispatch a subtask for each photo source. */ + for (link = list; link != NULL; link = g_list_next (link)) { + EPhotoSource *photo_source; + AsyncSubtask *async_subtask; + + photo_source = E_PHOTO_SOURCE (link->data); + async_subtask = async_subtask_new (photo_source, simple); + + g_hash_table_add ( + async_context->subtasks, + async_subtask_ref (async_subtask)); + + e_photo_source_get_photo ( + photo_source, email_address, + async_subtask->cancellable, + photo_cache_async_subtask_done_cb, + async_subtask_ref (async_subtask)); + + async_subtask_unref (async_subtask); + } + + g_mutex_unlock (&async_context->lock); + + g_list_free_full (list, (GDestroyNotify) g_object_unref); + + /* Check if we were cancelled while dispatching subtasks. */ + if (g_cancellable_is_cancelled (cancellable)) + async_context_cancel_subtasks (async_context); + +exit: g_object_unref (simple); + g_object_unref (data_capture); } /** @@ -817,27 +1199,12 @@ e_photo_cache_get_photo_finish (EPhotoCache *photo_cache, return FALSE; if (out_stream != NULL) { - *out_stream = async_context->input_stream; - async_context->input_stream = NULL; + if (async_context->stream != NULL) + *out_stream = g_object_ref (async_context->stream); + else + *out_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 index af44e2e3f1..0c310080e7 100644 --- a/e-util/e-photo-cache.h +++ b/e-util/e-photo-cache.h @@ -25,6 +25,7 @@ #include <libebook/libebook.h> #include <e-util/e-client-cache.h> +#include <e-util/e-photo-source.h> /* Standard GObject macros */ #define E_TYPE_PHOTO_CACHE \ @@ -69,6 +70,18 @@ struct _EPhotoCacheClass { 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); +void e_photo_cache_add_photo_source (EPhotoCache *photo_cache, + EPhotoSource *photo_source); +GList * e_photo_cache_list_photo_sources + (EPhotoCache *photo_cache); +gboolean e_photo_cache_remove_photo_source + (EPhotoCache *photo_cache, + EPhotoSource *photo_source); +void e_photo_cache_add_photo (EPhotoCache *photo_cache, + const gchar *email_address, + GBytes *bytes); +gboolean e_photo_cache_remove_photo (EPhotoCache *photo_cache, + const gchar *email_address); gboolean e_photo_cache_get_photo_sync (EPhotoCache *photo_cache, const gchar *email_address, GCancellable *cancellable, @@ -83,8 +96,6 @@ 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 |