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
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/>.
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
30 #include "e-photo-cache.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
;
67 GHashTable
*sources_ht
;
68 GMutex sources_ht_lock
;
71 struct _AsyncContext
{
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
;
93 struct _DataCaptureClosure
{
99 volatile gint ref_count
;
109 /* Forward Declarations */
110 static void async_context_cancel_subtasks (AsyncContext
*async_context
);
112 G_DEFINE_TYPE_WITH_CODE (
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
;
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
) {
162 "%s: Unpropagated error in %s subtask: %s",
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
);
180 async_subtask_cancel_idle_cb (gpointer user_data
)
182 AsyncSubtask
*async_subtask
= user_data
;
184 g_cancellable_cancel (async_subtask
->cancellable
);
190 async_subtask_compare (gconstpointer a
,
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
)
201 if (subtask_a
->error
== NULL
&& subtask_b
->error
!= NULL
)
204 if (subtask_a
->error
!= NULL
&& subtask_b
->error
== NULL
)
207 if (subtask_a
->priority
== subtask_b
->priority
)
210 return (subtask_a
->priority
< subtask_b
->priority
) ? -1 : 1;
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
,
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
) {
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. */
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
);
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
);
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
)) {
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
),
326 (GDestroyNotify
) NULL
);
327 async_context
->cancelled_handler_id
= handler_id
;
330 return async_context
;
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
);
356 async_context_cancel_subtasks (AsyncContext
*async_context
)
358 GMainContext
*main_context
;
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 (
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
);
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
);
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
);
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
);
425 photo_data
->bytes
= g_bytes_ref (bytes
);
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
);
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
);
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
);
471 photo_data_set_bytes (PhotoData
*photo_data
,
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
;
482 photo_data
->bytes
= g_bytes_ref (bytes
);
484 g_mutex_unlock (&photo_data
->lock
);
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
;
501 photo_ht_insert (EPhotoCache
*photo_cache
,
502 const gchar
*email_address
,
505 GHashTable
*photo_ht
;
506 GQueue
*photo_ht_keys
;
507 PhotoData
*photo_data
;
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
) {
524 /* Replace the old photo data if we have new photo
525 * data, otherwise leave the old photo data alone. */
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 (
532 (GCompareFunc
) strcmp
);
534 g_queue_unlink (photo_ht_keys
, link
);
535 g_queue_push_head_link (photo_ht_keys
, link
);
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
) {
551 oldest_key
= g_queue_pop_tail (photo_ht_keys
);
552 g_hash_table_remove (photo_ht
, oldest_key
);
556 photo_data_unref (photo_data
);
559 /* Hash table and queue sizes should be equal at all times. */
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
);
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
;
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
) {
593 bytes
= photo_data_ref_bytes (photo_data
);
596 g_memory_input_stream_new_from_bytes (bytes
);
597 g_bytes_unref (bytes
);
604 g_mutex_unlock (&photo_cache
->priv
->photo_ht_lock
);
612 photo_ht_remove (EPhotoCache
*photo_cache
,
613 const gchar
*email_address
)
615 GHashTable
*photo_ht
;
616 GQueue
*photo_ht_keys
;
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
)) {
632 link
= g_queue_find_custom (
634 (GCompareFunc
) strcmp
);
637 g_queue_delete_link (photo_ht_keys
, link
);
642 /* Hash table and queue sizes should be equal at all times. */
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
);
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
);
674 photo_cache_data_captured_cb (EDataCapture
*data_capture
,
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
);
690 photo_cache_async_subtask_done_cb (GObject
*source_object
,
691 GAsyncResult
*result
,
694 AsyncSubtask
*async_subtask
= user_data
;
696 e_photo_source_get_photo_finish (
697 E_PHOTO_SOURCE (source_object
),
699 &async_subtask
->stream
,
700 &async_subtask
->priority
,
701 &async_subtask
->error
);
703 async_subtask_complete (async_subtask
);
704 async_subtask_unref (async_subtask
);
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
);
718 photo_cache_set_property (GObject
*object
,
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
));
731 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
735 photo_cache_get_property (GObject
*object
,
740 switch (property_id
) {
741 case PROP_CLIENT_CACHE
:
742 g_value_take_object (
744 e_photo_cache_ref_client_cache (
745 E_PHOTO_CACHE (object
)));
749 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
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
);
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
);
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
));
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 (
817 g_param_spec_object (
820 "Cache of shared EClient instances",
823 G_PARAM_CONSTRUCT_ONLY
|
824 G_PARAM_STATIC_STRINGS
));
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
);
856 * @client_cache: an #EClientCache
858 * Creates a new #EPhotoCache instance.
860 * Returns: an #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 (
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
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.
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:
929 * g_list_free_full (list, g_object_unref);
932 * Returns: a sorted list of photo sources
935 e_photo_cache_list_photo_sources (EPhotoCache
*photo_cache
)
937 GHashTable
*sources_ht
;
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
);
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
964 e_photo_cache_remove_photo_source (EPhotoCache
*photo_cache
,
965 EPhotoSource
*photo_source
)
967 GHashTable
*sources_ht
;
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
);
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
998 * The entry may be removed without notice however, subject to @photo_cache's
999 * internal caching policy.
1002 e_photo_cache_add_photo (EPhotoCache
*photo_cache
,
1003 const gchar
*email_address
,
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
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
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
1053 e_photo_cache_get_photo_sync (EPhotoCache
*photo_cache
,
1054 const gchar
*email_address
,
1055 GCancellable
*cancellable
,
1056 GInputStream
**out_stream
,
1059 EAsyncClosure
*closure
;
1060 GAsyncResult
*result
;
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
);
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.
1094 e_photo_cache_get_photo (EPhotoCache
*photo_cache
,
1095 const gchar
*email_address
,
1096 GCancellable
*cancellable
,
1097 GAsyncReadyCallback callback
,
1100 GSimpleAsyncResult
*simple
;
1101 AsyncContext
*async_context
;
1102 EDataCapture
*data_capture
;
1103 GInputStream
*stream
= NULL
;
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
);
1137 list
= e_photo_cache_list_photo_sources (photo_cache
);
1140 g_simple_async_result_complete_in_idle (simple
);
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
);
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
);
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
1200 e_photo_cache_get_photo_finish (EPhotoCache
*photo_cache
,
1201 GAsyncResult
*result
,
1202 GInputStream
**out_stream
,
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
))
1219 if (out_stream
!= NULL
) {
1220 if (async_context
->stream
!= NULL
)
1221 *out_stream
= g_object_ref (async_context
->stream
);