aboutsummaryrefslogtreecommitdiffstats
path: root/modules/contact-photos
diff options
context:
space:
mode:
Diffstat (limited to 'modules/contact-photos')
-rw-r--r--modules/contact-photos/Makefile.am30
-rw-r--r--modules/contact-photos/e-contact-photo-source.c432
-rw-r--r--modules/contact-photos/e-contact-photo-source.h73
-rw-r--r--modules/contact-photos/e-photo-cache-contact-loader.c252
-rw-r--r--modules/contact-photos/e-photo-cache-contact-loader.h66
-rw-r--r--modules/contact-photos/evolution-contact-photos.c40
6 files changed, 893 insertions, 0 deletions
diff --git a/modules/contact-photos/Makefile.am b/modules/contact-photos/Makefile.am
new file mode 100644
index 0000000000..83b934471f
--- /dev/null
+++ b/modules/contact-photos/Makefile.am
@@ -0,0 +1,30 @@
+module_LTLIBRARIES = module-contact-photos.la
+
+module_contact_photos_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -I$(top_srcdir) \
+ -DG_LOG_DOMAIN=\"evolution-contact-photos\" \
+ $(EVOLUTION_DATA_SERVER_CFLAGS) \
+ $(GNOME_PLATFORM_CFLAGS) \
+ $(GTKHTML_CFLAGS) \
+ $(NULL)
+
+module_contact_photos_la_SOURCES = \
+ evolution-contact-photos.c \
+ e-contact-photo-source.c \
+ e-contact-photo-source.h \
+ e-photo-cache-contact-loader.c \
+ e-photo-cache-contact-loader.h \
+ $(NULL)
+
+module_contact_photos_la_LIBADD = \
+ $(top_builddir)/e-util/libeutil.la \
+ $(EVOLUTION_DATA_SERVER_LIBS) \
+ $(GNOME_PLATFORM_LIBS) \
+ $(GTKHTML_LIBS) \
+ $(NULL)
+
+module_contact_photos_la_LDFLAGS = \
+ -module -avoid-version $(NO_UNDEFINED)
+
+-include $(top_srcdir)/git.mk
diff --git a/modules/contact-photos/e-contact-photo-source.c b/modules/contact-photos/e-contact-photo-source.c
new file mode 100644
index 0000000000..b290a2168f
--- /dev/null
+++ b/modules/contact-photos/e-contact-photo-source.c
@@ -0,0 +1,432 @@
+/*
+ * e-contact-photo-source.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-contact-photo-source.h"
+
+#define E_CONTACT_PHOTO_SOURCE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_CONTACT_PHOTO_SOURCE, EContactPhotoSourcePrivate))
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _EContactPhotoSourcePrivate {
+ EClientCache *client_cache;
+ ESource *source;
+};
+
+struct _AsyncContext {
+ gchar *query_string;
+ GInputStream *stream;
+ gint priority;
+};
+
+enum {
+ PROP_0,
+ PROP_CLIENT_CACHE,
+ PROP_SOURCE
+};
+
+/* Forward Declarations */
+static void e_contact_photo_source_interface_init
+ (EPhotoSourceInterface *interface);
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (
+ EContactPhotoSource,
+ e_contact_photo_source,
+ G_TYPE_OBJECT,
+ 0,
+ G_IMPLEMENT_INTERFACE_DYNAMIC (
+ E_TYPE_PHOTO_SOURCE,
+ e_contact_photo_source_interface_init))
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+ g_free (async_context->query_string);
+ g_clear_object (&async_context->stream);
+
+ g_slice_free (AsyncContext, async_context);
+}
+
+static EContactPhoto *
+contact_photo_source_extract_photo (EContact *contact,
+ gint *out_priority)
+{
+ EContactPhoto *photo;
+
+ photo = e_contact_get (contact, E_CONTACT_PHOTO);
+ *out_priority = G_PRIORITY_HIGH;
+
+ if (photo == NULL) {
+ photo = e_contact_get (contact, E_CONTACT_LOGO);
+ *out_priority = G_PRIORITY_LOW;
+ }
+
+ return photo;
+}
+
+static void
+contact_photo_source_get_photo_thread (GSimpleAsyncResult *simple,
+ GObject *source_object,
+ GCancellable *cancellable)
+{
+ EContactPhotoSource *photo_source;
+ AsyncContext *async_context;
+ EClientCache *client_cache;
+ ESourceRegistry *registry;
+ EClient *client = NULL;
+ ESource *source;
+ GSList *slist = NULL;
+ GSList *slink;
+ GError *error = NULL;
+
+ photo_source = E_CONTACT_PHOTO_SOURCE (source_object);
+ async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ client_cache = e_contact_photo_source_ref_client_cache (photo_source);
+ source = e_contact_photo_source_ref_source (photo_source);
+ registry = e_client_cache_ref_registry (client_cache);
+
+ /* Return no result if the source is disabled. */
+ if (!e_source_registry_check_enabled (registry, source))
+ goto exit;
+
+ client = e_client_cache_get_client_sync (
+ client_cache, source,
+ E_SOURCE_EXTENSION_ADDRESS_BOOK,
+ cancellable, &error);
+
+ /* Sanity check. */
+ g_return_if_fail (
+ ((client != NULL) && (error == NULL)) ||
+ ((client == NULL) && (error != NULL)));
+
+ if (error != NULL) {
+ g_simple_async_result_take_error (simple, error);
+ goto exit;
+ }
+
+ e_book_client_get_contacts_sync (
+ E_BOOK_CLIENT (client),
+ async_context->query_string,
+ &slist, cancellable, &error);
+
+ if (error != NULL) {
+ g_warn_if_fail (slist == NULL);
+ g_simple_async_result_take_error (simple, error);
+ goto exit;
+ }
+
+ /* See if any of the contacts have a photo. */
+ for (slink = slist; slink != NULL; slink = g_slist_next (slink)) {
+ EContact *contact = E_CONTACT (slink->data);
+ GInputStream *stream = NULL;
+ EContactPhoto *photo;
+
+ photo = contact_photo_source_extract_photo (
+ contact, &async_context->priority);
+
+ if (photo == NULL)
+ continue;
+
+ /* Stream takes ownership of the inlined data. */
+ if (photo->type == E_CONTACT_PHOTO_TYPE_INLINED) {
+ stream = g_memory_input_stream_new_from_data (
+ photo->data.inlined.data,
+ photo->data.inlined.length,
+ (GDestroyNotify) g_free);
+ photo->data.inlined.data = NULL;
+ photo->data.inlined.length = 0;
+
+ } else {
+ GFileInputStream *file_stream;
+ GFile *file;
+
+ file = g_file_new_for_uri (photo->data.uri);
+
+ /* Disregard errors and proceed as
+ * though the contact has no photo. */
+
+ /* XXX Return type should have been GInputStream. */
+ file_stream = g_file_read (file, cancellable, NULL);
+ if (file_stream != NULL)
+ stream = G_INPUT_STREAM (file_stream);
+
+ g_object_unref (file);
+ }
+
+ e_contact_photo_free (photo);
+
+ /* Stop on the first input stream. */
+ if (stream != NULL) {
+ async_context->stream = g_object_ref (stream);
+ g_object_unref (stream);
+ break;
+ }
+ }
+
+ g_slist_free_full (slist, (GDestroyNotify) g_object_unref);
+
+exit:
+ g_clear_object (&client_cache);
+ g_clear_object (&registry);
+ g_clear_object (&client);
+ g_clear_object (&source);
+}
+
+static void
+contact_photo_source_set_client_cache (EContactPhotoSource *photo_source,
+ EClientCache *client_cache)
+{
+ g_return_if_fail (E_IS_CLIENT_CACHE (client_cache));
+ g_return_if_fail (photo_source->priv->client_cache == NULL);
+
+ photo_source->priv->client_cache = g_object_ref (client_cache);
+}
+
+static void
+contact_photo_source_set_source (EContactPhotoSource *photo_source,
+ ESource *source)
+{
+ g_return_if_fail (E_IS_SOURCE (source));
+ g_return_if_fail (photo_source->priv->source == NULL);
+
+ photo_source->priv->source = g_object_ref (source);
+}
+
+static void
+contact_photo_source_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CLIENT_CACHE:
+ contact_photo_source_set_client_cache (
+ E_CONTACT_PHOTO_SOURCE (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_SOURCE:
+ contact_photo_source_set_source (
+ E_CONTACT_PHOTO_SOURCE (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+contact_photo_source_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CLIENT_CACHE:
+ g_value_take_object (
+ value,
+ e_contact_photo_source_ref_client_cache (
+ E_CONTACT_PHOTO_SOURCE (object)));
+ return;
+
+ case PROP_SOURCE:
+ g_value_take_object (
+ value,
+ e_contact_photo_source_ref_source (
+ E_CONTACT_PHOTO_SOURCE (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+contact_photo_source_dispose (GObject *object)
+{
+ EContactPhotoSourcePrivate *priv;
+
+ priv = E_CONTACT_PHOTO_SOURCE_GET_PRIVATE (object);
+
+ g_clear_object (&priv->client_cache);
+ g_clear_object (&priv->source);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_contact_photo_source_parent_class)->dispose (object);
+}
+
+static void
+contact_photo_source_get_photo (EPhotoSource *photo_source,
+ const gchar *email_address,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *async_context;
+ EBookQuery *book_query;
+
+ book_query = e_book_query_field_test (
+ E_CONTACT_EMAIL, E_BOOK_QUERY_IS, email_address);
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->query_string = e_book_query_to_string (book_query);
+
+ e_book_query_unref (book_query);
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (photo_source), callback,
+ user_data, contact_photo_source_get_photo);
+
+ g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, async_context, (GDestroyNotify) async_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, contact_photo_source_get_photo_thread,
+ G_PRIORITY_DEFAULT, cancellable);
+
+ g_object_unref (simple);
+}
+
+static gboolean
+contact_photo_source_get_photo_finish (EPhotoSource *photo_source,
+ GAsyncResult *result,
+ GInputStream **out_stream,
+ gint *out_priority,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *async_context;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (photo_source),
+ contact_photo_source_get_photo), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+
+ if (async_context->stream != NULL) {
+ *out_stream = g_object_ref (async_context->stream);
+ if (out_priority != NULL)
+ *out_priority = async_context->priority;
+ } else {
+ *out_stream = NULL;
+ }
+
+ return TRUE;
+}
+
+static void
+e_contact_photo_source_class_init (EContactPhotoSourceClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (EContactPhotoSourcePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = contact_photo_source_set_property;
+ object_class->get_property = contact_photo_source_get_property;
+ object_class->dispose = contact_photo_source_dispose;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CLIENT_CACHE,
+ g_param_spec_object (
+ "client-cache",
+ "Client Cache",
+ "Cache of shared EClient instances",
+ E_TYPE_CLIENT_CACHE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SOURCE,
+ g_param_spec_object (
+ "source",
+ "Source",
+ "An address book source",
+ E_TYPE_SOURCE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+e_contact_photo_source_class_finalize (EContactPhotoSourceClass *class)
+{
+}
+
+static void
+e_contact_photo_source_interface_init (EPhotoSourceInterface *interface)
+{
+ interface->get_photo = contact_photo_source_get_photo;
+ interface->get_photo_finish = contact_photo_source_get_photo_finish;
+}
+
+static void
+e_contact_photo_source_init (EContactPhotoSource *photo_source)
+{
+ photo_source->priv = E_CONTACT_PHOTO_SOURCE_GET_PRIVATE (photo_source);
+}
+
+void
+e_contact_photo_source_type_register (GTypeModule *type_module)
+{
+ /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
+ * function, so we have to wrap it with a public function in
+ * order to register types from a separate compilation unit. */
+ e_contact_photo_source_register_type (type_module);
+}
+
+EPhotoSource *
+e_contact_photo_source_new (EClientCache *client_cache,
+ ESource *source)
+{
+ g_return_val_if_fail (E_IS_CLIENT_CACHE (client_cache), NULL);
+ g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+
+ return g_object_new (
+ E_TYPE_CONTACT_PHOTO_SOURCE,
+ "client-cache", client_cache,
+ "source", source,
+ NULL);
+}
+
+EClientCache *
+e_contact_photo_source_ref_client_cache (EContactPhotoSource *photo_source)
+{
+ g_return_val_if_fail (E_IS_CONTACT_PHOTO_SOURCE (photo_source), NULL);
+
+ return g_object_ref (photo_source->priv->client_cache);
+}
+
+ESource *
+e_contact_photo_source_ref_source (EContactPhotoSource *photo_source)
+{
+ g_return_val_if_fail (E_IS_CONTACT_PHOTO_SOURCE (photo_source), NULL);
+
+ return g_object_ref (photo_source->priv->source);
+}
+
diff --git a/modules/contact-photos/e-contact-photo-source.h b/modules/contact-photos/e-contact-photo-source.h
new file mode 100644
index 0000000000..2720c18acc
--- /dev/null
+++ b/modules/contact-photos/e-contact-photo-source.h
@@ -0,0 +1,73 @@
+/*
+ * e-contact-photo-source.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_CONTACT_PHOTO_SOURCE_H
+#define E_CONTACT_PHOTO_SOURCE_H
+
+#include <e-util/e-util.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CONTACT_PHOTO_SOURCE \
+ (e_contact_photo_source_get_type ())
+#define E_CONTACT_PHOTO_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_CONTACT_PHOTO_SOURCE, EContactPhotoSource))
+#define E_CONTACT_PHOTO_SOURCE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_CONTACT_PHOTO_SOURCE, EContactPhotoSourceClass))
+#define E_IS_CONTACT_PHOTO_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_CONTACT_PHOTO_SOURCE))
+#define E_IS_CONTACT_PHOTO_SOURCE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_CONTACT_PHOTO_SOURCE))
+#define E_CONTACT_PHOTO_SOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_CONTACT_PHOTO_SOURCE, EContactPhotoSourceClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EContactPhotoSource EContactPhotoSource;
+typedef struct _EContactPhotoSourceClass EContactPhotoSourceClass;
+typedef struct _EContactPhotoSourcePrivate EContactPhotoSourcePrivate;
+
+struct _EContactPhotoSource {
+ GObject parent;
+ EContactPhotoSourcePrivate *priv;
+};
+
+struct _EContactPhotoSourceClass {
+ GObjectClass parent_class;
+};
+
+GType e_contact_photo_source_get_type
+ (void) G_GNUC_CONST;
+void e_contact_photo_source_type_register
+ (GTypeModule *type_module);
+EPhotoSource * e_contact_photo_source_new
+ (EClientCache *client_cache,
+ ESource *source);
+EClientCache * e_contact_photo_source_ref_client_cache
+ (EContactPhotoSource *photo_source);
+ESource * e_contact_photo_source_ref_source
+ (EContactPhotoSource *photo_source);
+
+G_END_DECLS
+
+#endif /* E_CONTACT_PHOTO_SOURCE_H */
+
diff --git a/modules/contact-photos/e-photo-cache-contact-loader.c b/modules/contact-photos/e-photo-cache-contact-loader.c
new file mode 100644
index 0000000000..122c6b1b5c
--- /dev/null
+++ b/modules/contact-photos/e-photo-cache-contact-loader.c
@@ -0,0 +1,252 @@
+/*
+ * e-photo-cache-contact-loader.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-photo-cache-contact-loader.h"
+
+#include "e-contact-photo-source.h"
+
+#define E_PHOTO_CACHE_CONTACT_LOADER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_PHOTO_CACHE_CONTACT_LOADER, EPhotoCacheContactLoaderPrivate))
+
+struct _EPhotoCacheContactLoaderPrivate {
+ ESourceRegistry *registry;
+ gulong source_added_handler_id;
+ gulong source_removed_handler_id;
+
+ /* ESource -> EPhotoSource */
+ GHashTable *photo_sources;
+};
+
+G_DEFINE_DYNAMIC_TYPE (
+ EPhotoCacheContactLoader,
+ e_photo_cache_contact_loader,
+ E_TYPE_EXTENSION)
+
+static EPhotoCache *
+photo_cache_contact_loader_get_photo_cache (EPhotoCacheContactLoader *loader)
+{
+ EExtensible *extensible;
+
+ extensible = e_extension_get_extensible (E_EXTENSION (loader));
+
+ return E_PHOTO_CACHE (extensible);
+}
+
+static void
+photo_cache_contact_loader_add_source (EPhotoCacheContactLoader *loader,
+ ESource *source)
+{
+ EPhotoCache *photo_cache;
+ EPhotoSource *photo_source;
+ EClientCache *client_cache;
+
+ photo_cache = photo_cache_contact_loader_get_photo_cache (loader);
+ client_cache = e_photo_cache_ref_client_cache (photo_cache);
+
+ photo_source = e_contact_photo_source_new (client_cache, source);
+ g_hash_table_insert (
+ loader->priv->photo_sources,
+ g_object_ref (source),
+ g_object_ref (photo_source));
+ e_photo_cache_add_photo_source (photo_cache, photo_source);
+ g_object_unref (photo_source);
+
+ g_object_unref (client_cache);
+}
+
+static void
+photo_cache_contact_loader_remove_source (EPhotoCacheContactLoader *loader,
+ ESource *source)
+{
+ EPhotoCache *photo_cache;
+ EPhotoSource *photo_source;
+ GHashTable *hash_table;
+
+ photo_cache = photo_cache_contact_loader_get_photo_cache (loader);
+
+ hash_table = loader->priv->photo_sources;
+ photo_source = g_hash_table_lookup (hash_table, source);
+ if (photo_source != NULL) {
+ e_photo_cache_remove_photo_source (photo_cache, photo_source);
+ g_hash_table_remove (hash_table, source);
+ }
+}
+
+static void
+photo_cache_contact_loader_source_added_cb (ESourceRegistry *registry,
+ ESource *source,
+ EPhotoCacheContactLoader *loader)
+{
+ const gchar *extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
+
+ if (e_source_has_extension (source, extension_name))
+ photo_cache_contact_loader_add_source (loader, source);
+}
+
+static void
+photo_cache_contact_loader_source_removed_cb (ESourceRegistry *registry,
+ ESource *source,
+ EPhotoCacheContactLoader *loader)
+{
+ const gchar *extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
+
+ if (e_source_has_extension (source, extension_name))
+ photo_cache_contact_loader_remove_source (loader, source);
+}
+
+static void
+photo_cache_contact_loader_dispose (GObject *object)
+{
+ EPhotoCacheContactLoaderPrivate *priv;
+
+ priv = E_PHOTO_CACHE_CONTACT_LOADER_GET_PRIVATE (object);
+
+ if (priv->source_added_handler_id > 0) {
+ g_signal_handler_disconnect (
+ priv->registry,
+ priv->source_added_handler_id);
+ priv->source_added_handler_id = 0;
+ }
+
+ if (priv->source_removed_handler_id > 0) {
+ g_signal_handler_disconnect (
+ priv->registry,
+ priv->source_removed_handler_id);
+ priv->source_removed_handler_id = 0;
+ }
+
+ g_clear_object (&priv->registry);
+
+ g_hash_table_remove_all (priv->photo_sources);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_photo_cache_contact_loader_parent_class)->
+ dispose (object);
+}
+
+static void
+photo_cache_contact_loader_finalize (GObject *object)
+{
+ EPhotoCacheContactLoaderPrivate *priv;
+
+ priv = E_PHOTO_CACHE_CONTACT_LOADER_GET_PRIVATE (object);
+
+ g_hash_table_destroy (priv->photo_sources);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_photo_cache_contact_loader_parent_class)->
+ finalize (object);
+}
+
+static void
+photo_cache_contact_loader_constructed (GObject *object)
+{
+ EPhotoCacheContactLoader *loader;
+ EPhotoCache *photo_cache;
+ EClientCache *client_cache;
+ ESourceRegistry *registry;
+ GList *list, *link;
+ const gchar *extension_name;
+ gulong handler_id;
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_photo_cache_contact_loader_parent_class)->
+ constructed (object);
+
+ loader = E_PHOTO_CACHE_CONTACT_LOADER (object);
+ photo_cache = photo_cache_contact_loader_get_photo_cache (loader);
+
+ 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_sources (registry, extension_name);
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ ESource *source = E_SOURCE (link->data);
+ photo_cache_contact_loader_add_source (loader, source);
+ }
+
+ g_list_free_full (list, (GDestroyNotify) g_object_unref);
+
+ loader->priv->registry = g_object_ref (registry);
+
+ handler_id = g_signal_connect (
+ registry, "source-added",
+ G_CALLBACK (photo_cache_contact_loader_source_added_cb),
+ loader);
+ loader->priv->source_added_handler_id = handler_id;
+
+ handler_id = g_signal_connect (
+ registry, "source-removed",
+ G_CALLBACK (photo_cache_contact_loader_source_removed_cb),
+ loader);
+ loader->priv->source_removed_handler_id = handler_id;
+
+ g_object_unref (client_cache);
+ g_object_unref (registry);
+}
+
+static void
+e_photo_cache_contact_loader_class_init (EPhotoCacheContactLoaderClass *class)
+{
+ GObjectClass *object_class;
+ EExtensionClass *extension_class;
+
+ g_type_class_add_private (
+ class, sizeof (EPhotoCacheContactLoaderPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = photo_cache_contact_loader_dispose;
+ object_class->finalize = photo_cache_contact_loader_finalize;
+ object_class->constructed = photo_cache_contact_loader_constructed;
+
+ extension_class = E_EXTENSION_CLASS (class);
+ extension_class->extensible_type = E_TYPE_PHOTO_CACHE;
+}
+
+static void
+e_photo_cache_contact_loader_class_finalize (EPhotoCacheContactLoaderClass *class)
+{
+}
+
+static void
+e_photo_cache_contact_loader_init (EPhotoCacheContactLoader *loader)
+{
+ GHashTable *photo_sources;
+
+ photo_sources = g_hash_table_new_full (
+ (GHashFunc) e_source_hash,
+ (GEqualFunc) e_source_equal,
+ (GDestroyNotify) g_object_unref,
+ (GDestroyNotify) g_object_unref);
+
+ loader->priv = E_PHOTO_CACHE_CONTACT_LOADER_GET_PRIVATE (loader);
+ loader->priv->photo_sources = photo_sources;
+}
+
+void
+e_photo_cache_contact_loader_type_register (GTypeModule *type_module)
+{
+ /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
+ * function, so we have to wrap it with a public function in
+ * order to register types from a separate compilation unit. */
+ e_photo_cache_contact_loader_register_type (type_module);
+}
+
diff --git a/modules/contact-photos/e-photo-cache-contact-loader.h b/modules/contact-photos/e-photo-cache-contact-loader.h
new file mode 100644
index 0000000000..3289723fcc
--- /dev/null
+++ b/modules/contact-photos/e-photo-cache-contact-loader.h
@@ -0,0 +1,66 @@
+/*
+ * e-photo-cache-contact-loader.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_PHOTO_CACHE_CONTACT_LOADER_H
+#define E_PHOTO_CACHE_CONTACT_LOADER_H
+
+#include <e-util/e-util.h>
+
+/* Standard GObject macros */
+#define E_TYPE_PHOTO_CACHE_CONTACT_LOADER \
+ (e_photo_cache_contact_loader_get_type ())
+#define E_PHOTO_CACHE_CONTACT_LOADER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_PHOTO_CACHE_CONTACT_LOADER, EPhotoCacheContactLoader))
+#define E_PHOTO_CACHE_CONTACT_LOADER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_PHOTO_CACHE_CONTACT_LOADER, EPhotoCacheContactLoaderClass))
+#define E_IS_PHOTO_CACHE_CONTACT_LOADER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_PHOTO_CACHE_CONTACT_LOADER))
+#define E_IS_PHOTO_CACHE_CONTACT_LOADER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_PHOTO_CACHE_CONTACT_LOADER))
+#define E_PHOTO_CACHE_CONTACT_LOADER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_PHOTO_CACHE_CONTACT_LOADER, EPhotoCacheContactLoaderClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EPhotoCacheContactLoader EPhotoCacheContactLoader;
+typedef struct _EPhotoCacheContactLoaderClass EPhotoCacheContactLoaderClass;
+typedef struct _EPhotoCacheContactLoaderPrivate EPhotoCacheContactLoaderPrivate;
+
+struct _EPhotoCacheContactLoader {
+ EExtension parent;
+ EPhotoCacheContactLoaderPrivate *priv;
+};
+
+struct _EPhotoCacheContactLoaderClass {
+ EExtensionClass parent_class;
+};
+
+GType e_photo_cache_contact_loader_get_type
+ (void) G_GNUC_CONST;
+void e_photo_cache_contact_loader_type_register
+ (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_PHOTO_CACHE_CONTACT_LOADER_H */
+
diff --git a/modules/contact-photos/evolution-contact-photos.c b/modules/contact-photos/evolution-contact-photos.c
new file mode 100644
index 0000000000..2ab6f04c40
--- /dev/null
+++ b/modules/contact-photos/evolution-contact-photos.c
@@ -0,0 +1,40 @@
+/*
+ * evolution-module-contact-photos.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include <gmodule.h>
+#include <glib-object.h>
+
+#include "e-contact-photo-source.h"
+#include "e-photo-cache-contact-loader.h"
+
+/* Module Entry Points */
+void e_module_load (GTypeModule *type_module);
+void e_module_unload (GTypeModule *type_module);
+
+G_MODULE_EXPORT void
+e_module_load (GTypeModule *type_module)
+{
+ e_contact_photo_source_type_register (type_module);
+ e_photo_cache_contact_loader_type_register (type_module);
+}
+
+G_MODULE_EXPORT void
+e_module_unload (GTypeModule *type_module)
+{
+}
+