Updated Spanish translation
[evolution.git] / e-util / e-photo-cache.c
blob32abcb47a1736311ec1682aa34feab7dc32bcd6c
1 /*
2 * e-photo-cache.c
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
18 /**
19 * SECTION: e-photo-cache
20 * @include: e-util/e-util.h
21 * @short_description: Search for photos by email address
23 * #EPhotoCache finds photos associated with an email address.
25 * A limited internal cache is employed to speed up frequently searched
26 * email addresses. The exact caching semantics are private and subject
27 * to change.
28 **/
30 #include "e-photo-cache.h"
32 #include <string.h>
33 #include <libebackend/libebackend.h>
35 #include <e-util/e-data-capture.h>
37 #define E_PHOTO_CACHE_GET_PRIVATE(obj) \
38 (G_TYPE_INSTANCE_GET_PRIVATE \
39 ((obj), E_TYPE_PHOTO_CACHE, EPhotoCachePrivate))
41 /* How long (in seconds) to hold out for a hit from the highest
42 * priority photo source, after which we settle for what we have. */
43 #define ASYNC_TIMEOUT_SECONDS 3.0
45 /* How many email addresses we track at once, regardless of whether
46 * the email address has a photo. As new cache entries are added, we
47 * discard the least recently accessed entries to keep the cache size
48 * within the limit. */
49 #define MAX_CACHE_SIZE 20
51 #define ERROR_IS_CANCELLED(error) \
52 (g_error_matches ((error), G_IO_ERROR, G_IO_ERROR_CANCELLED))
54 typedef struct _AsyncContext AsyncContext;
55 typedef struct _AsyncSubtask AsyncSubtask;
56 typedef struct _DataCaptureClosure DataCaptureClosure;
57 typedef struct _PhotoData PhotoData;
59 struct _EPhotoCachePrivate {
60 EClientCache *client_cache;
61 GMainContext *main_context;
63 GHashTable *photo_ht;
64 GQueue photo_ht_keys;
65 GMutex photo_ht_lock;
67 GHashTable *sources_ht;
68 GMutex sources_ht_lock;
71 struct _AsyncContext {
72 GMutex lock;
73 GTimer *timer;
74 GHashTable *subtasks;
75 GQueue results;
76 GInputStream *stream;
77 GConverter *data_capture;
79 GCancellable *cancellable;
80 gulong cancelled_handler_id;
83 struct _AsyncSubtask {
84 volatile gint ref_count;
85 EPhotoSource *photo_source;
86 GSimpleAsyncResult *simple;
87 GCancellable *cancellable;
88 GInputStream *stream;
89 gint priority;
90 GError *error;
93 struct _DataCaptureClosure {
94 GWeakRef photo_cache;
95 gchar *email_address;
98 struct _PhotoData {
99 volatile gint ref_count;
100 GMutex lock;
101 GBytes *bytes;
104 enum {
105 PROP_0,
106 PROP_CLIENT_CACHE
109 /* Forward Declarations */
110 static void async_context_cancel_subtasks (AsyncContext *async_context);
112 G_DEFINE_TYPE_WITH_CODE (
113 EPhotoCache,
114 e_photo_cache,
115 G_TYPE_OBJECT,
116 G_IMPLEMENT_INTERFACE (
117 E_TYPE_EXTENSIBLE, NULL))
119 static AsyncSubtask *
120 async_subtask_new (EPhotoSource *photo_source,
121 GSimpleAsyncResult *simple)
123 AsyncSubtask *async_subtask;
125 async_subtask = g_slice_new0 (AsyncSubtask);
126 async_subtask->ref_count = 1;
127 async_subtask->photo_source = g_object_ref (photo_source);
128 async_subtask->simple = g_object_ref (simple);
129 async_subtask->cancellable = g_cancellable_new ();
130 async_subtask->priority = G_PRIORITY_DEFAULT;
132 return async_subtask;
135 static AsyncSubtask *
136 async_subtask_ref (AsyncSubtask *async_subtask)
138 g_return_val_if_fail (async_subtask != NULL, NULL);
139 g_return_val_if_fail (async_subtask->ref_count > 0, NULL);
141 g_atomic_int_inc (&async_subtask->ref_count);
143 return async_subtask;
146 static void
147 async_subtask_unref (AsyncSubtask *async_subtask)
149 g_return_if_fail (async_subtask != NULL);
150 g_return_if_fail (async_subtask->ref_count > 0);
152 if (g_atomic_int_dec_and_test (&async_subtask->ref_count)) {
154 /* Ignore cancellations. */
155 if (ERROR_IS_CANCELLED (async_subtask->error))
156 g_clear_error (&async_subtask->error);
158 /* Leave a breadcrumb on the console
159 * about unpropagated subtask errors. */
160 if (async_subtask->error != NULL) {
161 g_warning (
162 "%s: Unpropagated error in %s subtask: %s",
163 __FILE__,
164 G_OBJECT_TYPE_NAME (
165 async_subtask->photo_source),
166 async_subtask->error->message);
167 g_error_free (async_subtask->error);
170 g_clear_object (&async_subtask->photo_source);
171 g_clear_object (&async_subtask->simple);
172 g_clear_object (&async_subtask->cancellable);
173 g_clear_object (&async_subtask->stream);
175 g_slice_free (AsyncSubtask, async_subtask);
179 static gboolean
180 async_subtask_cancel_idle_cb (gpointer user_data)
182 AsyncSubtask *async_subtask = user_data;
184 g_cancellable_cancel (async_subtask->cancellable);
186 return FALSE;
189 static gint
190 async_subtask_compare (gconstpointer a,
191 gconstpointer b)
193 const AsyncSubtask *subtask_a = a;
194 const AsyncSubtask *subtask_b = b;
196 /* Without error is always less than with error. */
198 if (subtask_a->error != NULL && subtask_b->error != NULL)
199 return 0;
201 if (subtask_a->error == NULL && subtask_b->error != NULL)
202 return -1;
204 if (subtask_a->error != NULL && subtask_b->error == NULL)
205 return 1;
207 if (subtask_a->priority == subtask_b->priority)
208 return 0;
210 return (subtask_a->priority < subtask_b->priority) ? -1 : 1;
213 static void
214 async_subtask_complete (AsyncSubtask *async_subtask)
216 GSimpleAsyncResult *simple;
217 AsyncContext *async_context;
218 gboolean cancel_subtasks = FALSE;
219 gdouble seconds_elapsed;
221 simple = async_subtask->simple;
222 async_context = g_simple_async_result_get_op_res_gpointer (simple);
224 g_mutex_lock (&async_context->lock);
226 seconds_elapsed = g_timer_elapsed (async_context->timer, NULL);
228 /* Discard successfully completed subtasks with no match found.
229 * Keep failed subtasks around so we have a GError to propagate
230 * if we need one, but those go on the end of the queue. */
232 if (async_subtask->stream != NULL) {
233 g_queue_insert_sorted (
234 &async_context->results,
235 async_subtask_ref (async_subtask),
236 (GCompareDataFunc) async_subtask_compare,
237 NULL);
239 /* If enough seconds have elapsed, just take the highest
240 * priority input stream we have. Cancel the unfinished
241 * subtasks and let them complete with an error. */
242 if (seconds_elapsed > ASYNC_TIMEOUT_SECONDS)
243 cancel_subtasks = TRUE;
245 } else if (async_subtask->error != NULL) {
246 g_queue_push_tail (
247 &async_context->results,
248 async_subtask_ref (async_subtask));
251 g_hash_table_remove (async_context->subtasks, async_subtask);
253 if (g_hash_table_size (async_context->subtasks) > 0) {
254 /* Let the remaining subtasks finish. */
255 goto exit;
258 /* The queue should be ordered now such that subtasks
259 * with input streams are before subtasks with errors.
260 * So just evaluate the first subtask on the queue. */
262 async_subtask = g_queue_pop_head (&async_context->results);
264 if (async_subtask != NULL) {
265 if (async_subtask->stream != NULL) {
266 async_context->stream =
267 g_converter_input_stream_new (
268 async_subtask->stream,
269 async_context->data_capture);
272 if (async_subtask->error != NULL) {
273 g_simple_async_result_take_error (
274 simple, async_subtask->error);
275 async_subtask->error = NULL;
278 async_subtask_unref (async_subtask);
281 g_simple_async_result_complete_in_idle (simple);
283 exit:
284 g_mutex_unlock (&async_context->lock);
286 if (cancel_subtasks) {
287 /* Call this after the mutex is unlocked. */
288 async_context_cancel_subtasks (async_context);
292 static void
293 async_context_cancelled_cb (GCancellable *cancellable,
294 AsyncContext *async_context)
296 async_context_cancel_subtasks (async_context);
299 static AsyncContext *
300 async_context_new (EDataCapture *data_capture,
301 GCancellable *cancellable)
303 AsyncContext *async_context;
305 async_context = g_slice_new0 (AsyncContext);
306 g_mutex_init (&async_context->lock);
307 async_context->timer = g_timer_new ();
309 async_context->subtasks = g_hash_table_new_full (
310 (GHashFunc) g_direct_hash,
311 (GEqualFunc) g_direct_equal,
312 (GDestroyNotify) async_subtask_unref,
313 (GDestroyNotify) NULL);
315 async_context->data_capture = g_object_ref (data_capture);
317 if (G_IS_CANCELLABLE (cancellable)) {
318 gulong handler_id;
320 async_context->cancellable = g_object_ref (cancellable);
322 handler_id = g_cancellable_connect (
323 async_context->cancellable,
324 G_CALLBACK (async_context_cancelled_cb),
325 async_context,
326 (GDestroyNotify) NULL);
327 async_context->cancelled_handler_id = handler_id;
330 return async_context;
333 static void
334 async_context_free (AsyncContext *async_context)
336 /* Do this first so the callback won't fire
337 * while we're dismantling the AsyncContext. */
338 if (async_context->cancelled_handler_id > 0)
339 g_cancellable_disconnect (
340 async_context->cancellable,
341 async_context->cancelled_handler_id);
343 g_mutex_clear (&async_context->lock);
344 g_timer_destroy (async_context->timer);
346 g_hash_table_destroy (async_context->subtasks);
348 g_clear_object (&async_context->stream);
349 g_clear_object (&async_context->data_capture);
350 g_clear_object (&async_context->cancellable);
352 g_slice_free (AsyncContext, async_context);
355 static void
356 async_context_cancel_subtasks (AsyncContext *async_context)
358 GMainContext *main_context;
359 GList *list, *link;
361 main_context = g_main_context_ref_thread_default ();
363 g_mutex_lock (&async_context->lock);
365 list = g_hash_table_get_keys (async_context->subtasks);
367 /* XXX Cancel subtasks from idle callbacks to make sure we don't
368 * finalize the GSimpleAsyncResult during a "cancelled" signal
369 * emission from the main task's GCancellable. That will make
370 * g_cancellable_disconnect() in async_context_free() deadlock. */
371 for (link = list; link != NULL; link = g_list_next (link)) {
372 AsyncSubtask *async_subtask = link->data;
373 GSource *idle_source;
375 idle_source = g_idle_source_new ();
376 g_source_set_priority (idle_source, G_PRIORITY_HIGH_IDLE);
377 g_source_set_callback (
378 idle_source,
379 async_subtask_cancel_idle_cb,
380 async_subtask_ref (async_subtask),
381 (GDestroyNotify) async_subtask_unref);
382 g_source_attach (idle_source, main_context);
383 g_source_unref (idle_source);
386 g_list_free (list);
388 g_mutex_unlock (&async_context->lock);
390 g_main_context_unref (main_context);
393 static DataCaptureClosure *
394 data_capture_closure_new (EPhotoCache *photo_cache,
395 const gchar *email_address)
397 DataCaptureClosure *closure;
399 closure = g_slice_new0 (DataCaptureClosure);
400 g_weak_ref_set (&closure->photo_cache, photo_cache);
401 closure->email_address = g_strdup (email_address);
403 return closure;
406 static void
407 data_capture_closure_free (DataCaptureClosure *closure)
409 g_weak_ref_set (&closure->photo_cache, NULL);
410 g_free (closure->email_address);
412 g_slice_free (DataCaptureClosure, closure);
415 static PhotoData *
416 photo_data_new (GBytes *bytes)
418 PhotoData *photo_data;
420 photo_data = g_slice_new0 (PhotoData);
421 photo_data->ref_count = 1;
422 g_mutex_init (&photo_data->lock);
424 if (bytes != NULL)
425 photo_data->bytes = g_bytes_ref (bytes);
427 return photo_data;
430 static PhotoData *
431 photo_data_ref (PhotoData *photo_data)
433 g_return_val_if_fail (photo_data != NULL, NULL);
434 g_return_val_if_fail (photo_data->ref_count > 0, NULL);
436 g_atomic_int_inc (&photo_data->ref_count);
438 return photo_data;
441 static void
442 photo_data_unref (PhotoData *photo_data)
444 g_return_if_fail (photo_data != NULL);
445 g_return_if_fail (photo_data->ref_count > 0);
447 if (g_atomic_int_dec_and_test (&photo_data->ref_count)) {
448 g_mutex_clear (&photo_data->lock);
449 if (photo_data->bytes != NULL)
450 g_bytes_unref (photo_data->bytes);
451 g_slice_free (PhotoData, photo_data);
455 static GBytes *
456 photo_data_ref_bytes (PhotoData *photo_data)
458 GBytes *bytes = NULL;
460 g_mutex_lock (&photo_data->lock);
462 if (photo_data->bytes != NULL)
463 bytes = g_bytes_ref (photo_data->bytes);
465 g_mutex_unlock (&photo_data->lock);
467 return bytes;
470 static void
471 photo_data_set_bytes (PhotoData *photo_data,
472 GBytes *bytes)
474 g_mutex_lock (&photo_data->lock);
476 if (photo_data->bytes != NULL) {
477 g_bytes_unref (photo_data->bytes);
478 photo_data->bytes = NULL;
481 if (bytes != NULL)
482 photo_data->bytes = g_bytes_ref (bytes);
484 g_mutex_unlock (&photo_data->lock);
487 static gchar *
488 photo_ht_normalize_key (const gchar *email_address)
490 gchar *lowercase_email_address;
491 gchar *collation_key;
493 lowercase_email_address = g_utf8_strdown (email_address, -1);
494 collation_key = g_utf8_collate_key (lowercase_email_address, -1);
495 g_free (lowercase_email_address);
497 return collation_key;
500 static void
501 photo_ht_insert (EPhotoCache *photo_cache,
502 const gchar *email_address,
503 GBytes *bytes)
505 GHashTable *photo_ht;
506 GQueue *photo_ht_keys;
507 PhotoData *photo_data;
508 gchar *key;
510 g_return_if_fail (email_address != NULL);
512 photo_ht = photo_cache->priv->photo_ht;
513 photo_ht_keys = &photo_cache->priv->photo_ht_keys;
515 key = photo_ht_normalize_key (email_address);
517 g_mutex_lock (&photo_cache->priv->photo_ht_lock);
519 photo_data = g_hash_table_lookup (photo_ht, key);
521 if (photo_data != NULL) {
522 GList *link;
524 /* Replace the old photo data if we have new photo
525 * data, otherwise leave the old photo data alone. */
526 if (bytes != NULL)
527 photo_data_set_bytes (photo_data, bytes);
529 /* Move the key to the head of the MRU queue. */
530 link = g_queue_find_custom (
531 photo_ht_keys, key,
532 (GCompareFunc) strcmp);
533 if (link != NULL) {
534 g_queue_unlink (photo_ht_keys, link);
535 g_queue_push_head_link (photo_ht_keys, link);
537 } else {
538 photo_data = photo_data_new (bytes);
540 g_hash_table_insert (
541 photo_ht, g_strdup (key),
542 photo_data_ref (photo_data));
544 /* Push the key to the head of the MRU queue. */
545 g_queue_push_head (photo_ht_keys, g_strdup (key));
547 /* Trim the cache if necessary. */
548 while (g_queue_get_length (photo_ht_keys) > MAX_CACHE_SIZE) {
549 gchar *oldest_key;
551 oldest_key = g_queue_pop_tail (photo_ht_keys);
552 g_hash_table_remove (photo_ht, oldest_key);
553 g_free (oldest_key);
556 photo_data_unref (photo_data);
559 /* Hash table and queue sizes should be equal at all times. */
560 g_warn_if_fail (
561 g_hash_table_size (photo_ht) ==
562 g_queue_get_length (photo_ht_keys));
564 g_mutex_unlock (&photo_cache->priv->photo_ht_lock);
566 g_free (key);
569 static gboolean
570 photo_ht_lookup (EPhotoCache *photo_cache,
571 const gchar *email_address,
572 GInputStream **out_stream)
574 GHashTable *photo_ht;
575 PhotoData *photo_data;
576 gboolean found = FALSE;
577 gchar *key;
579 g_return_val_if_fail (email_address != NULL, FALSE);
580 g_return_val_if_fail (out_stream != NULL, FALSE);
582 photo_ht = photo_cache->priv->photo_ht;
584 key = photo_ht_normalize_key (email_address);
586 g_mutex_lock (&photo_cache->priv->photo_ht_lock);
588 photo_data = g_hash_table_lookup (photo_ht, key);
590 if (photo_data != NULL) {
591 GBytes *bytes;
593 bytes = photo_data_ref_bytes (photo_data);
594 if (bytes != NULL) {
595 *out_stream =
596 g_memory_input_stream_new_from_bytes (bytes);
597 g_bytes_unref (bytes);
598 } else {
599 *out_stream = NULL;
601 found = TRUE;
604 g_mutex_unlock (&photo_cache->priv->photo_ht_lock);
606 g_free (key);
608 return found;
611 static gboolean
612 photo_ht_remove (EPhotoCache *photo_cache,
613 const gchar *email_address)
615 GHashTable *photo_ht;
616 GQueue *photo_ht_keys;
617 gchar *key;
618 gboolean removed = FALSE;
620 g_return_val_if_fail (email_address != NULL, FALSE);
622 photo_ht = photo_cache->priv->photo_ht;
623 photo_ht_keys = &photo_cache->priv->photo_ht_keys;
625 key = photo_ht_normalize_key (email_address);
627 g_mutex_lock (&photo_cache->priv->photo_ht_lock);
629 if (g_hash_table_remove (photo_ht, key)) {
630 GList *link;
632 link = g_queue_find_custom (
633 photo_ht_keys, key,
634 (GCompareFunc) strcmp);
635 if (link != NULL) {
636 g_free (link->data);
637 g_queue_delete_link (photo_ht_keys, link);
638 removed = TRUE;
642 /* Hash table and queue sizes should be equal at all times. */
643 g_warn_if_fail (
644 g_hash_table_size (photo_ht) ==
645 g_queue_get_length (photo_ht_keys));
647 g_mutex_unlock (&photo_cache->priv->photo_ht_lock);
649 g_free (key);
651 return removed;
654 static void
655 photo_ht_remove_all (EPhotoCache *photo_cache)
657 GHashTable *photo_ht;
658 GQueue *photo_ht_keys;
660 photo_ht = photo_cache->priv->photo_ht;
661 photo_ht_keys = &photo_cache->priv->photo_ht_keys;
663 g_mutex_lock (&photo_cache->priv->photo_ht_lock);
665 g_hash_table_remove_all (photo_ht);
667 while (!g_queue_is_empty (photo_ht_keys))
668 g_free (g_queue_pop_head (photo_ht_keys));
670 g_mutex_unlock (&photo_cache->priv->photo_ht_lock);
673 static void
674 photo_cache_data_captured_cb (EDataCapture *data_capture,
675 GBytes *bytes,
676 DataCaptureClosure *closure)
678 EPhotoCache *photo_cache;
680 photo_cache = g_weak_ref_get (&closure->photo_cache);
682 if (photo_cache != NULL) {
683 e_photo_cache_add_photo (
684 photo_cache, closure->email_address, bytes);
685 g_object_unref (photo_cache);
689 static void
690 photo_cache_async_subtask_done_cb (GObject *source_object,
691 GAsyncResult *result,
692 gpointer user_data)
694 AsyncSubtask *async_subtask = user_data;
696 e_photo_source_get_photo_finish (
697 E_PHOTO_SOURCE (source_object),
698 result,
699 &async_subtask->stream,
700 &async_subtask->priority,
701 &async_subtask->error);
703 async_subtask_complete (async_subtask);
704 async_subtask_unref (async_subtask);
707 static void
708 photo_cache_set_client_cache (EPhotoCache *photo_cache,
709 EClientCache *client_cache)
711 g_return_if_fail (E_IS_CLIENT_CACHE (client_cache));
712 g_return_if_fail (photo_cache->priv->client_cache == NULL);
714 photo_cache->priv->client_cache = g_object_ref (client_cache);
717 static void
718 photo_cache_set_property (GObject *object,
719 guint property_id,
720 const GValue *value,
721 GParamSpec *pspec)
723 switch (property_id) {
724 case PROP_CLIENT_CACHE:
725 photo_cache_set_client_cache (
726 E_PHOTO_CACHE (object),
727 g_value_get_object (value));
728 return;
731 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
734 static void
735 photo_cache_get_property (GObject *object,
736 guint property_id,
737 GValue *value,
738 GParamSpec *pspec)
740 switch (property_id) {
741 case PROP_CLIENT_CACHE:
742 g_value_take_object (
743 value,
744 e_photo_cache_ref_client_cache (
745 E_PHOTO_CACHE (object)));
746 return;
749 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
752 static void
753 photo_cache_dispose (GObject *object)
755 EPhotoCachePrivate *priv;
757 priv = E_PHOTO_CACHE_GET_PRIVATE (object);
759 g_clear_object (&priv->client_cache);
761 photo_ht_remove_all (E_PHOTO_CACHE (object));
763 /* Chain up to parent's dispose() method. */
764 G_OBJECT_CLASS (e_photo_cache_parent_class)->dispose (object);
767 static void
768 photo_cache_finalize (GObject *object)
770 EPhotoCachePrivate *priv;
772 priv = E_PHOTO_CACHE_GET_PRIVATE (object);
774 g_main_context_unref (priv->main_context);
776 g_hash_table_destroy (priv->photo_ht);
777 g_hash_table_destroy (priv->sources_ht);
779 g_mutex_clear (&priv->photo_ht_lock);
780 g_mutex_clear (&priv->sources_ht_lock);
782 /* Chain up to parent's finalize() method. */
783 G_OBJECT_CLASS (e_photo_cache_parent_class)->finalize (object);
786 static void
787 photo_cache_constructed (GObject *object)
789 /* Chain up to parent's constructed() method. */
790 G_OBJECT_CLASS (e_photo_cache_parent_class)->constructed (object);
792 e_extensible_load_extensions (E_EXTENSIBLE (object));
795 static void
796 e_photo_cache_class_init (EPhotoCacheClass *class)
798 GObjectClass *object_class;
800 g_type_class_add_private (class, sizeof (EPhotoCachePrivate));
802 object_class = G_OBJECT_CLASS (class);
803 object_class->set_property = photo_cache_set_property;
804 object_class->get_property = photo_cache_get_property;
805 object_class->dispose = photo_cache_dispose;
806 object_class->finalize = photo_cache_finalize;
807 object_class->constructed = photo_cache_constructed;
810 * EPhotoCache:client-cache:
812 * Cache of shared #EClient instances.
814 g_object_class_install_property (
815 object_class,
816 PROP_CLIENT_CACHE,
817 g_param_spec_object (
818 "client-cache",
819 "Client Cache",
820 "Cache of shared EClient instances",
821 E_TYPE_CLIENT_CACHE,
822 G_PARAM_READWRITE |
823 G_PARAM_CONSTRUCT_ONLY |
824 G_PARAM_STATIC_STRINGS));
827 static void
828 e_photo_cache_init (EPhotoCache *photo_cache)
830 GHashTable *photo_ht;
831 GHashTable *sources_ht;
833 photo_ht = g_hash_table_new_full (
834 (GHashFunc) g_str_hash,
835 (GEqualFunc) g_str_equal,
836 (GDestroyNotify) g_free,
837 (GDestroyNotify) photo_data_unref);
839 sources_ht = g_hash_table_new_full (
840 (GHashFunc) g_direct_hash,
841 (GEqualFunc) g_direct_equal,
842 (GDestroyNotify) g_object_unref,
843 (GDestroyNotify) NULL);
845 photo_cache->priv = E_PHOTO_CACHE_GET_PRIVATE (photo_cache);
846 photo_cache->priv->main_context = g_main_context_ref_thread_default ();
847 photo_cache->priv->photo_ht = photo_ht;
848 photo_cache->priv->sources_ht = sources_ht;
850 g_mutex_init (&photo_cache->priv->photo_ht_lock);
851 g_mutex_init (&photo_cache->priv->sources_ht_lock);
855 * e_photo_cache_new:
856 * @client_cache: an #EClientCache
858 * Creates a new #EPhotoCache instance.
860 * Returns: an #EPhotoCache
862 EPhotoCache *
863 e_photo_cache_new (EClientCache *client_cache)
865 g_return_val_if_fail (E_IS_CLIENT_CACHE (client_cache), NULL);
867 return g_object_new (
868 E_TYPE_PHOTO_CACHE,
869 "client-cache", client_cache, NULL);
873 * e_photo_cache_ref_client_cache:
874 * @photo_cache: an #EPhotoCache
876 * Returns the #EClientCache passed to e_photo_cache_new().
878 * The returned #EClientCache is referenced for thread-safety and must be
879 * unreferenced with g_object_unref() when finished with it.
881 * Returns: an #EClientCache
883 EClientCache *
884 e_photo_cache_ref_client_cache (EPhotoCache *photo_cache)
886 g_return_val_if_fail (E_IS_PHOTO_CACHE (photo_cache), NULL);
888 return g_object_ref (photo_cache->priv->client_cache);
892 * e_photo_cache_add_photo_source:
893 * @photo_cache: an #EPhotoCache
894 * @photo_source: an #EPhotoSource
896 * Adds @photo_source as a potential source of photos.
898 void
899 e_photo_cache_add_photo_source (EPhotoCache *photo_cache,
900 EPhotoSource *photo_source)
902 GHashTable *sources_ht;
904 g_return_if_fail (E_IS_PHOTO_CACHE (photo_cache));
905 g_return_if_fail (E_IS_PHOTO_SOURCE (photo_source));
907 sources_ht = photo_cache->priv->sources_ht;
909 g_mutex_lock (&photo_cache->priv->sources_ht_lock);
911 g_hash_table_add (sources_ht, g_object_ref (photo_source));
913 g_mutex_unlock (&photo_cache->priv->sources_ht_lock);
917 * e_photo_cache_list_photo_sources:
918 * @photo_cache: an #EPhotoCache
920 * Returns a list of photo sources for @photo_cache.
922 * The sources returned in the list are referenced for thread-safety.
923 * They must each be unreferenced with g_object_unref() when finished
924 * with them. Free the returned list itself with g_list_free().
926 * An easy way to free the list property in one step is as follows:
928 * |[
929 * g_list_free_full (list, g_object_unref);
930 * ]|
932 * Returns: a sorted list of photo sources
934 GList *
935 e_photo_cache_list_photo_sources (EPhotoCache *photo_cache)
937 GHashTable *sources_ht;
938 GList *list;
940 g_return_val_if_fail (E_IS_PHOTO_CACHE (photo_cache), NULL);
942 sources_ht = photo_cache->priv->sources_ht;
944 g_mutex_lock (&photo_cache->priv->sources_ht_lock);
946 list = g_hash_table_get_keys (sources_ht);
947 g_list_foreach (list, (GFunc) g_object_ref, NULL);
949 g_mutex_unlock (&photo_cache->priv->sources_ht_lock);
951 return list;
955 * e_photo_cache_remove_photo_source:
956 * @photo_cache: an #EPhotoCache
957 * @photo_source: an #EPhotoSource
959 * Removes @photo_source as a potential source of photos.
961 * Returns: %TRUE if @photo_source was found and removed, %FALSE if not
963 gboolean
964 e_photo_cache_remove_photo_source (EPhotoCache *photo_cache,
965 EPhotoSource *photo_source)
967 GHashTable *sources_ht;
968 gboolean removed;
970 g_return_val_if_fail (E_IS_PHOTO_CACHE (photo_cache), FALSE);
971 g_return_val_if_fail (E_IS_PHOTO_SOURCE (photo_source), FALSE);
973 sources_ht = photo_cache->priv->sources_ht;
975 g_mutex_lock (&photo_cache->priv->sources_ht_lock);
977 removed = g_hash_table_remove (sources_ht, photo_source);
979 g_mutex_unlock (&photo_cache->priv->sources_ht_lock);
981 return removed;
985 * e_photo_cache_add_photo:
986 * @photo_cache: an #EPhotoCache
987 * @email_address: an email address
988 * @bytes: a #GBytes containing photo data, or %NULL
990 * Adds a cache entry for @email_address, such that subsequent photo requests
991 * for @email_address will yield a #GMemoryInputStream loaded with @bytes
992 * without consulting available photo sources.
994 * The @bytes argument can also be %NULL to indicate no photo is available for
995 * @email_address. Subsequent photo requests for @email_address will yield no
996 * input stream.
998 * The entry may be removed without notice however, subject to @photo_cache's
999 * internal caching policy.
1001 void
1002 e_photo_cache_add_photo (EPhotoCache *photo_cache,
1003 const gchar *email_address,
1004 GBytes *bytes)
1006 g_return_if_fail (E_IS_PHOTO_CACHE (photo_cache));
1007 g_return_if_fail (email_address != NULL);
1009 photo_ht_insert (photo_cache, email_address, bytes);
1013 * e_photo_cache_remove_photo:
1014 * @photo_cache: an #EPhotoCache
1015 * @email_address: an email address
1017 * Removes the cache entry for @email_address, if such an entry exists.
1019 * Returns: %TRUE if a cache entry was found and removed
1021 gboolean
1022 e_photo_cache_remove_photo (EPhotoCache *photo_cache,
1023 const gchar *email_address)
1025 g_return_val_if_fail (E_IS_PHOTO_CACHE (photo_cache), FALSE);
1026 g_return_val_if_fail (email_address != NULL, FALSE);
1028 return photo_ht_remove (photo_cache, email_address);
1032 * e_photo_cache_get_photo_sync:
1033 * @photo_cache: an #EPhotoCache
1034 * @email_address: an email address
1035 * @cancellable: optional #GCancellable object, or %NULL
1036 * @out_stream: return location for a #GInputStream, or %NULL
1037 * @error: return location for a #GError, or %NULL
1039 * Searches available photo sources for a photo associated with
1040 * @email_address.
1042 * If a match is found, a #GInputStream from which to read image data is
1043 * returned through the @out_stream return location. If no match is found,
1044 * the @out_stream return location is set to %NULL.
1046 * The return value indicates whether the search completed successfully,
1047 * not whether a match was found. If an error occurs, the function will
1048 * set @error and return %FALSE.
1050 * Returns: whether the search completed successfully
1052 gboolean
1053 e_photo_cache_get_photo_sync (EPhotoCache *photo_cache,
1054 const gchar *email_address,
1055 GCancellable *cancellable,
1056 GInputStream **out_stream,
1057 GError **error)
1059 EAsyncClosure *closure;
1060 GAsyncResult *result;
1061 gboolean success;
1063 closure = e_async_closure_new ();
1065 e_photo_cache_get_photo (
1066 photo_cache, email_address, cancellable,
1067 e_async_closure_callback, closure);
1069 result = e_async_closure_wait (closure);
1071 success = e_photo_cache_get_photo_finish (
1072 photo_cache, result, out_stream, error);
1074 e_async_closure_free (closure);
1076 return success;
1080 * e_photo_cache_get_photo:
1081 * @photo_cache: an #EPhotoCache
1082 * @email_address: an email address
1083 * @cancellable: optional #GCancellable object, or %NULL
1084 * @callback: a #GAsyncReadyCallback to call when the request is satisfied
1085 * @user_data: data to pass to the callback function
1087 * Asynchronously searches available photo sources for a photo associated
1088 * with @email_address.
1090 * When the operation is finished, @callback will be called. You can then
1091 * call e_photo_cache_get_photo_finish() to get the result of the operation.
1093 void
1094 e_photo_cache_get_photo (EPhotoCache *photo_cache,
1095 const gchar *email_address,
1096 GCancellable *cancellable,
1097 GAsyncReadyCallback callback,
1098 gpointer user_data)
1100 GSimpleAsyncResult *simple;
1101 AsyncContext *async_context;
1102 EDataCapture *data_capture;
1103 GInputStream *stream = NULL;
1104 GList *list, *link;
1106 g_return_if_fail (E_IS_PHOTO_CACHE (photo_cache));
1107 g_return_if_fail (email_address != NULL);
1109 /* This will be used to eavesdrop on the resulting input stream
1110 * for the purpose of adding the photo data to the photo cache. */
1111 data_capture = e_data_capture_new (photo_cache->priv->main_context);
1113 g_signal_connect_data (
1114 data_capture, "finished",
1115 G_CALLBACK (photo_cache_data_captured_cb),
1116 data_capture_closure_new (photo_cache, email_address),
1117 (GClosureNotify) data_capture_closure_free, 0);
1119 async_context = async_context_new (data_capture, cancellable);
1121 simple = g_simple_async_result_new (
1122 G_OBJECT (photo_cache), callback,
1123 user_data, e_photo_cache_get_photo);
1125 g_simple_async_result_set_check_cancellable (simple, cancellable);
1127 g_simple_async_result_set_op_res_gpointer (
1128 simple, async_context, (GDestroyNotify) async_context_free);
1130 /* Check if we have this email address already cached. */
1131 if (photo_ht_lookup (photo_cache, email_address, &stream)) {
1132 async_context->stream = stream; /* takes ownership */
1133 g_simple_async_result_complete_in_idle (simple);
1134 goto exit;
1137 list = e_photo_cache_list_photo_sources (photo_cache);
1139 if (list == NULL) {
1140 g_simple_async_result_complete_in_idle (simple);
1141 goto exit;
1144 g_mutex_lock (&async_context->lock);
1146 /* Dispatch a subtask for each photo source. */
1147 for (link = list; link != NULL; link = g_list_next (link)) {
1148 EPhotoSource *photo_source;
1149 AsyncSubtask *async_subtask;
1151 photo_source = E_PHOTO_SOURCE (link->data);
1152 async_subtask = async_subtask_new (photo_source, simple);
1154 g_hash_table_add (
1155 async_context->subtasks,
1156 async_subtask_ref (async_subtask));
1158 e_photo_source_get_photo (
1159 photo_source, email_address,
1160 async_subtask->cancellable,
1161 photo_cache_async_subtask_done_cb,
1162 async_subtask_ref (async_subtask));
1164 async_subtask_unref (async_subtask);
1167 g_mutex_unlock (&async_context->lock);
1169 g_list_free_full (list, (GDestroyNotify) g_object_unref);
1171 /* Check if we were cancelled while dispatching subtasks. */
1172 if (g_cancellable_is_cancelled (cancellable))
1173 async_context_cancel_subtasks (async_context);
1175 exit:
1176 g_object_unref (simple);
1177 g_object_unref (data_capture);
1181 * e_photo_cache_get_photo_finish:
1182 * @photo_cache: an #EPhotoCache
1183 * @result: a #GAsyncResult
1184 * @out_stream: return location for a #GInputStream, or %NULL
1185 * @error: return location for a #GError, or %NULL
1187 * Finishes the operation started with e_photo_cache_get_photo().
1189 * If a match was found, a #GInputStream from which to read image data is
1190 * returned through the @out_stream return location. If no match was found,
1191 * the @out_stream return location is set to %NULL.
1193 * The return value indicates whether the search completed successfully,
1194 * not whether a match was found. If an error occurred, the function will
1195 * set @error and return %FALSE.
1197 * Returns: whether the search completed successfully
1199 gboolean
1200 e_photo_cache_get_photo_finish (EPhotoCache *photo_cache,
1201 GAsyncResult *result,
1202 GInputStream **out_stream,
1203 GError **error)
1205 GSimpleAsyncResult *simple;
1206 AsyncContext *async_context;
1208 g_return_val_if_fail (
1209 g_simple_async_result_is_valid (
1210 result, G_OBJECT (photo_cache),
1211 e_photo_cache_get_photo), FALSE);
1213 simple = G_SIMPLE_ASYNC_RESULT (result);
1214 async_context = g_simple_async_result_get_op_res_gpointer (simple);
1216 if (g_simple_async_result_propagate_error (simple, error))
1217 return FALSE;
1219 if (out_stream != NULL) {
1220 if (async_context->stream != NULL)
1221 *out_stream = g_object_ref (async_context->stream);
1222 else
1223 *out_stream = NULL;
1226 return TRUE;