[gnome-online-accounts] Add Micrsoft Exchange using EWS
- From: Debarshi Ray <debarshir src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-online-accounts] Add Micrsoft Exchange using EWS
- Date: Thu, 5 Apr 2012 14:44:37 +0000 (UTC)
commit 4c12de8cfa526d1e07ca77790cd202a6f14fcaf7
Author: Debarshi Ray <debarshir gnome org>
Date: Fri Mar 30 18:06:00 2012 +0200
Add Micrsoft Exchange using EWS
Known deficiencies:
* goa_provider_ensure_credentials is not implemented
* goa_provider_refresh_account is not implemented
* AsUrl and OabUrl values are not exposed over DBus
New dependency:
* libxml-2.0
Fixes: https://bugzilla.gnome.org/667889
configure.ac | 15 +
po/POTFILES.in | 2 +
src/goabackend/Makefile.am | 5 +
src/goabackend/goabackendtypes.h | 8 +-
src/goabackend/goaewsclient.c | 546 +++++++++++++++++++++++++++++
src/goabackend/goaewsclient.h | 71 ++++
src/goabackend/goaexchangeprovider.c | 625 ++++++++++++++++++++++++++++++++++
src/goabackend/goaexchangeprovider.h | 42 +++
src/goabackend/goaprovider.c | 4 +
9 files changed, 1317 insertions(+), 1 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 6d8756a..3783d97 100644
--- a/configure.ac
+++ b/configure.ac
@@ -69,11 +69,25 @@ PKG_CHECK_MODULES(REST, [rest-0.7])
AC_SUBST(REST_CFLAGS)
AC_SUBST(REST_LIBS)
+PKG_CHECK_MODULES(LIBXML, [libxml-2.0])
+AC_SUBST(LIBXML_CFLAGS)
+AC_SUBST(LIBXML_LIBS)
+
GOBJECT_INTROSPECTION_CHECK([0.6.2])
# service providers
#
+# Microsoft Exchange
+AC_ARG_ENABLE([exchange],
+ [AS_HELP_STRING([--enable-exchange],
+ [Enable Microsoft Exchange provider])],
+ [],
+ [enable_exchange=no])
+if test "$enable_exchange" != "no"; then
+ AC_DEFINE(GOA_EXCHANGE_ENABLED, 1, [Enable Microsoft Exchange data provider])
+fi
+
# Google
AC_ARG_ENABLE([google], [AS_HELP_STRING([--enable-google], [Enable Google provider])], [], [enable_google=yes])
AC_ARG_WITH(google-consumer-key,
@@ -240,6 +254,7 @@ echo "
introspection: ${found_introspection}
Google provider: ${enable_google} (OAuth 1.0, key:${with_google_consumer_key} secret:${with_google_consumer_secret})
+ Microsoft Exchange provider:${enable_exchange}
Twitter provider: ${enable_twitter} (OAuth 1.0, key:${with_twitter_consumer_key} secret:${with_twitter_consumer_secret})
Yahoo provider: ${enable_yahoo} (OAuth 1.0, key:${with_yahoo_consumer_key} secret:${with_yahoo_consumer_secret})
Facebook provider: ${enable_facebook} (OAuth 2.0, id:${with_facebook_client_id})
diff --git a/po/POTFILES.in b/po/POTFILES.in
index c25275d..9a54202 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -2,6 +2,8 @@
# List of source files containing translatable strings.
# Please keep this file sorted alphabetically.
src/daemon/goadaemon.c
+src/goabackend/goaewsclient.c
+src/goabackend/goaexchangeprovider.c
src/goabackend/goafacebookprovider.c
src/goabackend/goagoogleprovider.c
src/goabackend/goawindowsliveprovider.c
diff --git a/src/goabackend/Makefile.am b/src/goabackend/Makefile.am
index f175d38..2a5fc57 100644
--- a/src/goabackend/Makefile.am
+++ b/src/goabackend/Makefile.am
@@ -49,6 +49,7 @@ libgoa_backend_1_0_la_HEADERS = \
goalogging.h \
goaeditablelabel.h \
goaprovider.h \
+ goaexchangeprovider.h \
goaoauthprovider.h \
goaoauth2provider.h \
goagoogleprovider.h \
@@ -64,7 +65,9 @@ libgoa_backend_1_0_la_SOURCES = \
goabackendenums.h \
goabackendenumtypes.h goabackendenumtypes.c \
goaeditablelabel.h goaeditablelabel.c \
+ goaewsclient.h goaewsclient.c \
goaprovider.h goaprovider.c \
+ goaexchangeprovider.h goaexchangeprovider.c \
goalogging.h goalogging.c \
goaoauthprovider.h goaoauthprovider.c \
goaoauth2provider.h goaoauth2provider.c \
@@ -89,6 +92,7 @@ libgoa_backend_1_0_la_CFLAGS = \
$(GNOME_KEYRING_CFLAGS) \
$(REST_CFLAGS) \
$(LIBSOUP_CFLAGS) \
+ $(LIBXML_CFLAGS) \
$(NULL)
libgoa_backend_1_0_la_LIBADD = \
@@ -99,6 +103,7 @@ libgoa_backend_1_0_la_LIBADD = \
$(GNOME_KEYRING_LIBS) \
$(REST_LIBS) \
$(LIBSOUP_LIBS) \
+ $(LIBXML_LIBS) \
$(NULL)
# ----------------------------------------------------------------------------------------------------
diff --git a/src/goabackend/goabackendtypes.h b/src/goabackend/goabackendtypes.h
index b69c7c8..db340c9 100644
--- a/src/goabackend/goabackendtypes.h
+++ b/src/goabackend/goabackendtypes.h
@@ -1,6 +1,6 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
- * Copyright (C) 2011 Red Hat, Inc.
+ * Copyright (C) 2011, 2012 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -33,9 +33,15 @@
G_BEGIN_DECLS
+struct _GoaEwsClient;
+typedef struct _GoaEwsClient GoaEwsClient;
+
struct _GoaProvider;
typedef struct _GoaProvider GoaProvider;
+struct _GoaExchangeProvider;
+typedef struct _GoaExchangeProvider GoaExchangeProvider;
+
struct _GoaOAuthProvider;
typedef struct _GoaOAuthProvider GoaOAuthProvider;
diff --git a/src/goabackend/goaewsclient.c b/src/goabackend/goaewsclient.c
new file mode 100644
index 0000000..34a9944
--- /dev/null
+++ b/src/goabackend/goaewsclient.c
@@ -0,0 +1,546 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Debarshi Ray <debarshir gnome org>
+ */
+
+/* Based on code by the Evolution team.
+ *
+ * This was originally written as a part of evolution-ews:
+ * evolution-ews/src/server/e-ews-connection.c
+ */
+
+#include "config.h"
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+
+#include <libsoup/soup.h>
+#include <libxml/xmlIO.h>
+
+#include "goalogging.h"
+#include "goaewsclient.h"
+
+struct _GoaEwsClient
+{
+ GObject parent_instance;
+};
+
+typedef struct _GoaEwsClientClass GoaEwsClientClass;
+
+struct _GoaEwsClientClass
+{
+ GObjectClass parent_class;
+};
+
+G_DEFINE_TYPE (GoaEwsClient, goa_ews_client, G_TYPE_OBJECT);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+goa_ews_client_init (GoaEwsClient *client)
+{
+}
+
+static void
+goa_ews_client_class_init (GoaEwsClientClass *klass)
+{
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+GoaEwsClient *
+goa_ews_client_new (void)
+{
+ return GOA_EWS_CLIENT (g_object_new (GOA_TYPE_EWS_CLIENT, NULL));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GCancellable *cancellable;
+ GSimpleAsyncResult *res;
+ SoupMessage *msgs[2];
+ SoupSession *session;
+ gulong cancellable_id;
+ xmlOutputBuffer *buf;
+} AutodiscoverData;
+
+typedef struct
+{
+ gchar *password;
+ gchar *username;
+} AutodiscoverAuthData;
+
+static void
+ews_client_autodiscover_data_free (AutodiscoverData *data)
+{
+ if (data->cancellable_id > 0)
+ {
+ g_cancellable_disconnect (data->cancellable, data->cancellable_id);
+ g_object_unref (data->cancellable);
+ }
+
+ /* soup_session_queue_message stole the references to data->msgs */
+ xmlOutputBufferClose (data->buf);
+ g_object_unref (data->res);
+ g_object_unref (data->session);
+ g_slice_free (AutodiscoverData, data);
+}
+
+static void
+ews_client_autodiscover_auth_data_free (gpointer data, GClosure *closure)
+{
+ AutodiscoverAuthData *auth = data;
+
+ g_free (auth->password);
+ g_free (auth->username);
+ g_slice_free (AutodiscoverAuthData, auth);
+}
+
+static gboolean
+ews_client_check_node (const xmlNode *node, const gchar *name)
+{
+ g_return_val_if_fail (node != NULL, FALSE);
+ return node->type == XML_ELEMENT_NODE && !g_strcmp0 ((gchar *) node->name, name);
+}
+
+static void
+ews_client_authenticate (SoupSession *session,
+ SoupMessage *msg,
+ SoupAuth *auth,
+ gboolean retrying,
+ gpointer user_data)
+{
+ AutodiscoverAuthData *data = user_data;
+
+ if (retrying)
+ return;
+
+ soup_auth_authenticate (auth, data->username, data->password);
+}
+
+static void
+ews_client_autodiscover_cancelled_cb (GCancellable *cancellable, gpointer user_data)
+{
+ AutodiscoverData *data = user_data;
+ soup_session_abort (data->session);
+}
+
+static GoaEwsUrls *
+ews_client_autodiscover_parse_protocol (xmlNode *node)
+{
+ GoaEwsUrls *urls;
+ gchar *as_url;
+ gchar *oab_url;
+
+ urls = NULL;
+ as_url = NULL;
+ oab_url = NULL;
+
+ for (node = node->children; node; node = node->next)
+ {
+ if (ews_client_check_node (node, "ASUrl"))
+ as_url = (gchar *) xmlNodeGetContent (node);
+ else if (ews_client_check_node (node, "OABUrl"))
+ oab_url = (gchar *) xmlNodeGetContent (node);
+
+ if (as_url != NULL && oab_url !=NULL)
+ {
+ urls = g_slice_new0 (GoaEwsUrls);
+ urls->as_url = as_url;
+ urls->oab_url = oab_url;
+ goto out;
+ }
+ }
+
+ g_free (oab_url);
+ g_free (as_url);
+ out:
+ return urls;
+}
+
+static void
+ews_client_autodiscover_response_cb (SoupSession *session, SoupMessage *msg, gpointer user_data)
+{
+ GError *error;
+ AutodiscoverData *data = user_data;
+ GoaEwsUrls *urls;
+ guint status;
+ gint idx;
+ gsize size;
+ xmlDoc *doc;
+ xmlNode *node;
+
+ status = msg->status_code;
+ if (status == SOUP_STATUS_CANCELLED)
+ return;
+
+ urls = NULL;
+ error = NULL;
+ size = sizeof (data->msgs) / sizeof (data->msgs[0]);
+
+ for (idx = 0; idx < size; idx++)
+ {
+ if (data->msgs[idx] == msg)
+ break;
+ }
+ if (idx == size)
+ return;
+
+ data->msgs[idx] = NULL;
+
+ if (status != SOUP_STATUS_OK)
+ {
+ g_set_error (&error,
+ GOA_ERROR,
+ GOA_ERROR_FAILED, /* TODO: more specific */
+ _("Code: %u - Unexpected response from server"),
+ status);
+ goto out;
+ }
+
+ soup_buffer_free (soup_message_body_flatten (SOUP_MESSAGE (msg)->response_body));
+ g_debug ("The response headers");
+ g_debug ("===================");
+ g_debug ("%s", SOUP_MESSAGE (msg)->response_body->data);
+
+ doc = xmlReadMemory (msg->response_body->data, msg->response_body->length, "autodiscover.xml", NULL, 0);
+ if (doc == NULL)
+ {
+ g_set_error (&error,
+ GOA_ERROR,
+ GOA_ERROR_FAILED, /* TODO: more specific */
+ _("Failed to parse autodiscover response XML"));
+ goto out;
+ }
+
+ node = xmlDocGetRootElement (doc);
+ if (g_strcmp0 ((gchar *) node->name, "Autodiscover"))
+ {
+ g_set_error (&error,
+ GOA_ERROR,
+ GOA_ERROR_FAILED, /* TODO: more specific */
+ _("Failed to find Autodiscover element"));
+ goto out;
+ }
+
+ for (node = node->children; node; node = node->next)
+ {
+ if (ews_client_check_node (node, "Response"))
+ break;
+ }
+ if (node == NULL)
+ {
+ g_set_error (&error,
+ GOA_ERROR,
+ GOA_ERROR_FAILED, /* TODO: more specific */
+ _("Failed to find Response element"));
+ goto out;
+ }
+
+ for (node = node->children; node; node = node->next)
+ {
+ if (ews_client_check_node (node, "Account"))
+ break;
+ }
+ if (node == NULL)
+ {
+ g_set_error (&error,
+ GOA_ERROR,
+ GOA_ERROR_FAILED, /* TODO: more specific */
+ _("Failed to find Account element"));
+ goto out;
+ }
+
+ for (node = node->children; node; node = node->next)
+ {
+ if (ews_client_check_node (node, "Protocol"))
+ {
+ urls = ews_client_autodiscover_parse_protocol (node);
+ break;
+ }
+ }
+ if (urls == NULL)
+ {
+ g_set_error (&error,
+ GOA_ERROR,
+ GOA_ERROR_FAILED, /* TODO: more specific*/
+ _("Failed to find ASUrl and OABUrl in autodiscover response"));
+ goto out;
+ }
+
+ for (idx = 0; idx < size; idx++)
+ {
+ if (data->msgs[idx] != NULL)
+ {
+ /* Since we are cancelling from the same thread that we queued the
+ * message, the callback (ie. this function) will be invoked before
+ * soup_session_cancel_message returns.
+ */
+ soup_session_cancel_message (data->session, data->msgs[idx], SOUP_STATUS_CANCELLED);
+ data->msgs[idx] = NULL;
+ }
+ }
+
+ out:
+ if (error != NULL)
+ {
+ for (idx = 0; idx < size; idx++)
+ {
+ if (data->msgs[idx] != NULL)
+ {
+ /* There's another request outstanding.
+ * Hope that it has better luck.
+ */
+ g_clear_error (&error);
+ return;
+ }
+ }
+ g_simple_async_result_set_from_error (data->res, error);
+ }
+ else
+ g_simple_async_result_set_op_res_gpointer (data->res, urls, NULL);
+
+ g_simple_async_result_complete_in_idle (data->res);
+ ews_client_autodiscover_data_free (data);
+}
+
+static xmlDoc *
+ews_client_create_autodiscover_xml (const gchar *email)
+{
+ xmlDoc *doc;
+ xmlNode *node;
+ xmlNs *ns;
+
+ doc = xmlNewDoc ((xmlChar *) "1.0");
+
+ node = xmlNewDocNode (doc, NULL, (xmlChar *) "Autodiscover", NULL);
+ xmlDocSetRootElement (doc, node);
+ ns = xmlNewNs (node,
+ (xmlChar *) "http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006",
+ NULL);
+
+ node = xmlNewChild (node, ns, (xmlChar *) "Request", NULL);
+ xmlNewChild (node, ns, (xmlChar *) "EMailAddress", (xmlChar *) email);
+ xmlNewChild (node,
+ ns,
+ (xmlChar *) "AcceptableResponseSchema",
+ (xmlChar *) "http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a");
+
+ return doc;
+}
+
+static void
+ews_client_post_restarted_cb (SoupMessage *msg, gpointer data)
+{
+ xmlOutputBuffer *buf = data;
+
+ /* In violation of RFC2616, libsoup will change a POST request to
+ * a GET on receiving a 302 redirect.
+ */
+ g_debug ("Working around libsoup bug with redirect");
+ g_object_set (msg, SOUP_MESSAGE_METHOD, "POST", NULL);
+
+ soup_message_set_request(msg,
+ "text/xml; charset=utf-8",
+ SOUP_MEMORY_COPY,
+ (gchar *) buf->buffer->content,
+ buf->buffer->use);
+}
+
+static SoupMessage *
+ews_client_create_msg_for_url (const gchar *url, xmlOutputBuffer *buf)
+{
+ SoupMessage *msg;
+
+ msg = soup_message_new (buf != NULL ? "POST" : "GET", url);
+ soup_message_headers_append (msg->request_headers, "User-Agent", "libews/0.1");
+
+ if (buf != NULL)
+ {
+ soup_message_set_request (msg,
+ "text/xml; charset=utf-8",
+ SOUP_MEMORY_COPY,
+ (gchar *) buf->buffer->content,
+ buf->buffer->use);
+ g_signal_connect (msg, "restarted", G_CALLBACK (ews_client_post_restarted_cb), buf);
+ }
+
+ soup_buffer_free (soup_message_body_flatten (SOUP_MESSAGE (msg)->request_body));
+ g_debug ("The request headers");
+ g_debug ("===================");
+ g_debug ("%s", SOUP_MESSAGE (msg)->request_body->data);
+
+ return msg;
+}
+
+void
+goa_ews_client_autodiscover (GoaEwsClient *client,
+ const gchar *email,
+ const gchar *password,
+ const gchar *username,
+ const gchar *server,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ AutodiscoverData *data;
+ AutodiscoverAuthData *auth;
+ gchar *url1;
+ gchar *url2;
+ xmlDoc *doc;
+ xmlOutputBuffer *buf;
+
+ g_return_if_fail (GOA_IS_EWS_CLIENT (client));
+ g_return_if_fail (email != NULL || email[0] != '\0');
+ g_return_if_fail (password != NULL || password[0] != '\0');
+ g_return_if_fail (username != NULL || username[0] != '\0');
+ g_return_if_fail (server != NULL || server[0] != '\0');
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+ doc = ews_client_create_autodiscover_xml (email);
+ buf = xmlAllocOutputBuffer (NULL);
+ xmlNodeDumpOutput (buf, doc, xmlDocGetRootElement (doc), 0, 1, NULL);
+ xmlOutputBufferFlush (buf);
+
+ url1 = g_strdup_printf ("https://%s/autodiscover/autodiscover.xml", server);
+ url2 = g_strdup_printf ("https://autodiscover.%s/autodiscover/autodiscover.xml", server);
+
+ /* http://msdn.microsoft.com/en-us/library/ee332364.aspx says we are
+ * supposed to try $domain and then autodiscover.$domain. But some
+ * people have broken firewalls on the former which drop packets
+ * instead of rejecting connections, and make the request take ages
+ * to time out. So run both queries in parallel and let the fastest
+ * (successful) one win.
+ */
+ data = g_slice_new0 (AutodiscoverData);
+ data->buf = buf;
+ data->res = g_simple_async_result_new (G_OBJECT (client), callback, user_data, goa_ews_client_autodiscover);
+ data->msgs[0] = ews_client_create_msg_for_url (url1, buf);
+ data->msgs[1] = ews_client_create_msg_for_url (url2, buf);
+ data->session = soup_session_async_new_with_options (SOUP_SESSION_USE_NTLM, TRUE,
+ SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
+ NULL);
+ if (cancellable != NULL)
+ {
+ data->cancellable = g_object_ref (cancellable);
+ data->cancellable_id = g_cancellable_connect (data->cancellable,
+ G_CALLBACK (ews_client_autodiscover_cancelled_cb),
+ data,
+ NULL);
+ }
+
+ auth = g_slice_new0 (AutodiscoverAuthData);
+ auth->username = g_strdup (username);
+ auth->password = g_strdup (password);
+ g_signal_connect_data (data->session,
+ "authenticate",
+ G_CALLBACK (ews_client_authenticate),
+ auth,
+ ews_client_autodiscover_auth_data_free,
+ 0);
+
+ soup_session_queue_message (data->session, data->msgs[0], ews_client_autodiscover_response_cb, data);
+ soup_session_queue_message (data->session, data->msgs[1], ews_client_autodiscover_response_cb, data);
+
+ g_free (url2);
+ g_free (url1);
+ xmlFreeDoc (doc);
+}
+
+GoaEwsUrls *
+goa_ews_client_autodiscover_finish (GoaEwsClient *client, GAsyncResult *res, GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (res, G_OBJECT (client), goa_ews_client_autodiscover), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ simple = G_SIMPLE_ASYNC_RESULT (res);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return NULL;
+
+ return g_simple_async_result_get_op_res_gpointer (simple);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GError **error;
+ GMainLoop *loop;
+ GoaEwsUrls *urls;
+} AutodiscoverSyncData;
+
+static void
+ews_client_autodiscover_sync_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+ AutodiscoverSyncData *data = user_data;
+
+ data->urls = goa_ews_client_autodiscover_finish (GOA_EWS_CLIENT (source_object), res, data->error);
+ g_main_loop_quit (data->loop);
+}
+
+GoaEwsUrls *
+goa_ews_client_autodiscover_sync (GoaEwsClient *client,
+ const gchar *email,
+ const gchar *password,
+ const gchar *username,
+ const gchar *server,
+ GCancellable *cancellable,
+ GError **error)
+{
+ AutodiscoverSyncData data;
+ GMainContext *context = NULL;
+
+ data.error = error;
+ data.urls = NULL;
+
+ context = g_main_context_new ();
+ g_main_context_push_thread_default (context);
+ data.loop = g_main_loop_new (context, FALSE);
+
+ goa_ews_client_autodiscover (client,
+ email,
+ password,
+ username,
+ server,
+ cancellable,
+ ews_client_autodiscover_sync_cb,
+ &data);
+ g_main_loop_run (data.loop);
+ g_main_loop_unref (data.loop);
+
+ g_main_context_pop_thread_default (context);
+ g_main_context_unref (context);
+
+ return data.urls;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+void
+goa_ews_urls_free (GoaEwsUrls *urls)
+{
+ g_free (urls->as_url);
+ g_free (urls->oab_url);
+ g_slice_free (GoaEwsUrls, urls);
+}
diff --git a/src/goabackend/goaewsclient.h b/src/goabackend/goaewsclient.h
new file mode 100644
index 0000000..0390506
--- /dev/null
+++ b/src/goabackend/goaewsclient.h
@@ -0,0 +1,71 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Debarshi Ray <debarshir gnome org>
+ */
+
+#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION)
+#error "Only <goabackend/goabackend.h> can be included directly."
+#endif
+
+#ifndef __GOA_EWS_CLIENT_H__
+#define __GOA_EWS_CLIENT_H__
+
+#include <goabackend/goabackendtypes.h>
+
+G_BEGIN_DECLS
+
+#define GOA_TYPE_EWS_CLIENT (goa_ews_client_get_type ())
+#define GOA_EWS_CLIENT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_EWS_CLIENT, GoaEwsClient))
+#define GOA_IS_EWS_CLIENT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_EWS_CLIENT))
+
+typedef struct _GoaEwsUrls GoaEwsUrls;
+
+struct _GoaEwsUrls
+{
+ gchar *as_url;
+ gchar *oab_url;
+};
+
+GType goa_ews_client_get_type (void) G_GNUC_CONST;
+GoaEwsClient *goa_ews_client_new (void);
+void goa_ews_client_autodiscover (GoaEwsClient *client,
+ const gchar *email,
+ const gchar *password,
+ const gchar *username,
+ const gchar *server,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer gpointer);
+GoaEwsUrls *goa_ews_client_autodiscover_finish (GoaEwsClient *client,
+ GAsyncResult *res,
+ GError **error);
+GoaEwsUrls *goa_ews_client_autodiscover_sync (GoaEwsClient *client,
+ const gchar *email,
+ const gchar *password,
+ const gchar *username,
+ const gchar *server,
+ GCancellable *cancellable,
+ GError **error);
+
+void goa_ews_urls_free (GoaEwsUrls *urls);
+
+G_END_DECLS
+
+#endif /* __GOA_EWS_CLIENT_H__ */
diff --git a/src/goabackend/goaexchangeprovider.c b/src/goabackend/goaexchangeprovider.c
new file mode 100644
index 0000000..b046104
--- /dev/null
+++ b/src/goabackend/goaexchangeprovider.c
@@ -0,0 +1,625 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Debarshi Ray <debarshir gnome org>
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#include "goaewsclient.h"
+#include "goalogging.h"
+#include "goaprovider.h"
+#include "goaexchangeprovider.h"
+#include "goaeditablelabel.h"
+
+/**
+ * GoaExchangeProvider:
+ *
+ * The #GoaExchangeProvider structure contains only private data and should
+ * only be accessed using the provided API.
+ */
+struct _GoaExchangeProvider
+{
+ /*< private >*/
+ GoaProvider parent_instance;
+};
+
+typedef struct _GoaExchangeProviderClass GoaExchangeProviderClass;
+
+struct _GoaExchangeProviderClass
+{
+ GoaProviderClass parent_class;
+};
+
+/**
+ * SECTION:goaexchangeprovider
+ * @title: GoaExchangeProvider
+ * @short_description: A provider for Microsoft Exchange servers
+ *
+ * #GoaExchangeProvider is used to access Microsoft Exchange servers.
+ */
+
+G_DEFINE_TYPE_WITH_CODE (GoaExchangeProvider, goa_exchange_provider, GOA_TYPE_PROVIDER,
+ g_io_extension_point_implement (GOA_PROVIDER_EXTENSION_POINT_NAME,
+ g_define_type_id,
+ "exchange",
+ 0));
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static const gchar *
+get_provider_type (GoaProvider *provider)
+{
+ return "exchange";
+}
+
+static gchar *
+get_provider_name (GoaProvider *provider, GoaObject *object)
+{
+ return g_strdup(_("Microsoft Exchange"));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean on_handle_get_password (GoaPasswordBased *interface,
+ GDBusMethodInvocation *invocation,
+ const gchar *id,
+ gpointer user_data);
+
+static gboolean
+build_object (GoaProvider *provider,
+ GoaObjectSkeleton *object,
+ GKeyFile *key_file,
+ const gchar *group,
+ GError **error)
+{
+ GoaAccount *account;
+ GoaCalendar *calendar;
+ GoaContacts *contacts;
+ GoaMail *mail;
+ GoaPasswordBased *password_based;
+ gboolean calendar_enabled;
+ gboolean contacts_enabled;
+ gboolean mail_enabled;
+ gboolean ret;
+
+ account = NULL;
+ calendar = NULL;
+ contacts = NULL;
+ mail = NULL;
+ password_based = NULL;
+ ret = FALSE;
+
+ /* Chain up */
+ if (!GOA_PROVIDER_CLASS (goa_exchange_provider_parent_class)->build_object (provider,
+ object,
+ key_file,
+ group,
+ error))
+ goto out;
+
+ password_based = goa_object_get_password_based (GOA_OBJECT (object));
+ if (password_based == NULL)
+ {
+ password_based = goa_password_based_skeleton_new ();
+ /* Ensure D-Bus method invocations run in their own thread */
+ g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (password_based),
+ G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD);
+ goa_object_skeleton_set_password_based (object, password_based);
+ g_signal_connect (password_based,
+ "handle-get-password",
+ G_CALLBACK (on_handle_get_password),
+ NULL);
+ }
+
+ account = goa_object_get_account (GOA_OBJECT (object));
+
+ /* Email */
+ mail = goa_object_get_mail (GOA_OBJECT (object));
+ mail_enabled = g_key_file_get_boolean (key_file, group, "MailEnabled", NULL);
+ if (mail_enabled)
+ {
+ if (mail == NULL)
+ {
+ const gchar *email_address;
+
+ email_address = goa_account_get_presentation_identity (account);
+ mail = goa_mail_skeleton_new ();
+ g_object_set (G_OBJECT (mail), "email-address", email_address, NULL);
+ goa_object_skeleton_set_mail (object, mail);
+ }
+ }
+ else
+ {
+ if (mail != NULL)
+ goa_object_skeleton_set_mail (object, NULL);
+ }
+
+ /* Calendar */
+ calendar = goa_object_get_calendar (GOA_OBJECT (object));
+ calendar_enabled = g_key_file_get_boolean (key_file, group, "CalendarEnabled", NULL);
+ if (calendar_enabled)
+ {
+ if (calendar == NULL)
+ {
+ calendar = goa_calendar_skeleton_new ();
+ goa_object_skeleton_set_calendar (object, calendar);
+ }
+ }
+ else
+ {
+ if (calendar != NULL)
+ goa_object_skeleton_set_calendar (object, NULL);
+ }
+
+ /* Contacts */
+ contacts = goa_object_get_contacts (GOA_OBJECT (object));
+ contacts_enabled = g_key_file_get_boolean (key_file, group, "ContactsEnabled", NULL);
+ if (contacts_enabled)
+ {
+ if (contacts == NULL)
+ {
+ contacts = goa_contacts_skeleton_new ();
+ goa_object_skeleton_set_contacts (object, contacts);
+ }
+ }
+ else
+ {
+ if (contacts != NULL)
+ goa_object_skeleton_set_contacts (object, NULL);
+ }
+
+ ret = TRUE;
+
+ out:
+ if (contacts != NULL)
+ g_object_unref (contacts);
+ if (calendar != NULL)
+ g_object_unref (calendar);
+ if (mail != NULL)
+ g_object_unref (mail);
+ if (password_based != NULL)
+ g_object_unref (password_based);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+ensure_credentials_sync (GoaProvider *provider,
+ GoaObject *object,
+ gint *out_expires_in,
+ GCancellable *cancellable,
+ GError **error)
+{
+ if (out_expires_in != NULL)
+ *out_expires_in = 0;
+ return TRUE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+add_entry (GtkWidget *table,
+ guint row,
+ const gchar *text,
+ GtkWidget **out_entry)
+{
+ GtkWidget *label;
+ GtkWidget *entry;
+
+ label = gtk_label_new_with_mnemonic (text);
+ gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
+ gtk_table_attach (GTK_TABLE (table), label,
+ 0, 1,
+ row, row + 1,
+ GTK_FILL, GTK_FILL, 0, 0);
+ entry = gtk_entry_new ();
+ gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
+ gtk_entry_set_max_length (GTK_ENTRY (entry), 132);
+ gtk_table_attach (GTK_TABLE (table), entry,
+ 1, 2,
+ row, row + 1,
+ GTK_FILL, GTK_FILL, 0, 0);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
+ if (out_entry != NULL)
+ *out_entry = entry;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GtkDialog *dialog;
+ GMainLoop *loop;
+
+ GtkWidget *cluebar;
+ GtkWidget *cluebar_label;
+
+ GtkWidget *email_address;
+ GtkWidget *password;
+
+ GtkWidget *username;
+ GtkWidget *server;
+
+ gchar *account_object_path;
+
+ GError *error;
+} AddAccountData;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_valid_email_address (const gchar *email, gchar **out_username, gchar **out_domain)
+{
+ gchar *at;
+
+ if (email == NULL || email[0] == '\0')
+ return FALSE;
+
+ at = strchr (email, '@');
+ if (at == NULL || *(at + 1) == '\0')
+ return FALSE;
+
+ if (out_username != NULL)
+ {
+ *out_username = g_strdup (email);
+ (*out_username)[at - email] = '\0';
+ }
+
+ if (out_domain != NULL)
+ *out_domain = g_strdup (at + 1);
+
+ return TRUE;
+}
+
+static void
+on_email_address_or_password_changed (GtkEditable *editable, gpointer user_data)
+{
+ AddAccountData *data = user_data;
+ gboolean can_add;
+ const gchar *email;
+ gchar *domain;
+ gchar *url;
+ gchar *username;
+
+ can_add = FALSE;
+ domain = NULL;
+ url = NULL;
+ username = NULL;
+
+ email = gtk_entry_get_text (GTK_ENTRY (data->email_address));
+ if (!is_valid_email_address (email, &username, &domain))
+ goto out;
+
+ gtk_entry_set_text (GTK_ENTRY (data->username), username);
+ gtk_entry_set_text (GTK_ENTRY (data->server), domain);
+
+ can_add = gtk_entry_get_text_length (GTK_ENTRY (data->password)) != 0;
+
+ out:
+ gtk_dialog_set_response_sensitive (data->dialog, GTK_RESPONSE_OK, can_add);
+ g_free (url);
+ g_free (domain);
+ g_free (username);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+add_account_cb (GoaManager *manager, GAsyncResult *res, gpointer user_data)
+{
+ AddAccountData *data = user_data;
+ goa_manager_call_add_account_finish (manager,
+ &data->account_object_path,
+ res,
+ &data->error);
+ g_main_loop_quit (data->loop);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GoaObject *
+add_account (GoaProvider *provider,
+ GoaClient *client,
+ GtkDialog *dialog,
+ GtkBox *vbox,
+ GError **error)
+{
+ AddAccountData data;
+ GError *local_error;
+ GVariantBuilder builder;
+ GoaEwsClient *ews_client;
+ GoaEwsUrls *urls;
+ GoaObject *ret;
+ GtkWidget *alignment;
+ GtkWidget *label;
+ GtkWidget *table;
+
+ const gchar *email_address;
+ const gchar *server;
+ const gchar *password;
+ const gchar *username;
+ gchar *markup;
+ gint response;
+ guint row;
+
+ ews_client = NULL;
+ ret = NULL;
+
+ memset (&data, 0, sizeof (AddAccountData));
+ data.loop = g_main_loop_new (NULL, FALSE);
+ data.dialog = dialog;
+ data.error = NULL;
+
+ data.cluebar = gtk_info_bar_new ();
+ gtk_info_bar_set_message_type (GTK_INFO_BAR (data.cluebar), GTK_MESSAGE_ERROR);
+ gtk_widget_set_no_show_all (data.cluebar, TRUE);
+ gtk_box_pack_start (GTK_BOX (vbox), data.cluebar, FALSE, FALSE, 0);
+
+ data.cluebar_label = gtk_label_new ("");
+ gtk_label_set_line_wrap (GTK_LABEL (data.cluebar_label), TRUE);
+ gtk_container_add (GTK_CONTAINER (gtk_info_bar_get_content_area (GTK_INFO_BAR (data.cluebar))),
+ data.cluebar_label);
+
+ label = gtk_label_new (NULL);
+ markup = g_strconcat ("<b>", _("New Microsoft Exchange Account"), "</b>", NULL);
+ gtk_label_set_markup (GTK_LABEL (label), markup);
+ g_free (markup);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_box_pack_start (vbox, label, FALSE, FALSE, 0);
+
+ table = gtk_table_new (4, 2, FALSE);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 12);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 12);
+
+ alignment = gtk_alignment_new (0.5, 0.0, 0.0, 0.0);
+ gtk_container_add (GTK_CONTAINER (alignment), table);
+ gtk_box_pack_start (vbox, alignment, FALSE, TRUE, 0);
+
+ row = 0;
+ add_entry (table, row++, _("Email _Address"), &data.email_address);
+ add_entry (table, row++, _("_Password"), &data.password);
+ add_entry (table, row++, _("User_name"), &data.username);
+ add_entry (table, row++, _("_Server"), &data.server);
+
+ gtk_entry_set_visibility (GTK_ENTRY (data.password), FALSE);
+ gtk_widget_grab_focus (data.email_address);
+
+ g_signal_connect (data.email_address, "changed", G_CALLBACK (on_email_address_or_password_changed), &data);
+ g_signal_connect (data.password, "changed", G_CALLBACK (on_email_address_or_password_changed), &data);
+
+ gtk_widget_show_all (GTK_WIDGET (vbox));
+ gtk_dialog_add_button (dialog, GTK_STOCK_OK, GTK_RESPONSE_OK);
+ gtk_dialog_set_default_response (dialog, GTK_RESPONSE_OK);
+ gtk_dialog_set_response_sensitive (dialog, GTK_RESPONSE_OK, FALSE);
+
+ ews_client = goa_ews_client_new ();
+
+ ews_again:
+ response = gtk_dialog_run (dialog);
+ if (response != GTK_RESPONSE_OK)
+ {
+ g_set_error (error,
+ GOA_ERROR,
+ GOA_ERROR_DIALOG_DISMISSED,
+ _("Dialog was dismissed"));
+ goto out;
+ }
+
+ email_address = gtk_entry_get_text (GTK_ENTRY (data.email_address));
+ password = gtk_entry_get_text (GTK_ENTRY (data.password));
+ username = gtk_entry_get_text (GTK_ENTRY (data.username));
+ server = gtk_entry_get_text (GTK_ENTRY (data.server));
+
+ local_error = NULL;
+ urls = goa_ews_client_autodiscover_sync (ews_client,
+ email_address,
+ password,
+ username,
+ server,
+ NULL,
+ &local_error);
+ if (urls == NULL)
+ {
+ markup = g_strdup_printf ("<b>%s:</b> %s",
+ _("Error connecting to Microsoft Exchange server"),
+ local_error->message);
+ g_error_free (local_error);
+
+ gtk_label_set_markup (GTK_LABEL (data.cluebar_label), markup);
+ g_free (markup);
+
+ gtk_widget_set_no_show_all (data.cluebar, FALSE);
+ gtk_widget_show_all (data.cluebar);
+ goto ews_again;
+ }
+
+ gtk_widget_hide (GTK_WIDGET (dialog));
+
+ /* OK, everything is dandy, add the account */
+ /* we want the GoaClient to update before this method returns (so it
+ * can create a proxy for the new object) so run the mainloop while
+ * waiting for this to complete
+ */
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
+ g_variant_builder_add (&builder, "{ss}", "MailEnabled", "true");
+ g_variant_builder_add (&builder, "{ss}", "CalendarEnabled", "true");
+ g_variant_builder_add (&builder, "{ss}", "ContactsEnabled", "true");
+ g_variant_builder_add (&builder, "{ss}", "Server", server);
+ g_variant_builder_add (&builder, "{ss}", "AsUrl", urls->as_url);
+ g_variant_builder_add (&builder, "{ss}", "OabUrl", urls->oab_url);
+ goa_ews_urls_free (urls);
+
+ goa_manager_call_add_account (goa_client_get_manager (client),
+ goa_provider_get_provider_type (provider),
+ username,
+ email_address,
+ g_variant_builder_end (&builder),
+ NULL, /* GCancellable* */
+ (GAsyncReadyCallback) add_account_cb,
+ &data);
+ g_main_loop_run (data.loop);
+ if (data.error != NULL)
+ {
+ g_propagate_error (error, data.error);
+ goto out;
+ }
+
+ ret = GOA_OBJECT (g_dbus_object_manager_get_object (goa_client_get_object_manager (client),
+ data.account_object_path));
+ /* TODO: run in worker thread */
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+ g_variant_builder_add (&builder, "{sv}", "password", g_variant_new_string (password));
+
+ if (!goa_provider_store_credentials_sync (provider,
+ ret,
+ g_variant_builder_end (&builder),
+ NULL, /* GCancellable */
+ error))
+ goto out;
+
+ out:
+ g_free (data.account_object_path);
+ if (data.loop != NULL)
+ g_main_loop_unref (data.loop);
+ if (ews_client != NULL)
+ g_object_unref (ews_client);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+refresh_account (GoaProvider *provider,
+ GoaClient *client,
+ GoaObject *object,
+ GtkWindow *parent,
+ GError **error)
+{
+ return TRUE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+show_account (GoaProvider *provider,
+ GoaClient *client,
+ GoaObject *object,
+ GtkBox *vbox,
+ GtkTable *table)
+{
+ /* Chain up */
+ GOA_PROVIDER_CLASS (goa_exchange_provider_parent_class)->show_account (provider, client, object, vbox, table);
+
+ goa_util_add_account_info (table, object);
+
+ goa_util_add_row_switch_from_keyfile_with_blurb (GTK_TABLE (table), object,
+ _("Use for"),
+ "MailEnabled",
+ _("Mail"));
+
+ goa_util_add_row_switch_from_keyfile_with_blurb (GTK_TABLE (table), object,
+ NULL,
+ "CalendarEnabled",
+ _("Calendar"));
+
+ goa_util_add_row_switch_from_keyfile_with_blurb (GTK_TABLE (table), object,
+ NULL,
+ "ContactsEnabled",
+ _("Contacts"));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+goa_exchange_provider_init (GoaExchangeProvider *provider)
+{
+}
+
+static void
+goa_exchange_provider_class_init (GoaExchangeProviderClass *klass)
+{
+ GoaProviderClass *provider_class;
+
+ provider_class = GOA_PROVIDER_CLASS (klass);
+ provider_class->get_provider_type = get_provider_type;
+ provider_class->get_provider_name = get_provider_name;
+ provider_class->add_account = add_account;
+ provider_class->refresh_account = refresh_account;
+ provider_class->build_object = build_object;
+ provider_class->show_account = show_account;
+ provider_class->ensure_credentials_sync = ensure_credentials_sync;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+on_handle_get_password (GoaPasswordBased *interface,
+ GDBusMethodInvocation *invocation,
+ const gchar *id,
+ gpointer user_data)
+{
+ GoaObject *object;
+ GoaAccount *account;
+ GoaProvider *provider;
+ GError *error;
+ GVariant *credentials;
+ gchar *password;
+
+ /* TODO: maybe log what app is requesting access */
+
+ password = NULL;
+ credentials = NULL;
+
+ object = GOA_OBJECT (g_dbus_interface_get_object (G_DBUS_INTERFACE (interface)));
+ account = goa_object_peek_account (object);
+ provider = goa_provider_get_for_provider_type (goa_account_get_provider_type (account));
+
+ error = NULL;
+ credentials = goa_provider_lookup_credentials_sync (provider,
+ object,
+ NULL, /* GCancellable* */
+ &error);
+ if (credentials == NULL)
+ {
+ g_dbus_method_invocation_take_error (invocation, error);
+ goto out;
+ }
+
+ if (!g_variant_lookup (credentials, "password", "s", &password))
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ GOA_ERROR,
+ GOA_ERROR_FAILED, /* TODO: more specific */
+ _("Did not find password with username `%s' in credentials"),
+ id);
+ goto out;
+ }
+
+ goa_password_based_complete_get_password (interface, invocation, password);
+
+ out:
+ g_free (password);
+ if (credentials != NULL)
+ g_variant_unref (credentials);
+ g_object_unref (provider);
+ return TRUE; /* invocation was handled */
+}
diff --git a/src/goabackend/goaexchangeprovider.h b/src/goabackend/goaexchangeprovider.h
new file mode 100644
index 0000000..76c6911
--- /dev/null
+++ b/src/goabackend/goaexchangeprovider.h
@@ -0,0 +1,42 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Debarshi Ray <debarshir gnome org>
+ */
+
+#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION)
+#error "Only <goabackend/goabackend.h> can be included directly."
+#endif
+
+#ifndef __GOA_EXCHANGE_PROVIDER_H__
+#define __GOA_EXCHANGE_PROVIDER_H__
+
+#include <goabackend/goabackendtypes.h>
+
+G_BEGIN_DECLS
+
+#define GOA_TYPE_EXCHANGE_PROVIDER (goa_exchange_provider_get_type ())
+#define GOA_EXCHANGE_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_EXCHANGE_PROVIDER, GoaExchangeProvider))
+#define GOA_IS_EXCHANGE_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_EXCHANGE_PROVIDER))
+
+GType goa_exchange_provider_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __GOA_EXCHANGE_PROVIDER_H__ */
diff --git a/src/goabackend/goaprovider.c b/src/goabackend/goaprovider.c
index 0bd6241..8cba458 100644
--- a/src/goabackend/goaprovider.c
+++ b/src/goabackend/goaprovider.c
@@ -28,6 +28,7 @@
#include "goalogging.h"
#include "goaprovider.h"
+#include "goaexchangeprovider.h"
#include "goagoogleprovider.h"
#include "goafacebookprovider.h"
#include "goayahooprovider.h"
@@ -591,6 +592,9 @@ ensure_ep_and_builtins (void)
extension_point = g_io_extension_point_register (GOA_PROVIDER_EXTENSION_POINT_NAME);
g_io_extension_point_set_required_type (extension_point, GOA_TYPE_PROVIDER);
+#ifdef GOA_EXCHANGE_ENABLED
+ type = GOA_TYPE_EXCHANGE_PROVIDER;
+#endif
#ifdef GOA_GOOGLE_ENABLED
type = GOA_TYPE_GOOGLE_PROVIDER;
#endif
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]