diff options
-rw-r--r-- | doc/reference/libeutil/libeutil-sections.txt | 6 | ||||
-rw-r--r-- | e-util/e-photo-cache.c | 923 | ||||
-rw-r--r-- | e-util/e-photo-cache.h | 15 | ||||
-rw-r--r-- | mail/e-mail-reader.c | 4 |
4 files changed, 665 insertions, 283 deletions
diff --git a/doc/reference/libeutil/libeutil-sections.txt b/doc/reference/libeutil/libeutil-sections.txt index 49420677c4..71174e3db2 100644 --- a/doc/reference/libeutil/libeutil-sections.txt +++ b/doc/reference/libeutil/libeutil-sections.txt @@ -2489,10 +2489,14 @@ e_passwords_ask_password EPhotoCache e_photo_cache_new e_photo_cache_ref_client_cache +e_photo_cache_add_photo_source +e_photo_cache_list_photo_sources +e_photo_cache_remove_photo_source +e_photo_cache_add_photo +e_photo_cache_remove_photo 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 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 diff --git a/mail/e-mail-reader.c b/mail/e-mail-reader.c index 1556481d62..e36b1f745b 100644 --- a/mail/e-mail-reader.c +++ b/mail/e-mail-reader.c @@ -199,7 +199,7 @@ action_mail_add_sender_cb (GtkAction *action, photo_cache = e_mail_ui_session_get_photo_cache ( E_MAIL_UI_SESSION (session)); camel_internet_address_get (cia, 0, NULL, &address_only); - e_photo_cache_remove (photo_cache, address_only); + e_photo_cache_remove_photo (photo_cache, address_only); } g_object_unref (cia); @@ -261,7 +261,7 @@ action_add_to_address_book_cb (GtkAction *action, photo_cache = e_mail_ui_session_get_photo_cache ( E_MAIL_UI_SESSION (session)); camel_internet_address_get (cia, 0, NULL, &address_only); - e_photo_cache_remove (photo_cache, address_only); + e_photo_cache_remove_photo (photo_cache, address_only); g_object_unref (cia); |