summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Zeuthen <davidz@redhat.com>2011-05-10 16:30:44 -0400
committerDavid Zeuthen <davidz@redhat.com>2011-05-10 16:30:44 -0400
commitab5f088b98cf63f03ddf7df710f0e9c96214cb70 (patch)
tree74db105509273a1db1ae80e471167f8b0cc6ebbe
parent631d30c53597f3536aadbd572e71c5b7cadc053c (diff)
Add an simple IMAP client and assorted D-Bus interfaces to expose it
I've also written code for gnome-shell to use it, see http://people.freedesktop.org/~david/mail-notif-1.png http://people.freedesktop.org/~david/mail-notif-2.png http://people.freedesktop.org/~david/mail-notif-3.png Signed-off-by: David Zeuthen <davidz@redhat.com>
-rw-r--r--configure.ac4
-rw-r--r--data/dbus-interfaces.xml138
-rw-r--r--doc/goa-docs.xml9
-rw-r--r--doc/goa-sections.txt194
-rw-r--r--doc/goa.types11
-rw-r--r--src/Makefile.am2
-rw-r--r--src/examples/Makefile.am34
-rw-r--r--src/examples/mail-query.c273
-rw-r--r--src/goa/Makefile.am63
-rw-r--r--src/goa/goabackend.h6
-rw-r--r--src/goa/goabackendenums.h59
-rw-r--r--src/goa/goabackendenumtypes.c.template40
-rw-r--r--src/goa/goabackendenumtypes.h.template24
-rw-r--r--src/goa/goabackendgoogleprovider.c24
-rw-r--r--src/goa/goabackendimapauth.c84
-rw-r--r--src/goa/goabackendimapauth.h88
-rw-r--r--src/goa/goabackendimapauthoauth.c572
-rw-r--r--src/goa/goabackendimapauthoauth.h46
-rw-r--r--src/goa/goabackendimapclient.c2023
-rw-r--r--src/goa/goabackendimapclient.h80
-rw-r--r--src/goa/goabackendimapmail.c551
-rw-r--r--src/goa/goabackendimapmail.h45
-rw-r--r--src/goa/goabackendimapmessage.c263
-rw-r--r--src/goa/goabackendimapmessage.h49
-rw-r--r--src/goa/goabackendimapprivate.h63
-rw-r--r--src/goa/goabackendoauthprovider.c87
-rw-r--r--src/goa/goabackendoauthprovider.h8
-rw-r--r--src/goa/goabackendprovider.c73
-rw-r--r--src/goa/goabackendprovider.h8
-rw-r--r--src/goa/goabackendtypes.h16
30 files changed, 4935 insertions, 2 deletions
diff --git a/configure.ac b/configure.ac
index 0b7c15b..e79ae2c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -63,6 +63,8 @@ PKG_CHECK_MODULES(REST, [rest-0.7])
AC_SUBST(REST_CFLAGS)
AC_SUBST(REST_LIBS)
+GOBJECT_INTROSPECTION_CHECK([0.6.2])
+
# Internationalization
#
@@ -82,6 +84,7 @@ src/Makefile
src/goa/Makefile
src/daemon/Makefile
src/panel/Makefile
+src/examples/Makefile
po/Makefile.in
doc/Makefile
doc/version.xml
@@ -104,6 +107,7 @@ echo "
compiler: ${CC}
cflags: ${CFLAGS}
cppflags: ${CPPFLAGS}
+ introspection: ${found_introspection}
Maintainer mode: ${USE_MAINTAINER_MODE}
Building api docs: ${enable_gtk_doc}
diff --git a/data/dbus-interfaces.xml b/data/dbus-interfaces.xml
index 0e6a012..ed483cf 100644
--- a/data/dbus-interfaces.xml
+++ b/data/dbus-interfaces.xml
@@ -275,4 +275,142 @@
</interface>
+
+ <!--
+ org.gnome.OnlineAccounts.Mail:
+
+ An account object implements this interface if it provides
+ email-like messaging capabilities.
+
+ TODO: have a way to open an user agent for a message guid
+ obtained from a MailQuery. Also have a way to download the full
+ message (multipart message? ugh).
+ -->
+ <interface name="org.gnome.OnlineAccounts.Mail">
+ <!--
+ CreateQuery:
+ @criteria: The criteria of the query.
+ @max_size: The maximum number of messages to query for.
+ @query_object: Object path of the resulting query object.
+
+ Creates a new #org.gnome.OnlineAccounts.Mail.Query object for
+ querying the first @max_size messages matching @criteria. The
+ returned object will stay alive until
+ org.gnome.OnlineAccounts.Mail.Query.Close() is called or the
+ caller vanishes from the message bus.
+
+ See the #org.gnome.OnlineAccounts.Mail.Query:Criteria property
+ on the #org.gnome.OnlineAccounts.Mail.Query interface for the
+ values that can be used for the @criteria parameter.
+ -->
+ <method name="CreateQuery">
+ <arg name="criteria" type="s" direction="in"/>
+ <arg name="max_size" type="i" direction="in"/>
+ <arg name="query_object" type="o" direction="out"/>
+ </method>
+ </interface>
+
+ <!--
+ org.gnome.OnlineAccounts.Mail.Query:
+
+ An interface used for querying for messages. Use the
+ org.gnome.OnlineAccounts.Mail.CreateQuery() method on the
+ #org.gnome.OnlineAccounts.Mail interface to create an object
+ with this interface.
+ -->
+ <interface name="org.gnome.OnlineAccounts.Mail.Query">
+
+ <!-- Criteria:
+ The criteria used for what to include in the
+ #org.gnome.OnlineAccounts.Mail.Query:Result array.
+
+ If this is the empty string, all messages are included. Other
+ valid criteria includes the string <literal>unread</literal> to only
+ include unread messages.
+
+ TODO: add other criteria, e.g. "contains:some string".
+ -->
+ <property name="Criteria" type="s" access="read"/>
+
+ <!-- MaxSize:
+ The maximum number of messages to return in the
+ #org.gnome.OnlineAccounts.Mail.Query:Result property.
+ -->
+ <property name="MaxSize" type="u" access="read"/>
+
+ <!--
+ Result:
+
+ An array of messages in the window being monitored - this
+ array contains up to #org.gnome.OnlineAccounts.Mail.Query:MaxSize
+ messages. Each element in the array is described in <xref linkend="goa-mail-query-struct"/>.
+
+<table frame='all' id='goa-mail-query-struct'>
+ <title>Elements in the #org.gnome.OnlineAccounts.Mail.Query:Result structure</title>
+ <tgroup cols='2' align='left' colsep='1' rowsep='1'>
+ <tbody>
+ <row><entry><literal>s</literal> guid</entry><entry>Unique ID</entry></row>
+ <row><entry><literal>t</literal> date</entry><entry>Date (seconds since the Epoch)</entry></row>
+ <row><entry><literal>s</literal> from</entry><entry>Name of sender</entry></row>
+ <row><entry><literal>s</literal> subject</entry><entry>Subject</entry></row>
+ <row><entry><literal>s</literal> excerpt</entry><entry>Plain-text excerpt</entry></row>
+ <row><entry><literal>i</literal> flags</entry><entry>Flags (bit 0: unread)</entry></row>
+ <row><entry><literal>a{sv}</literal> extras</entry><entry>Other information (currently unused)</entry></row>
+ </tbody>
+ </tgroup>
+</table>
+ -->
+ <property name="Result" type="a(stsssia{sv})" access="read"/>
+
+ <!-- NumUnread:
+ The total number of unread messages (not influenced by the
+ #org.gnome.OnlineAccounts.Mail.Query:Criteria property) or -1
+ if unknown.
+ -->
+ <property name="NumUnread" type="i" access="read"/>
+
+ <!-- NumMessages:
+ The total number of messages (not influenced by the
+ #org.gnome.OnlineAccounts.Mail.Query:Criteria property) or -1
+ if unknown.
+ -->
+ <property name="NumMessages" type="i" access="read"/>
+
+ <!-- Connected:
+ TRUE if currently connected to the server, FALSE if not.
+
+ If FALSE, a client should periodically call the
+ org.gnome.OnlineAccounts.Mail.Query.Refresh() method when
+ network connectivity is available.
+ -->
+ <property name="Connected" type="b" access="read"/>
+
+ <!-- Refresh:
+ Forcibly does a server roundtrip and updates e.g. the
+ #org.gnome.OnlineAccounts.Mail.Query:Result property
+
+ Normally this isn't needed as implementations typically uses
+ techniques such as <ulink
+ url="http://en.wikipedia.org/wiki/IMAP_IDLE">IMAP
+ IDLE</ulink> to keep the current.
+
+ As a side-effect, this also updates the
+ #org.gnome.OnlineAccounts.Mail.Query:Connected property -
+ specifically this property might bet se to FALSE (in case
+ network connectivity was lost) or TRUE (in case network
+ connectivity was acquired).
+
+ This method won't return until the check is done so it is
+ appropriate to e.g. show a spinner while the operation is
+ pending.
+ -->
+ <method name="Refresh"/>
+
+ <!-- Close:
+ Method that can be used to close the query and release all
+ resources used for it.
+ -->
+ <method name="Close"/>
+ </interface>
+
</node>
diff --git a/doc/goa-docs.xml b/doc/goa-docs.xml
index d635870..930d0bb 100644
--- a/doc/goa-docs.xml
+++ b/doc/goa-docs.xml
@@ -106,6 +106,8 @@
<xi:include href="../src/goa/goa-generated-doc-org.gnome.OnlineAccounts.TwitterAccount.xml"/>
<xi:include href="../src/goa/goa-generated-doc-org.gnome.OnlineAccounts.OAuthBased.xml"/>
<xi:include href="../src/goa/goa-generated-doc-org.gnome.OnlineAccounts.OAuth2Based.xml"/>
+ <xi:include href="../src/goa/goa-generated-doc-org.gnome.OnlineAccounts.Mail.xml"/>
+ <xi:include href="../src/goa/goa-generated-doc-org.gnome.OnlineAccounts.Mail.Query.xml"/>
</chapter>
</part>
@@ -123,6 +125,8 @@
<xi:include href="xml/GoaTwitterAccount.xml"/>
<xi:include href="xml/GoaOAuthBased.xml"/>
<xi:include href="xml/GoaOAuth2Based.xml"/>
+ <xi:include href="xml/GoaMail.xml"/>
+ <xi:include href="xml/GoaMailQuery.xml"/>
</part>
<part id="ref-backend-library">
@@ -134,6 +138,11 @@
<xi:include href="xml/goabackendfacebookprovider.xml"/>
<xi:include href="xml/goabackendyahooprovider.xml"/>
<xi:include href="xml/goabackendtwitterprovider.xml"/>
+ <xi:include href="xml/goabackendimapauth.xml"/>
+ <xi:include href="xml/goabackendimapauthoauth.xml"/>
+ <xi:include href="xml/goabackendimapclient.xml"/>
+ <xi:include href="xml/goabackendimapmessage.xml"/>
+ <xi:include href="xml/goabackendimapmail.xml"/>
</part>
<part id="tools-fileformats">
diff --git a/doc/goa-sections.txt b/doc/goa-sections.txt
index a210f07..be2ef71 100644
--- a/doc/goa-sections.txt
+++ b/doc/goa-sections.txt
@@ -55,6 +55,8 @@ goa_object_get_yahoo_account
goa_object_get_twitter_account
goa_object_get_oauth_based
goa_object_get_oauth2_based
+goa_object_get_mail
+goa_object_get_mail_query
goa_object_peek_manager
goa_object_peek_account
goa_object_peek_google_account
@@ -63,6 +65,8 @@ goa_object_peek_yahoo_account
goa_object_peek_twitter_account
goa_object_peek_oauth_based
goa_object_peek_oauth2_based
+goa_object_peek_mail
+goa_object_peek_mail_query
GoaObjectProxy
GoaObjectProxyClass
goa_object_proxy_new
@@ -77,6 +81,8 @@ goa_object_skeleton_set_yahoo_account
goa_object_skeleton_set_twitter_account
goa_object_skeleton_set_oauth_based
goa_object_skeleton_set_oauth2_based
+goa_object_skeleton_set_mail
+goa_object_skeleton_set_mail_query
<SUBSECTION Standard>
goa_object_get_type
goa_object_proxy_get_type
@@ -492,6 +498,7 @@ goa_backend_provider_lookup_credentials
goa_backend_provider_lookup_credentials_finish
goa_backend_provider_ensure_credentials
goa_backend_provider_ensure_credentials_finish
+goa_backend_provider_ensure_credentials_sync
GOA_BACKEND_PROVIDER_EXTENSION_POINT_NAME
goa_backend_provider_get_all
goa_backend_provider_get_for_provider_type
@@ -549,6 +556,7 @@ goa_backend_oauth_provider_get_identity
goa_backend_oauth_provider_get_identity_finish
goa_backend_oauth_provider_get_access_token
goa_backend_oauth_provider_get_access_token_finish
+goa_backend_oauth_provider_get_access_token_sync
goa_backend_oauth_provider_get_use_external_browser
<SUBSECTION Standard>
GOA_BACKEND_OAUTH_PROVIDER
@@ -600,3 +608,189 @@ GOA_IS_BACKEND_TWITTER_PROVIDER
GOA_TYPE_BACKEND_TWITTER_PROVIDER
goa_backend_twitter_provider_get_type
</SECTION>
+
+<SECTION>
+<FILE>GoaMail</FILE>
+GoaMail
+GoaMailIface
+goa_mail_interface_info
+goa_mail_call_create_query
+goa_mail_call_create_query_finish
+goa_mail_call_create_query_sync
+goa_mail_complete_create_query
+GoaMailProxy
+GoaMailProxyClass
+goa_mail_proxy_new
+goa_mail_proxy_new_finish
+goa_mail_proxy_new_sync
+goa_mail_proxy_new_for_bus
+goa_mail_proxy_new_for_bus_finish
+goa_mail_proxy_new_for_bus_sync
+GoaMailSkeleton
+GoaMailSkeletonClass
+goa_mail_skeleton_new
+<SUBSECTION Standard>
+GOA_TYPE_MAIL
+GOA_IS_MAIL
+GOA_MAIL
+GOA_MAIL_GET_IFACE
+GOA_TYPE_MAIL_PROXY
+GOA_IS_MAIL_PROXY
+GOA_IS_MAIL_PROXY_CLASS
+GOA_MAIL_PROXY
+GOA_MAIL_PROXY_CLASS
+GOA_MAIL_PROXY_GET_CLASS
+GOA_TYPE_MAIL_SKELETON
+GOA_IS_MAIL_SKELETON
+GOA_IS_MAIL_SKELETON_CLASS
+GOA_MAIL_SKELETON
+GOA_MAIL_SKELETON_CLASS
+GOA_MAIL_SKELETON_GET_CLASS
+GoaMailProxyPrivate
+GoaMailSkeletonPrivate
+goa_mail_get_type
+goa_mail_proxy_get_type
+goa_mail_skeleton_get_type
+</SECTION>
+
+<SECTION>
+<FILE>GoaMailQuery</FILE>
+GoaMailQuery
+GoaMailQueryIface
+goa_mail_query_interface_info
+goa_mail_query_override_properties
+goa_mail_query_get_connected
+goa_mail_query_get_criteria
+goa_mail_query_get_max_size
+goa_mail_query_get_num_messages
+goa_mail_query_get_num_unread
+goa_mail_query_get_result
+goa_mail_query_set_connected
+goa_mail_query_set_criteria
+goa_mail_query_set_max_size
+goa_mail_query_set_num_messages
+goa_mail_query_set_num_unread
+goa_mail_query_set_result
+goa_mail_query_call_refresh
+goa_mail_query_call_refresh_finish
+goa_mail_query_call_refresh_sync
+goa_mail_query_complete_refresh
+goa_mail_query_call_close
+goa_mail_query_call_close_finish
+goa_mail_query_call_close_sync
+goa_mail_query_complete_close
+GoaMailQueryProxy
+GoaMailQueryProxyClass
+goa_mail_query_proxy_new
+goa_mail_query_proxy_new_finish
+goa_mail_query_proxy_new_sync
+goa_mail_query_proxy_new_for_bus
+goa_mail_query_proxy_new_for_bus_finish
+goa_mail_query_proxy_new_for_bus_sync
+GoaMailQuerySkeleton
+GoaMailQuerySkeletonClass
+goa_mail_query_skeleton_new
+<SUBSECTION Standard>
+GOA_TYPE_MAIL_QUERY
+GOA_IS_MAIL_QUERY
+GOA_MAIL_QUERY
+GOA_MAIL_QUERY_GET_IFACE
+GOA_TYPE_MAIL_QUERY_PROXY
+GOA_IS_MAIL_QUERY_PROXY
+GOA_IS_MAIL_QUERY_PROXY_CLASS
+GOA_MAIL_QUERY_PROXY
+GOA_MAIL_QUERY_PROXY_CLASS
+GOA_MAIL_QUERY_PROXY_GET_CLASS
+GOA_TYPE_MAIL_QUERY_SKELETON
+GOA_IS_MAIL_QUERY_SKELETON
+GOA_IS_MAIL_QUERY_SKELETON_CLASS
+GOA_MAIL_QUERY_SKELETON
+GOA_MAIL_QUERY_SKELETON_CLASS
+GOA_MAIL_QUERY_SKELETON_GET_CLASS
+GoaMailQueryProxyPrivate
+GoaMailQuerySkeletonPrivate
+goa_mail_query_get_type
+goa_mail_query_proxy_get_type
+goa_mail_query_skeleton_get_type
+</SECTION>
+
+<SECTION>
+<FILE>goabackendimapclient</FILE>
+GoaBackendImapClient
+goa_backend_imap_client_new
+goa_backend_imap_client_new_finish
+goa_backend_imap_client_new_sync
+goa_backend_imap_client_get_messages
+goa_backend_imap_client_get_num_messages
+goa_backend_imap_client_get_num_unread
+goa_backend_imap_client_refresh
+goa_backend_imap_client_refresh_finish
+goa_backend_imap_client_refresh_sync
+goa_backend_imap_client_get_closed
+goa_backend_imap_client_close
+<SUBSECTION Standard>
+GOA_TYPE_BACKEND_IMAP_MESSAGE_FLAGS
+goa_backend_imap_message_flags_get_type
+GOA_BACKEND_IMAP_CLIENT
+GOA_IS_BACKEND_IMAP_CLIENT
+GOA_TYPE_BACKEND_IMAP_CLIENT
+goa_backend_imap_client_get_type
+</SECTION>
+
+<SECTION>
+<FILE>goabackendimapmessage</FILE>
+GoaBackendImapMessage
+GoaBackendImapMessageFlags
+goa_backend_imap_message_ref
+goa_backend_imap_message_unref
+goa_backend_imap_message_get_uid
+goa_backend_imap_message_get_internal_date
+goa_backend_imap_message_get_flags
+goa_backend_imap_message_get_excerpt
+goa_backend_imap_message_get_headers
+goa_backend_imap_message_lookup_header
+<SUBSECTION Standard>
+GOA_TYPE_BACKEND_IMAP_MESSAGE
+goa_backend_imap_message_get_type
+goa_backend_imap_message_compare_seqnum_reverse
+goa_backend_imap_message_flags_from_strv
+goa_backend_imap_message_new
+</SECTION>
+
+<SECTION>
+<FILE>goabackendimapmail</FILE>
+GoaBackendImapMail
+goa_backend_imap_mail_new
+<SUBSECTION Standard>
+GOA_BACKEND_IMAP_MAIL
+GOA_IS_BACKEND_IMAP_MAIL
+GOA_TYPE_BACKEND_IMAP_MAIL
+goa_backend_imap_mail_get_type
+</SECTION>
+
+<SECTION>
+<FILE>goabackendimapauth</FILE>
+GoaBackendImapAuth
+GoaBackendImapAuthClass
+goa_backend_imap_auth_run_sync
+<SUBSECTION Standard>
+GoaBackendImapAuthPrivate
+GOA_BACKEND_IMAP_AUTH
+GOA_IS_BACKEND_IMAP_AUTH
+GOA_TYPE_BACKEND_IMAP_AUTH
+GOA_BACKEND_IMAP_AUTH_CLASS
+GOA_IS_BACKEND_IMAP_AUTH_CLASS
+GOA_BACKEND_IMAP_AUTH_GET_CLASS
+goa_backend_imap_auth_get_type
+</SECTION>
+
+<SECTION>
+<FILE>goabackendimapauthoauth</FILE>
+GoaBackendImapAuthOAuth
+goa_backend_imap_auth_oauth_new
+<SUBSECTION Standard>
+GOA_BACKEND_IMAP_AUTH_OAUTH
+GOA_IS_BACKEND_IMAP_AUTH_OAUTH
+GOA_TYPE_BACKEND_IMAP_AUTH_OAUTH
+goa_backend_imap_auth_oauth_get_type
+</SECTION>
diff --git a/doc/goa.types b/doc/goa.types
index b9f0b2a..aa069c6 100644
--- a/doc/goa.types
+++ b/doc/goa.types
@@ -30,3 +30,14 @@ goa_object_proxy_get_type
goa_object_skeleton_get_type
goa_oauth_based_get_type
goa_oauth2_based_get_type
+goa_mail_get_type
+goa_mail_proxy_get_type
+goa_mail_skeleton_get_type
+goa_mail_query_get_type
+goa_mail_query_proxy_get_type
+goa_mail_query_skeleton_get_type
+goa_backend_imap_message_get_type
+goa_backend_imap_auth_get_type
+goa_backend_imap_auth_oauth_get_type
+goa_backend_imap_client_get_type
+goa_backend_imap_mail_get_type
diff --git a/src/Makefile.am b/src/Makefile.am
index 03c88ef..6027402 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,4 +1,4 @@
NULL =
-SUBDIRS = goa daemon panel
+SUBDIRS = goa daemon panel examples
diff --git a/src/examples/Makefile.am b/src/examples/Makefile.am
new file mode 100644
index 0000000..a3135b3
--- /dev/null
+++ b/src/examples/Makefile.am
@@ -0,0 +1,34 @@
+
+NULL =
+
+INCLUDES = \
+ -I$(top_builddir)/src -I$(top_srcdir)/src \
+ -DPACKAGE_LIBEXEC_DIR=\""$(libexecdir)"\" \
+ -DPACKAGE_SYSCONF_DIR=\""$(sysconfdir)"\" \
+ -DPACKAGE_DATA_DIR=\""$(datadir)"\" \
+ -DPACKAGE_BIN_DIR=\""$(bindir)"\" \
+ -DPACKAGE_LOCALSTATE_DIR=\""$(localstatedir)"\" \
+ -DPACKAGE_LOCALE_DIR=\""$(localedir)"\" \
+ -DPACKAGE_LIB_DIR=\""$(libdir)"\" \
+ -D_POSIX_PTHREAD_SEMANTICS -D_REENTRANT \
+ -DGOA_API_IS_SUBJECT_TO_CHANGE \
+ $(WARN_CFLAGS) \
+ $(NULL)
+
+noinst_PROGRAMS = mail-query
+
+mail_query_SOURCES = \
+ mail-query.c \
+ $(NULL)
+
+mail_query_CFLAGS = \
+ $(GLIB_CFLAGS) \
+ $(NULL)
+
+mail_query_LDADD = \
+ $(GLIB_LIBS) \
+ $(top_builddir)/src/goa/libgoa.la \
+ $(NULL)
+
+clean-local :
+ rm -f *~
diff --git a/src/examples/mail-query.c b/src/examples/mail-query.c
new file mode 100644
index 0000000..81298c5
--- /dev/null
+++ b/src/examples/mail-query.c
@@ -0,0 +1,273 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 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: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+#include <glib/gi18n.h>
+#include <stdlib.h>
+
+#include "goa/goa.h"
+
+static gchar *
+fix_up_excerpt (const gchar *s,
+ gsize max_len)
+{
+ GString *str;
+ guint n;
+
+ str = g_string_new (NULL);
+ for (n = 0; s[n] != '\0'; n++)
+ {
+ gint c = s[n];
+ if (c == '\r')
+ g_string_append_c (str, ' ');
+ else if (c == '\n')
+ g_string_append_c (str, ' ');
+ else
+ g_string_append_c (str, c);
+ if (str->len > max_len)
+ break;
+ }
+ if (s[n] != '\0')
+ g_string_append (str, "...");
+
+ return g_string_free (str, FALSE);
+}
+
+static void
+print_result (GoaMailQuery *query)
+{
+ GVariantIter iter;
+ GVariant *result;
+ const gchar *guid;
+ gint64 date;
+ const gchar *from;
+ const gchar *subject;
+ const gchar *excerpt;
+ gint flags;
+ guint n;
+
+ result = goa_mail_query_get_result (query);
+ if (result != NULL)
+ {
+ g_print ("Mailbox: NumMessages=%d NumUnread=%d Connected=%d\n"
+ "Query: NumResults=%d MaxSize=%d\n"
+ "===========================================\n",
+ goa_mail_query_get_num_messages (query),
+ goa_mail_query_get_num_unread (query),
+ goa_mail_query_get_connected (query),
+ (gint) g_variant_n_children (result),
+ goa_mail_query_get_max_size (query));
+ if (g_variant_n_children (result) == 0)
+ g_print ("\n");
+
+ n = 0;
+ g_variant_iter_init (&iter, result);
+ while (g_variant_iter_next (&iter,
+ "(st&s&s&si@a{sv})",
+ &guid, &date, &from, &subject, &excerpt, &flags, NULL))
+ {
+ gchar *excerpt_fixed_up;
+ excerpt_fixed_up = fix_up_excerpt (excerpt, 50);
+ g_print ("Message %d: guid %s, flags %d\n"
+ " Date: %" G_GINT64_FORMAT "\n"
+ " From: %s\n"
+ " Subject: %s\n"
+ " Excerpt: %s\n"
+ "\n",
+ n, guid, flags,
+ date,
+ from,
+ subject,
+ excerpt_fixed_up);
+ g_free (excerpt_fixed_up);
+ n++;
+ }
+ }
+ else
+ {
+ g_print ("result is NULL so proxy must be stale\n"
+ "=======================================\n"
+ "\n");
+ }
+}
+
+static void
+on_notify (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GoaMailQuery *query = GOA_MAIL_QUERY (object);
+ print_result (query);
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ gint ret;
+ GMainLoop *loop;
+ GError *error;
+ GoaClient *client;
+ GList *accounts;
+ GList *l;
+ GoaMailQuery *query;
+ gchar *opt_account;
+ gchar *opt_query;
+ gint opt_size;
+ GOptionEntry opt_entries[] =
+ {
+ { "account", 'a', 0, G_OPTION_ARG_STRING, &opt_account, "The account to do a query for", NULL },
+ { "query", 'q', 0, G_OPTION_ARG_STRING, &opt_query, "The query to perform (blank if not set)", NULL },
+ { "size", 's', 0, G_OPTION_ARG_INT, &opt_size, "The size of the query (5 if not set)", NULL },
+ { NULL}
+ };
+ GOptionContext *opt_context;
+ gchar *query_object_path;
+
+ ret = 1;
+ client = NULL;
+ accounts = NULL;
+ query = NULL;
+ opt_account = NULL;
+ opt_query = NULL;
+ opt_size = 5;
+ query_object_path = NULL;
+
+ g_type_init ();
+
+ opt_context = g_option_context_new ("goa mail query example");
+ error = NULL;
+ g_option_context_add_main_entries (opt_context, opt_entries, NULL);
+ if (!g_option_context_parse (opt_context, &argc, &argv, &error))
+ {
+ g_printerr ("Error parsing options: %s\n", error->message);
+ g_error_free (error);
+ goto out;
+ }
+ if (opt_account == NULL)
+ {
+ g_printerr ("Incorrect usage, try --help.\n");
+ goto out;
+ }
+
+ if (opt_query == NULL)
+ opt_query = g_strdup("");
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ error = NULL;
+ client = goa_client_new_sync (NULL, /* GCancellable */
+ &error);
+ if (client == NULL)
+ {
+ g_printerr ("Error creating a GOA client: %s (%s, %d)\n",
+ error->message, g_quark_to_string (error->domain), error->code);
+ g_error_free (error);
+ goto out;
+ }
+
+ accounts = goa_client_get_accounts (client);
+ for (l = accounts; l != NULL; l = l->next)
+ {
+ GoaObject *object = GOA_OBJECT (l->data);
+ GoaAccount *account;
+ GoaMail *mail;
+
+ account = goa_object_peek_account (object);
+ if (account == NULL)
+ continue;
+
+ if (!(g_strcmp0 (goa_account_get_id (account), opt_account) == 0 ||
+ g_strcmp0 (g_dbus_object_get_object_path (G_DBUS_OBJECT (object)), opt_account) == 0))
+ continue;
+
+ mail = goa_object_peek_mail (object);
+ if (mail == NULL)
+ {
+ g_printerr ("Given account does not implement the Mail interface\n");
+ goto out;
+ }
+
+ /* Start querying account */
+ g_print ("Querying mail for %s with query string \"%s\"\n",
+ goa_account_get_id (account),
+ opt_query);
+ error = NULL;
+ if (!goa_mail_call_create_query_sync (mail,
+ opt_query, /* query string */
+ opt_size, /* return no more than N messages */
+ &query_object_path,
+ NULL, /* GCancellable */
+ &error))
+ {
+ g_printerr ("Error creating mail query: %s (%s, %d)\n",
+ error->message, g_quark_to_string (error->domain), error->code);
+ g_error_free (error);
+ goto out;
+ }
+
+ error = NULL;
+ query = goa_mail_query_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ "org.gnome.OnlineAccounts",
+ query_object_path,
+ NULL, /* GCancellable */
+ &error);
+ if (query == NULL)
+ {
+ g_printerr ("Error creating query proxy: %s (%s, %d)\n",
+ error->message, g_quark_to_string (error->domain), error->code);
+ g_error_free (error);
+ goto out;
+ }
+
+ print_result (query);
+ g_signal_connect (query,
+ "notify",
+ G_CALLBACK (on_notify),
+ NULL);
+ break;
+ }
+
+ if (query_object_path == NULL)
+ {
+ g_printerr ("Didn't find requested account.\n");
+ goto out;
+ }
+
+ g_main_loop_run (loop);
+
+ ret = 0;
+
+ out:
+ if (query != NULL)
+ g_object_unref (query);
+ if (client != NULL)
+ g_object_unref (client);
+ g_free (query_object_path);
+ g_list_foreach (accounts, (GFunc) g_object_unref, NULL);
+ g_list_free (accounts);
+ g_free (opt_account);
+ g_free (opt_query);
+ g_option_context_free (opt_context);
+ return ret;
+}
diff --git a/src/goa/Makefile.am b/src/goa/Makefile.am
index 2a62c9c..2d05710 100644
--- a/src/goa/Makefile.am
+++ b/src/goa/Makefile.am
@@ -1,5 +1,6 @@
NULL =
+CLEANFILES =
INCLUDES = \
-I$(top_builddir)/src -I$(top_srcdir)/src \
@@ -41,8 +42,19 @@ goaenumtypes.c: goaenums.h goaenumtypes.c.template
cd $(srcdir) && glib-mkenums --template goaenumtypes.c.template goaenums.h ) > \
goaenumtypes.c.tmp && mv goaenumtypes.c.tmp goaenumtypes.c
+goabackendenumtypes.h: goabackendenums.h goabackendenumtypes.h.template
+ ( top_builddir=`cd $(top_builddir) && pwd`; \
+ cd $(srcdir) && glib-mkenums --template goabackendenumtypes.h.template goabackendenums.h ) > \
+ goabackendenumtypes.h.tmp && mv goabackendenumtypes.h.tmp goabackendenumtypes.h
+
+goabackendenumtypes.c: goabackendenums.h goabackendenumtypes.c.template
+ ( top_builddir=`cd $(top_builddir) && pwd`; \
+ cd $(srcdir) && glib-mkenums --template goabackendenumtypes.c.template goabackendenums.h ) > \
+ goabackendenumtypes.c.tmp && mv goabackendenumtypes.c.tmp goabackendenumtypes.c
+
enum_built_sources = \
goaenumtypes.h goaenumtypes.c \
+ goabackendenumtypes.h goabackendenumtypes.c \
$(NULL)
# ----------------------------------------------------------------------------------------------------
@@ -79,6 +91,38 @@ libgoa_la_LIBADD = \
$(GLIB_LIBS) \
$(NULL)
+if HAVE_INTROSPECTION
+girdir = $(INTROSPECTION_GIRDIR)
+gir_DATA = Goa-1.0.gir
+
+typelibsdir = $(INTROSPECTION_TYPELIBDIR)
+typelibs_DATA = Goa-1.0.typelib
+
+Goa-1.0.gir: libgoa.la $(INTROSPECTION_SCANNER) Makefile.am
+ $(INTROSPECTION_SCANNER) -v \
+ --warn-all \
+ --namespace Goa \
+ --nsversion=1.0 \
+ --include=Gio-2.0 \
+ --library=goa \
+ --output $@ \
+ --pkg=glib-2.0 \
+ --pkg=gobject-2.0 \
+ --pkg=gio-2.0 \
+ --libtool=$(top_builddir)/libtool \
+ --c-include='goa/goa.h' \
+ -I$(top_srcdir)/src \
+ -DGOA_COMPILATION \
+ $(libgoa_la_SOURCES) \
+ $(NULL)
+
+Goa-1.0.typelib: Goa-1.0.gir $(INTROSPECTION_COMPILER)
+ $(INTROSPECTION_COMPILER) $< -o $@
+
+CLEANFILES += $(gir_DATA) $(typelibs_DATA)
+
+endif # HAVE_INTROSPECTION
+
# ----------------------------------------------------------------------------------------------------
lib_LTLIBRARIES += libgoa-backend.la
@@ -89,6 +133,18 @@ libgoa_backend_la_HEADERS = \
goabackend.h \
goabackendtypes.h \
goabackendprovider.h \
+ goabackendoauthprovider.h \
+ goabackendoauth2provider.h \
+ goabackendgoogleprovider.h \
+ goabackendfacebookprovider.h \
+ goabackendyahooprovider.h \
+ goabackendtwitterprovider.h \
+ goabackendimapauth.h \
+ goabackendimapauthoauth.h \
+ goabackendimapclient.h \
+ goabackendimapmessage.h \
+ goabackendenums.h \
+ goabackendenumtypes.h \
$(NULL)
libgoa_backend_la_SOURCES = \
@@ -101,6 +157,13 @@ libgoa_backend_la_SOURCES = \
goabackendfacebookprovider.h goabackendfacebookprovider.c \
goabackendyahooprovider.h goabackendyahooprovider.c \
goabackendtwitterprovider.h goabackendtwitterprovider.c \
+ goabackendimapprivate.h \
+ goabackendimapauth.h goabackendimapauth.c \
+ goabackendimapauthoauth.h goabackendimapauthoauth.c \
+ goabackendimapclient.h goabackendimapclient.c \
+ goabackendimapmessage.h goabackendimapmessage.c \
+ goabackendimapmail.h goabackendimapmail.c \
+ goabackendenumtypes.h goabackendenumtypes.c \
$(NULL)
libgoa_backend_la_CFLAGS = \
diff --git a/src/goa/goabackend.h b/src/goa/goabackend.h
index 9ebad88..f7e1dce 100644
--- a/src/goa/goabackend.h
+++ b/src/goa/goabackend.h
@@ -35,6 +35,12 @@
#include <goa/goabackendgoogleprovider.h>
#include <goa/goabackendfacebookprovider.h>
#include <goa/goabackendyahooprovider.h>
+#include <goa/goabackendtwitterprovider.h>
+#include <goa/goabackendimapauth.h>
+#include <goa/goabackendimapauthoauth.h>
+#include <goa/goabackendimapclient.h>
+#include <goa/goabackendimapmessage.h>
+#include <goa/goabackendimapmail.h>
#undef __GOA_BACKEND_INSIDE_GOA_BACKEND_H__
#endif /* __GOA_BACKEND_H__ */
diff --git a/src/goa/goabackendenums.h b/src/goa/goabackendenums.h
new file mode 100644
index 0000000..2b08547
--- /dev/null
+++ b/src/goa/goabackendenums.h
@@ -0,0 +1,59 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 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: David Zeuthen <davidz@redhat.com>
+ */
+
+#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION)
+#error "Only <goa/goabackend.h> can be included directly."
+#endif
+
+#ifndef __GOA_BACKEND_ENUMS_H__
+#define __GOA_BACKEND_ENUMS_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GoaBackendImapMessageFlags:
+ * @GOA_BACKEND_IMAP_MESSAGE_FLAGS_NONE: No flags set.
+ * @GOA_BACKEND_IMAP_MESSAGE_FLAGS_SEEN: Corresponds to the <literal>\Seen</literal> flag.
+ * @GOA_BACKEND_IMAP_MESSAGE_FLAGS_ANSWERED: Corresponds to the <literal>\Answered</literal> flag.
+ * @GOA_BACKEND_IMAP_MESSAGE_FLAGS_FLAGGED: Corresponds to the <literal>\Flagged</literal> flag.
+ * @GOA_BACKEND_IMAP_MESSAGE_FLAGS_DELETED: Corresponds to the <literal>\Deleted</literal> flag.
+ * @GOA_BACKEND_IMAP_MESSAGE_FLAGS_DRAFT: Corresponds to the <literal>\Draft</literal> flag.
+ * @GOA_BACKEND_IMAP_MESSAGE_FLAGS_RECENT: Corresponds to the <literal>\Recent</literal> flag.
+ *
+ * Flag enumeration corresponding to <ulink url="http://tools.ietf.org/html/rfc3501#section-2.3.2">IMAP flags</ulink>.
+ */
+typedef enum
+{
+ GOA_BACKEND_IMAP_MESSAGE_FLAGS_NONE = 0,
+ GOA_BACKEND_IMAP_MESSAGE_FLAGS_SEEN = (1<<0),
+ GOA_BACKEND_IMAP_MESSAGE_FLAGS_ANSWERED = (1<<1),
+ GOA_BACKEND_IMAP_MESSAGE_FLAGS_FLAGGED = (1<<2),
+ GOA_BACKEND_IMAP_MESSAGE_FLAGS_DELETED = (1<<3),
+ GOA_BACKEND_IMAP_MESSAGE_FLAGS_DRAFT = (1<<4),
+ GOA_BACKEND_IMAP_MESSAGE_FLAGS_RECENT = (1<<5)
+} GoaBackendImapMessageFlags;
+
+G_END_DECLS
+
+#endif /* __GOA_BACKEND_ENUMS_H__ */
diff --git a/src/goa/goabackendenumtypes.c.template b/src/goa/goabackendenumtypes.c.template
new file mode 100644
index 0000000..7a76459
--- /dev/null
+++ b/src/goa/goabackendenumtypes.c.template
@@ -0,0 +1,40 @@
+/*** BEGIN file-header ***/
+#include "goabackendenums.h"
+#include "goabackendenumtypes.h"
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@filename@" */
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType
+@enum_name@_get_type (void)
+{
+ static volatile gsize g_define_type_id__volatile = 0;
+
+ if (g_once_init_enter (&g_define_type_id__volatile))
+ {
+ static const G@Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+ { @VALUENAME@, "@VALUENAME@", "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+ { 0, NULL, NULL }
+ };
+ GType g_define_type_id =
+ g_@type@_register_static (g_intern_static_string ("@EnumName@"), values);
+ g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+ }
+
+ return g_define_type_id__volatile;
+}
+
+/*** END value-tail ***/
+
+/*** BEGIN file-tail ***/
+/*** END file-tail ***/
diff --git a/src/goa/goabackendenumtypes.h.template b/src/goa/goabackendenumtypes.h.template
new file mode 100644
index 0000000..7321076
--- /dev/null
+++ b/src/goa/goabackendenumtypes.h.template
@@ -0,0 +1,24 @@
+/*** BEGIN file-header ***/
+#ifndef __GOA_BACKEND_ENUM_TYPES_H__
+#define __GOA_BACKEND_ENUM_TYPES_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+
+/* enumerations from "@filename@" */
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType @enum_name@_get_type (void) G_GNUC_CONST;
+#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ())
+/*** END value-header ***/
+
+/*** BEGIN file-tail ***/
+G_END_DECLS
+
+#endif /* __GOA_BACKEND_ENUM_TYPES_H__ */
+/*** END file-tail ***/
diff --git a/src/goa/goabackendgoogleprovider.c b/src/goa/goabackendgoogleprovider.c
index 95353f0..4b78749 100644
--- a/src/goa/goabackendgoogleprovider.c
+++ b/src/goa/goabackendgoogleprovider.c
@@ -30,6 +30,9 @@
#include "goabackendoauthprovider.h"
#include "goabackendgoogleprovider.h"
+#include "goabackendimapmail.h"
+#include "goabackendimapauthoauth.h"
+
/**
* GoaBackendGoogleProvider:
*
@@ -328,12 +331,14 @@ goa_backend_google_provider_build_object (GoaBackendProvider *provider,
{
GoaAccount *account;
GoaGoogleAccount *google_account;
+ GoaMail *mail;
gboolean ret;
gchar *email_address;
email_address = NULL;
account = NULL;
google_account = NULL;
+ mail = NULL;
ret = FALSE;
/* Chain up */
@@ -366,10 +371,27 @@ goa_backend_google_provider_build_object (GoaBackendProvider *provider,
goa_google_account_set_email_address (google_account, email_address);
+ mail = goa_object_get_mail (GOA_OBJECT (object));
+ if (mail == NULL)
+ {
+ GoaBackendImapAuth *auth;
+ gchar *request_uri;
+ request_uri = g_strdup_printf ("https://mail.google.com/mail/b/%s/imap/", email_address);
+ auth = goa_backend_imap_auth_oauth_new (GOA_BACKEND_OAUTH_PROVIDER (provider),
+ GOA_OBJECT (object),
+ request_uri);
+ mail = goa_backend_imap_mail_new ("imap.gmail.com", TRUE, auth);
+ goa_object_skeleton_set_mail (object, mail);
+ g_object_unref (auth);
+ g_free (request_uri);
+ }
+
ret = TRUE;
out:
g_free (email_address);
+ if (mail != NULL)
+ g_object_unref (mail);
if (google_account != NULL)
g_object_unref (google_account);
if (account != NULL)
@@ -415,3 +437,5 @@ goa_backend_google_provider_class_init (GoaBackendGoogleProviderClass *klass)
oauth_class->get_callback_uri = get_callback_uri;
oauth_class->get_use_external_browser = get_use_external_browser;
}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/src/goa/goabackendimapauth.c b/src/goa/goabackendimapauth.c
new file mode 100644
index 0000000..2f8f02b
--- /dev/null
+++ b/src/goa/goabackendimapauth.c
@@ -0,0 +1,84 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 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: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+#include <stdlib.h>
+
+#include "goabackendimapauth.h"
+
+/**
+ * SECTION:goabackendimapauth
+ * @title: GoaBackendImapAuth
+ * @short_description: Helper type for authenticating IMAP connections
+ *
+ * #GoaBackendImapAuth is an abstract type used for authenticating
+ * IMAP connections. See #GoaBackendImapAuthOAuth for a concrete
+ * implementation.
+ */
+
+G_DEFINE_ABSTRACT_TYPE (GoaBackendImapAuth, goa_backend_imap_auth, G_TYPE_OBJECT);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+goa_backend_imap_auth_init (GoaBackendImapAuth *client)
+{
+}
+
+static void
+goa_backend_imap_auth_class_init (GoaBackendImapAuthClass *klass)
+{
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * goa_backend_imap_auth_run_sync:
+ * @auth: A #GoaBackendImapAuth.
+ * @input: A valid #GDataInputStream.
+ * @output: A valid #GDataOutputStream.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Authenticates the IMAP connection represented by @input and
+ * @output. This method blocks the calling thread until authentication
+ * is done.
+ *
+ * Returns: %TRUE if authentication succeeded, %FALSE if @error is
+ * set.
+ */
+gboolean
+goa_backend_imap_auth_run_sync (GoaBackendImapAuth *auth,
+ GDataInputStream *input,
+ GDataOutputStream *output,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (GOA_IS_BACKEND_IMAP_AUTH (auth), FALSE);
+ g_return_val_if_fail (G_IS_DATA_INPUT_STREAM (input), FALSE);
+ g_return_val_if_fail (G_IS_DATA_OUTPUT_STREAM (output), FALSE);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
+ return GOA_BACKEND_IMAP_AUTH_GET_CLASS (auth)->run_sync (auth, input, output, cancellable, error);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/src/goa/goabackendimapauth.h b/src/goa/goabackendimapauth.h
new file mode 100644
index 0000000..37ac521
--- /dev/null
+++ b/src/goa/goabackendimapauth.h
@@ -0,0 +1,88 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 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: David Zeuthen <davidz@redhat.com>
+ */
+
+#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION)
+#error "Only <goa/goabackend.h> can be included directly."
+#endif
+
+#ifndef __GOA_BACKEND_IMAP_AUTH_H__
+#define __GOA_BACKEND_IMAP_AUTH_H__
+
+#include <goa/goabackendtypes.h>
+
+G_BEGIN_DECLS
+
+#define GOA_TYPE_BACKEND_IMAP_AUTH (goa_backend_imap_auth_get_type ())
+#define GOA_BACKEND_IMAP_AUTH(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_BACKEND_IMAP_AUTH, GoaBackendImapAuth))
+#define GOA_BACKEND_IMAP_AUTH_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOA_TYPE_BACKEND_IMAP_AUTH, GoaBackendImapAuthClass))
+#define GOA_BACKEND_IMAP_AUTH_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOA_TYPE_BACKEND_IMAP_AUTH, GoaBackendImapAuthClass))
+#define GOA_IS_BACKEND_IMAP_AUTH(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_BACKEND_IMAP_AUTH))
+#define GOA_IS_BACKEND_IMAP_AUTH_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOA_TYPE_BACKEND_IMAP_AUTH))
+
+struct _GoaBackendImapAuthClass;
+struct _GoaBackendImapAuthPrivate;
+typedef struct _GoaBackendImapAuthClass GoaBackendImapAuthClass;
+typedef struct _GoaBackendImapAuthPrivate GoaBackendImapAuthPrivate;
+
+/**
+ * GoaBackendImapAuth:
+ *
+ * The #GoaBackendImapAuth structure contains only private data and
+ * should only be accessed using the provided API.
+ */
+struct _GoaBackendImapAuth
+{
+ /*< private >*/
+ GObject parent_instance;
+ GoaBackendImapAuthPrivate *priv;
+};
+
+/**
+ * GoaBackendImapAuthClass:
+ * @parent_class: The parent class
+ * @run_sync: Virtual function for the goa_backend_imap_auth_run_sync() method.
+ *
+ * Class structure for #GoaBackendImapAuth.
+ */
+struct _GoaBackendImapAuthClass
+{
+ GObjectClass parent_class;
+ gboolean (*run_sync) (GoaBackendImapAuth *auth,
+ GDataInputStream *input,
+ GDataOutputStream *output,
+ GCancellable *cancellable,
+ GError **error);
+ /*< private >*/
+ /* Padding for future expansion */
+ gpointer goa_reserved[8];
+};
+
+GType goa_backend_imap_auth_get_type (void) G_GNUC_CONST;
+gboolean goa_backend_imap_auth_run_sync (GoaBackendImapAuth *auth,
+ GDataInputStream *input,
+ GDataOutputStream *output,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __GOA_BACKEND_IMAP_AUTH_H__ */
diff --git a/src/goa/goabackendimapauthoauth.c b/src/goa/goabackendimapauthoauth.c
new file mode 100644
index 0000000..0abb9f8
--- /dev/null
+++ b/src/goa/goabackendimapauthoauth.c
@@ -0,0 +1,572 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 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: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+#include <stdlib.h>
+
+#include "goabackendimapauth.h"
+#include "goabackendimapauthoauth.h"
+#include "goabackendoauthprovider.h"
+
+/**
+ * SECTION:goabackendimapauthoauth
+ * @title: GoaBackendImapAuthOAuth
+ * @short_description: XOAUTH authentication method for IMAP
+ *
+ * #GoaBackendImapAuthOAuth implements the <ulink
+ * url="http://code.google.com/apis/gmail/oauth/protocol.html">XOAUTH</ulink>
+ * authentication method for IMAP.
+ */
+
+/**
+ * GoaBackendImapAuthOAuth:
+ *
+ * The #GoaBackendImapAuthOAuth structure contains only private data
+ * and should only be accessed using the provided API.
+ */
+struct _GoaBackendImapAuthOAuth
+{
+ GoaBackendImapAuth parent_instance;
+
+ GoaBackendOAuthProvider *provider;
+ GoaObject *object;
+ gchar *request_uri;
+};
+
+typedef struct
+{
+ GoaBackendImapAuthClass parent_class;
+
+} GoaBackendImapAuthOAuthClass;
+
+enum
+{
+ PROP_0,
+ PROP_PROVIDER,
+ PROP_OBJECT,
+ PROP_REQUEST_URI
+};
+
+static gboolean goa_backend_imap_auth_oauth_run_sync (GoaBackendImapAuth *_auth,
+ GDataInputStream *input,
+ GDataOutputStream *output,
+ GCancellable *cancellable,
+ GError **error);
+
+G_DEFINE_TYPE (GoaBackendImapAuthOAuth, goa_backend_imap_auth_oauth, GOA_TYPE_BACKEND_IMAP_AUTH);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+goa_backend_imap_auth_oauth_finalize (GObject *object)
+{
+ GoaBackendImapAuthOAuth *auth = GOA_BACKEND_IMAP_AUTH_OAUTH (object);
+
+ g_object_unref (auth->provider);
+ g_object_unref (auth->object);
+
+ G_OBJECT_CLASS (goa_backend_imap_auth_oauth_parent_class)->finalize (object);
+}
+
+static void
+goa_backend_imap_auth_oauth_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GoaBackendImapAuthOAuth *auth = GOA_BACKEND_IMAP_AUTH_OAUTH (object);
+
+ switch (prop_id)
+ {
+ case PROP_PROVIDER:
+ g_value_set_object (value, auth->provider);
+ break;
+
+ case PROP_OBJECT:
+ g_value_set_object (value, auth->object);
+ break;
+
+ case PROP_REQUEST_URI:
+ g_value_set_string (value, auth->request_uri);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+goa_backend_imap_auth_oauth_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GoaBackendImapAuthOAuth *auth = GOA_BACKEND_IMAP_AUTH_OAUTH (object);
+
+ switch (prop_id)
+ {
+ case PROP_PROVIDER:
+ auth->provider = g_value_dup_object (value);
+ break;
+
+ case PROP_OBJECT:
+ auth->object = g_value_dup_object (value);
+ break;
+
+ case PROP_REQUEST_URI:
+ auth->request_uri = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+
+static void
+goa_backend_imap_auth_oauth_init (GoaBackendImapAuthOAuth *client)
+{
+}
+
+static void
+goa_backend_imap_auth_oauth_class_init (GoaBackendImapAuthOAuthClass *klass)
+{
+ GObjectClass *gobject_class;
+ GoaBackendImapAuthClass *auth_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->finalize = goa_backend_imap_auth_oauth_finalize;
+ gobject_class->get_property = goa_backend_imap_auth_oauth_get_property;
+ gobject_class->set_property = goa_backend_imap_auth_oauth_set_property;
+
+ auth_class = GOA_BACKEND_IMAP_AUTH_CLASS (klass);
+ auth_class->run_sync = goa_backend_imap_auth_oauth_run_sync;
+
+ /**
+ * GoaBackendImapAuthOAuth:provider:
+ *
+ * The #GoaBackendOAuthProvider object to use when calculating the XOAUTH mechanism parameter.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_PROVIDER,
+ g_param_spec_object ("provider",
+ "provider",
+ "provider",
+ GOA_TYPE_BACKEND_OAUTH_PROVIDER,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GoaBackendImapAuthOAuth:object:
+ *
+ * The #GoaObject object to use when calculating the XOAUTH mechanism parameter.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_OBJECT,
+ g_param_spec_object ("object",
+ "object",
+ "object",
+ GOA_TYPE_OBJECT,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GoaBackendImapAuthOAuth:request-uri:
+ *
+ * The request URI to use when calculating the XOAUTH mechanism parameter.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_REQUEST_URI,
+ g_param_spec_string ("request-uri",
+ "request-uri",
+ "request-uri",
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * goa_backend_imap_auth_oauth_new:
+ * @provider: A #GoaBackendOAuthProvider.
+ * @object: An account object.
+ * @request_uri: The request URI to use.
+ *
+ * Creates a new #GoaBackendImapAuth to be used for XOAUTH authentication.
+ *
+ * Returns: (type GoaBackendImapAuthOAuth): A #GoaBackendImapAuthOAuth. Free with g_object_unref().
+ */
+GoaBackendImapAuth *
+goa_backend_imap_auth_oauth_new (GoaBackendOAuthProvider *provider,
+ GoaObject *object,
+ const gchar *request_uri)
+{
+ g_return_val_if_fail (GOA_IS_BACKEND_OAUTH_PROVIDER (provider), NULL);
+ g_return_val_if_fail (GOA_IS_OBJECT (object), NULL);
+ return GOA_BACKEND_IMAP_AUTH (g_object_new (GOA_TYPE_BACKEND_IMAP_AUTH_OAUTH,
+ "provider", provider,
+ "object", object,
+ "request-uri", request_uri,
+ NULL));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+
+#include <libsoup/soup.h>
+
+#define OAUTH_ENCODE_STRING(x_) (x_ ? soup_uri_encode( (x_), "!$&'()*+,;=@") : g_strdup (""))
+
+#define SHA1_BLOCK_SIZE 64
+#define SHA1_LENGTH 20
+
+/*
+ * hmac_sha1:
+ * @key: The key
+ * @message: The message
+ *
+ * Given the key and message, compute the HMAC-SHA1 hash and return the base-64
+ * encoding of it. This is very geared towards OAuth, and as such both key and
+ * message must be NULL-terminated strings, and the result is base-64 encoded.
+ */
+static char *
+hmac_sha1 (const char *key, const char *message)
+{
+ GChecksum *checksum;
+ char *real_key;
+ guchar ipad[SHA1_BLOCK_SIZE];
+ guchar opad[SHA1_BLOCK_SIZE];
+ guchar inner[SHA1_LENGTH];
+ guchar digest[SHA1_LENGTH];
+ gsize key_length, inner_length, digest_length;
+ int i;
+
+ g_return_val_if_fail (key, NULL);
+ g_return_val_if_fail (message, NULL);
+
+ checksum = g_checksum_new (G_CHECKSUM_SHA1);
+
+ /* If the key is longer than the block size, hash it first */
+ if (strlen (key) > SHA1_BLOCK_SIZE) {
+ guchar new_key[SHA1_LENGTH];
+
+ key_length = sizeof (new_key);
+
+ g_checksum_update (checksum, (guchar*)key, strlen (key));
+ g_checksum_get_digest (checksum, new_key, &key_length);
+ g_checksum_reset (checksum);
+
+ real_key = g_memdup (new_key, key_length);
+ } else {
+ real_key = g_strdup (key);
+ key_length = strlen (key);
+ }
+
+ /* Sanity check the length */
+ g_assert (key_length <= SHA1_BLOCK_SIZE);
+
+ /* Protect against use of the provided key by NULLing it */
+ key = NULL;
+
+ /* Stage 1 */
+ memset (ipad, 0, sizeof (ipad));
+ memset (opad, 0, sizeof (opad));
+
+ memcpy (ipad, real_key, key_length);
+ memcpy (opad, real_key, key_length);
+
+ /* Stage 2 and 5 */
+ for (i = 0; i < sizeof (ipad); i++) {
+ ipad[i] ^= 0x36;
+ opad[i] ^= 0x5C;
+ }
+
+ /* Stage 3 and 4 */
+ g_checksum_update (checksum, ipad, sizeof (ipad));
+ g_checksum_update (checksum, (guchar*)message, strlen (message));
+ inner_length = sizeof (inner);
+ g_checksum_get_digest (checksum, inner, &inner_length);
+ g_checksum_reset (checksum);
+
+ /* Stage 6 and 7 */
+ g_checksum_update (checksum, opad, sizeof (opad));
+ g_checksum_update (checksum, inner, inner_length);
+
+ digest_length = sizeof (digest);
+ g_checksum_get_digest (checksum, digest, &digest_length);
+
+ g_checksum_free (checksum);
+ g_free (real_key);
+
+ return g_base64_encode (digest, digest_length);
+}
+
+static char *
+sign_plaintext (const gchar *consumer_secret,
+ const gchar *token_secret)
+{
+ char *cs;
+ char *ts;
+ char *rv;
+
+ cs = OAUTH_ENCODE_STRING (consumer_secret);
+ ts = OAUTH_ENCODE_STRING (token_secret);
+ rv = g_strconcat (cs, "&", ts, NULL);
+
+ g_free (cs);
+ g_free (ts);
+
+ return rv;
+}
+
+static char *
+sign_hmac (const gchar *consumer_secret,
+ const gchar *token_secret,
+ const gchar *http_method,
+ const gchar *request_uri,
+ const gchar *encoded_params)
+{
+ GString *text;
+
+ text = g_string_new (NULL);
+ g_string_append (text, http_method);
+ g_string_append_c (text, '&');
+ g_string_append_uri_escaped (text, request_uri, NULL, FALSE);
+ g_string_append_c (text, '&');
+ g_string_append_uri_escaped (text, encoded_params, NULL, FALSE);
+
+ /* PLAINTEXT signature value is the HMAC-SHA1 key value */
+ gchar *key;
+ key = sign_plaintext (consumer_secret, token_secret);
+
+ gchar *signature;
+ signature = hmac_sha1 (key, text->str);
+
+ g_free (key);
+ g_string_free (text, TRUE);
+
+ return signature;
+}
+
+static GHashTable *
+calculate_xoauth_params (const gchar *request_uri,
+ const gchar *consumer_key,
+ const gchar *consumer_secret,
+ const gchar *access_token,
+ const gchar *access_token_secret)
+{
+ GHashTable *params;
+ gchar *nonce;
+ gchar *timestamp;
+ GList *keys;
+ GList *l;
+ GString *normalized;
+
+ nonce = g_strdup_printf ("%u", g_random_int ());
+ timestamp = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) time (NULL));
+
+ params = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
+ g_hash_table_insert (params, "oauth_consumer_key", g_strdup (consumer_key));
+ g_hash_table_insert (params, "oauth_nonce", nonce); /* takes ownership */
+ g_hash_table_insert (params, "oauth_timestamp", timestamp); /* takes ownership */
+ g_hash_table_insert (params, "oauth_version", g_strdup ("1.0"));
+ g_hash_table_insert (params, "oauth_signature_method", g_strdup ("HMAC-SHA1"));
+ g_hash_table_insert (params, "oauth_token", g_strdup (access_token));
+
+ normalized = g_string_new (NULL);
+ keys = g_hash_table_get_keys (params);
+ keys = g_list_sort (keys, (GCompareFunc) g_strcmp0); /* TODO: locale specific? */
+ for (l = keys; l != NULL; l = l->next)
+ {
+ const gchar *key = l->data;
+ const gchar *value;
+ gchar *k;
+ gchar *v;
+
+ value = g_hash_table_lookup (params, key);
+ if (normalized->len > 0)
+ g_string_append_c (normalized, '&');
+
+ k = OAUTH_ENCODE_STRING (key);
+ v = OAUTH_ENCODE_STRING (value);
+
+ g_string_append_printf (normalized, "%s=%s", k, v);
+
+ g_free (k);
+ g_free (v);
+
+ g_print ("key %s=`%s'\n", key, value);
+ }
+ g_list_free (keys);
+
+ g_print ("normalized: `%s'\n", normalized->str);
+
+ gchar *signature;
+ signature = sign_hmac (consumer_secret,
+ access_token_secret,
+ "GET",
+ request_uri,
+ normalized->str);
+ g_hash_table_insert (params, "oauth_signature", signature); /* takes ownership */
+
+ g_string_free (normalized, TRUE);
+ return params;
+}
+
+static gchar *
+calculate_xoauth_param (const gchar *request_uri,
+ const gchar *consumer_key,
+ const gchar *consumer_secret,
+ const gchar *access_token,
+ const gchar *access_token_secret,
+ GError **error)
+{
+ gchar *ret;
+ GString *str;
+ GHashTable *params;
+ GList *keys;
+ GList *l;
+
+ params = calculate_xoauth_params (request_uri,
+ consumer_key,
+ consumer_secret,
+ access_token,
+ access_token_secret);
+ str = g_string_new ("GET ");
+ g_string_append (str, request_uri);
+ g_string_append_c (str, ' ');
+ keys = g_hash_table_get_keys (params);
+ keys = g_list_sort (keys, (GCompareFunc) g_strcmp0); /* TODO: locale specific? */
+ for (l = keys; l != NULL; l = l->next)
+ {
+ const gchar *key = l->data;
+ const gchar *value;
+ gchar *k;
+ gchar *v;
+
+ value = g_hash_table_lookup (params, key);
+ if (l != keys)
+ g_string_append_c (str, ',');
+
+ k = OAUTH_ENCODE_STRING (key);
+ v = OAUTH_ENCODE_STRING (value);
+ g_string_append_printf (str, "%s=\"%s\"", k, v);
+ g_free (k);
+ g_free (v);
+ }
+ g_list_free (keys);
+
+ ret = g_base64_encode ((const guchar *) str->str, str->len);
+ g_string_free (str, TRUE);
+ g_hash_table_unref (params);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+goa_backend_imap_auth_oauth_run_sync (GoaBackendImapAuth *_auth,
+ GDataInputStream *input,
+ GDataOutputStream *output,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GoaBackendImapAuthOAuth *auth = GOA_BACKEND_IMAP_AUTH_OAUTH (_auth);
+ gchar *access_token;
+ gchar *access_token_secret;
+ gchar *xoauth_param;
+ gchar *request;
+ gchar *response;
+ gboolean ret;
+
+ access_token = NULL;
+ access_token_secret = NULL;
+ xoauth_param = NULL;
+ request = NULL;
+ response = NULL;
+ ret = FALSE;
+
+ access_token = goa_backend_oauth_provider_get_access_token_sync (auth->provider,
+ auth->object,
+ FALSE, /* force_refresh */
+ &access_token_secret,
+ NULL, /* out_access_token_expires_in */
+ NULL, /* GCancellable */
+ error); /* GError */
+ if (access_token == NULL)
+ goto out;
+
+ xoauth_param = calculate_xoauth_param (auth->request_uri,
+ goa_backend_oauth_provider_get_consumer_key (auth->provider),
+ goa_backend_oauth_provider_get_consumer_secret (auth->provider),
+ access_token,
+ access_token_secret,
+ error);
+ if (xoauth_param == NULL)
+ goto out;
+
+ request = g_strdup_printf ("A001 AUTHENTICATE XOAUTH %s\r\n", xoauth_param);
+ if (!g_data_output_stream_put_string (output, request, cancellable, error))
+ goto out;
+
+ again:
+ response = g_data_input_stream_read_line (input, NULL, cancellable, error);
+ if (response == NULL)
+ goto out;
+ /* ignore untagged responses */
+ if (g_str_has_prefix (response, "*"))
+ {
+ g_free (response);
+ goto again;
+ }
+ if (!g_str_has_prefix (response, "A001 OK"))
+ {
+ g_set_error (error,
+ GOA_ERROR,
+ GOA_ERROR_FAILED,
+ "Unexpected response `%s' while doing XOAUTH authentication",
+ response);
+ goto out;
+ }
+
+ ret = TRUE;
+
+ out:
+ g_free (response);
+ g_free (request);
+ g_free (xoauth_param);
+ g_free (access_token);
+ g_free (access_token_secret);
+ return ret;
+}
diff --git a/src/goa/goabackendimapauthoauth.h b/src/goa/goabackendimapauthoauth.h
new file mode 100644
index 0000000..3899117
--- /dev/null
+++ b/src/goa/goabackendimapauthoauth.h
@@ -0,0 +1,46 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 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: David Zeuthen <davidz@redhat.com>
+ */
+
+#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION)
+#error "Only <goa/goabackend.h> can be included directly."
+#endif
+
+#ifndef __GOA_BACKEND_IMAP_AUTH_OAUTH_H__
+#define __GOA_BACKEND_IMAP_AUTH_OAUTH_H__
+
+#include <goa/goabackendtypes.h>
+
+G_BEGIN_DECLS
+
+#define GOA_TYPE_BACKEND_IMAP_AUTH_OAUTH (goa_backend_imap_auth_oauth_get_type ())
+#define GOA_BACKEND_IMAP_AUTH_OAUTH(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_BACKEND_IMAP_AUTH_OAUTH, GoaBackendImapAuthOAuth))
+#define GOA_IS_BACKEND_IMAP_AUTH_OAUTH(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_BACKEND_IMAP_AUTH_OAUTH))
+
+
+GType goa_backend_imap_auth_oauth_get_type (void) G_GNUC_CONST;
+GoaBackendImapAuth *goa_backend_imap_auth_oauth_new (GoaBackendOAuthProvider *provider,
+ GoaObject *object,
+ const gchar *request_uri);
+
+G_END_DECLS
+
+#endif /* __GOA_BACKEND_IMAP_AUTH_OAUTH_H__ */
diff --git a/src/goa/goabackendimapclient.c b/src/goa/goabackendimapclient.c
new file mode 100644
index 0000000..0b50b5a
--- /dev/null
+++ b/src/goa/goabackendimapclient.c
@@ -0,0 +1,2023 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 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: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+#include <stdlib.h>
+
+#include "goabackendimapauth.h"
+#include "goabackendimapclient.h"
+#include "goabackendimapmessage.h"
+#include "goabackendimapprivate.h"
+
+typedef enum
+{
+ CAPABILITY_FLAGS_NONE = 0,
+ CAPABILITY_FLAGS_IMAP4REV1 = (1<<0),
+ CAPABILITY_FLAGS_IDLE = (1<<1),
+ CAPABILITY_FLAGS_X_GM_EXT_1 = (1<<2),
+} CapabilityFlags;
+
+static CapabilityFlags
+capability_flags_from_strv (const gchar *const *strings)
+{
+ CapabilityFlags ret;
+ guint n;
+
+ g_return_val_if_fail (strings != NULL, CAPABILITY_FLAGS_NONE);
+
+ ret = CAPABILITY_FLAGS_NONE;
+ for (n = 0; strings[n] != NULL; n++)
+ {
+ if (g_strcmp0 (strings[n], "IMAP4rev1") == 0)
+ ret |= CAPABILITY_FLAGS_IMAP4REV1;
+ else if (g_strcmp0 (strings[n], "IDLE") == 0)
+ ret |= CAPABILITY_FLAGS_IDLE;
+ else if (g_strcmp0 (strings[n], "X-GM-EXT-1") == 0)
+ ret |= CAPABILITY_FLAGS_X_GM_EXT_1;
+ }
+ return ret;
+}
+
+/**
+ * GoaBackendImapClient:
+ *
+ * The #GoaBackendImapClient structure contains only private data and should
+ * only be accessed using the provided API.
+ */
+struct _GoaBackendImapClient
+{
+ /*< private >*/
+ GObject parent_instance;
+
+ /* set at init / object construction */
+ GMainContext *context;
+ gboolean closed;
+ GThread *worker_thread;
+ GCancellable *worker_cancellable;
+
+ /* set at object construction */
+ GoaBackendImapAuth *auth;
+ gchar *host_and_port;
+ guint use_tls;
+ gchar *criteria;
+ guint query_offset;
+ guint query_size;
+
+ /* The remaining data members are already related to the running
+ * session
+ */
+ gboolean is_running;
+ CapabilityFlags caps;
+ GSocketClient *sc;
+ GSocketConnection *c;
+ GDataInputStream *dis;
+ GDataOutputStream *dos;
+ gint num_search_result;
+ gint *search_result;
+
+ /* used for generating command tags */
+ guint tag;
+
+ /* the number of messages in the selected mailbox */
+ gint exists;
+
+ /* the number of unseen messages in the selected mailbox */
+ gint unseen;
+
+ /* the 32-bit uidvalidity */
+ gint uidvalidity;
+
+ /* The number of times we've fetched data */
+ gint num_completed_fetches;
+ GCond *num_completed_fetches_cond;
+ GMutex *num_completed_fetches_mutex;
+
+ /* A list of results of the query */
+ GList *messages;
+ /* A list currently being built */
+ GList *messages_buildup;
+};
+
+typedef struct _GoaBackendImapClientClass GoaBackendImapClientClass;
+
+struct _GoaBackendImapClientClass
+{
+ GObjectClass parent_class;
+ void (*updated) (GoaBackendImapClient *client);
+ void (*closed) (GoaBackendImapClient *client,
+ const GError **error);
+};
+
+/**
+ * SECTION:goabackendimapclient
+ * @title: GoaBackendImapClient
+ * @short_description: A simple IMAP client
+ *
+ * #GoaBackendImapClient is a type used for obtaining information via the
+ * <ulink url="http://tools.ietf.org/html/rfc3501">IMAP</ulink>
+ * protocol.
+ */
+
+enum
+{
+ UPDATED_SIGNAL,
+ CLOSED_SIGNAL,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_HOST_AND_PORT,
+ PROP_USE_TLS,
+ PROP_CRITERIA,
+ PROP_QUERY_OFFSET,
+ PROP_QUERY_SIZE,
+ PROP_AUTH,
+ PROP_CLOSED
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+static void goa_backend_imap_client__g_initable_iface_init (GInitableIface *iface);
+
+static void goa_backend_imap_client__g_async_initable_iface_init (GAsyncInitableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GoaBackendImapClient, goa_backend_imap_client, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, goa_backend_imap_client__g_initable_iface_init)
+ G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, goa_backend_imap_client__g_async_initable_iface_init));
+
+G_LOCK_DEFINE_STATIC (messages_lock);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+goa_backend_imap_client_finalize (GObject *object)
+{
+ GoaBackendImapClient *client = GOA_BACKEND_IMAP_CLIENT (object);
+
+ g_free (client->search_result);
+ if (client->sc != NULL)
+ g_object_unref (client->sc);
+ if (client->c != NULL)
+ g_object_unref (client->c);
+ if (client->dis != NULL)
+ g_object_unref (client->dis);
+ if (client->dos != NULL)
+ g_object_unref (client->dos);
+
+ g_free (client->host_and_port);
+ g_free (client->criteria);
+ g_object_unref (client->auth);
+
+ g_mutex_free (client->num_completed_fetches_mutex);
+ g_cond_free (client->num_completed_fetches_cond);
+
+ g_object_unref (client->worker_cancellable);
+ if (client->context != NULL)
+ g_main_context_unref (client->context);
+
+ G_OBJECT_CLASS (goa_backend_imap_client_parent_class)->finalize (object);
+}
+
+static void
+goa_backend_imap_client_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GoaBackendImapClient *client = GOA_BACKEND_IMAP_CLIENT (object);
+
+ switch (prop_id)
+ {
+ case PROP_HOST_AND_PORT:
+ g_value_set_string (value, client->host_and_port);
+ break;
+
+ case PROP_USE_TLS:
+ g_value_set_boolean (value, client->use_tls);
+ break;
+
+ case PROP_CRITERIA:
+ g_value_set_string (value, client->criteria);
+ break;
+
+ case PROP_QUERY_OFFSET:
+ g_value_set_uint (value, client->query_offset);
+ break;
+
+ case PROP_QUERY_SIZE:
+ g_value_set_uint (value, client->query_size);
+ break;
+
+ case PROP_AUTH:
+ g_value_set_object (value, client->auth);
+ break;
+
+ case PROP_CLOSED:
+ g_value_set_boolean (value, client->closed);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+goa_backend_imap_client_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GoaBackendImapClient *client = GOA_BACKEND_IMAP_CLIENT (object);
+
+ switch (prop_id)
+ {
+ case PROP_HOST_AND_PORT:
+ client->host_and_port = g_value_dup_string (value);
+ break;
+
+ case PROP_USE_TLS:
+ client->use_tls = g_value_get_boolean (value);
+ break;
+
+ case PROP_CRITERIA:
+ client->criteria = g_value_dup_string (value);
+ break;
+
+ case PROP_QUERY_OFFSET:
+ client->query_offset = g_value_get_uint (value);
+ break;
+
+ case PROP_QUERY_SIZE:
+ client->query_size = g_value_get_uint (value);
+ break;
+
+ case PROP_AUTH:
+ client->auth = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+goa_backend_imap_client_init (GoaBackendImapClient *client)
+{
+ client->num_completed_fetches_cond = g_cond_new ();
+ client->num_completed_fetches_mutex = g_mutex_new ();
+
+ client->context = g_main_context_get_thread_default ();
+ if (client->context != NULL)
+ g_main_context_ref (client->context);
+ client->worker_cancellable = g_cancellable_new ();
+}
+
+static void
+goa_backend_imap_client_class_init (GoaBackendImapClientClass *klass)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->finalize = goa_backend_imap_client_finalize;
+ gobject_class->set_property = goa_backend_imap_client_set_property;
+ gobject_class->get_property = goa_backend_imap_client_get_property;
+
+ /**
+ * GoaBackendImapClient:host-and-port:
+ *
+ * The host to connect to.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_HOST_AND_PORT,
+ g_param_spec_string ("host-and-port",
+ "host-and-port",
+ "host-and-port",
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GoaBackendImapClient:use-tls:
+ *
+ * Whether TLS should be used when establishing the connection.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_USE_TLS,
+ g_param_spec_boolean ("use-tls",
+ "use-tls",
+ "use-tls",
+ TRUE,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GoaBackendImapClient:criteria:
+ *
+ * The criteria used for selecting messages. See
+ * goa_backend_imap_client_new_sync() for an explanation of what
+ * strings are valid.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_CRITERIA,
+ g_param_spec_string ("criteria",
+ "criteria",
+ "criteria",
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GoaBackendImapClient:query-offset:
+ *
+ * Offset into array of matching messages used when returning messages.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_QUERY_OFFSET,
+ g_param_spec_uint ("query-offset",
+ "query-offset",
+ "query-offset",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GoaBackendImapClient:query-size:
+ *
+ * Maximum number of messages to return.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_QUERY_SIZE,
+ g_param_spec_uint ("query-size",
+ "query-size",
+ "query-size",
+ 1, G_MAXUINT, 10,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GoaBackendImapClient:auth:
+ *
+ * The #GoaBackendImapAuth object used for authentication the connection.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_AUTH,
+ g_param_spec_object ("auth",
+ "auth",
+ "auth",
+ GOA_TYPE_BACKEND_IMAP_AUTH,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GoaBackendImapClient:closed:
+ *
+ * Whether the connection to the IMAP server has been closed.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_CLOSED,
+ g_param_spec_boolean ("closed",
+ "closed",
+ "closed",
+ FALSE,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GoaBackendImapClient::updated:
+ * @client: The #GoaBackendImapClient emitting the signal.
+ *
+ * Signal emitted every time messages has been fetched from the
+ * server. This does not neccesarily mean that the messages returned
+ * by goa_backend_imap_client_get_messages() has changed.
+ */
+ signals[UPDATED_SIGNAL] =
+ g_signal_new ("updated",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GoaBackendImapClientClass, updated),
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ /**
+ * GoaBackendImapClient::closed:
+ * @client: The #GoaBackendImapClient emitting the signal.
+ * @error: A #GError or %NULL.
+ *
+ * Emitted when the connection has been closed. If the connection
+ * was closed because of an error condition, @error will be
+ * non-%NULL.
+ *
+ * After this signal has been emitted, the object is no longer useful.
+ */
+ signals[CLOSED_SIGNAL] =
+ g_signal_new ("closed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GoaBackendImapClientClass, closed),
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_ERROR);
+}
+
+static gboolean
+emit_updated_in_idle_cb (gpointer user_data)
+{
+ GoaBackendImapClient *client = GOA_BACKEND_IMAP_CLIENT (user_data);
+ g_signal_emit (client, signals[UPDATED_SIGNAL], 0);
+ return FALSE;
+}
+
+static void
+emit_updated_in_idle (GoaBackendImapClient *client)
+{
+ GSource *source;
+
+ g_return_if_fail (GOA_IS_BACKEND_IMAP_CLIENT (client));
+
+ source = g_idle_source_new ();
+ g_source_set_priority (source, G_PRIORITY_DEFAULT);
+ g_source_set_callback (source, emit_updated_in_idle_cb, g_object_ref (client), g_object_unref);
+ g_source_attach (source, client->context);
+ g_source_unref (source);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GoaBackendImapClient *client;
+ GError *error;
+} EmitClosedData;
+
+static void
+emit_closed_data_free (EmitClosedData *data)
+{
+ g_object_unref (data->client);
+ if (data->error != NULL)
+ g_error_free (data->error);
+ g_free (data);
+}
+
+static gboolean
+emit_closed_in_idle_cb (gpointer user_data)
+{
+ EmitClosedData *data = user_data;
+ g_signal_emit (data->client, signals[CLOSED_SIGNAL], 0, data->error);
+ g_object_notify (G_OBJECT (data->client), "closed");
+ return FALSE;
+}
+
+static void
+emit_closed_in_idle (GoaBackendImapClient *client,
+ const GError *error)
+{
+ GSource *source;
+ EmitClosedData *data;
+
+ g_return_if_fail (GOA_IS_BACKEND_IMAP_CLIENT (client));
+
+ data = g_new0 (EmitClosedData, 1);
+ data->client = g_object_ref (client);
+ data->error = error != NULL ? g_error_copy (error) : NULL;
+
+ source = g_idle_source_new ();
+ g_source_set_priority (source, G_PRIORITY_DEFAULT);
+ g_source_set_callback (source, emit_closed_in_idle_cb, data, (GDestroyNotify) emit_closed_data_free);
+ g_source_attach (source, client->context);
+ g_source_unref (source);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * goa_backend_imap_client_new:
+ * @host_and_port: The name and optionally port to connect to.
+ * @use_tls: Whether TLS should be used.
+ * @auth: Object used for authenticating the connection.
+ * @criteria: Criteria string used to filter returned messages.
+ * @query_offset: Offset of result window in the array of matching messages.
+ * @query_size: Size of result window in the array of matching messages.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @callback: Function to call when the request has been satisfied.
+ * @user_data: Data to pass to @callback.
+ *
+ * Async version of goa_backend_imap_client_new_sync().
+ *
+ * When the result is ready, @callback will be called in the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link>. You can then use goa_backend_imap_client_new_finish()
+ * to get the result.
+ */
+void
+goa_backend_imap_client_new (const gchar *host_and_port,
+ gboolean use_tls,
+ GoaBackendImapAuth *auth,
+ const gchar *criteria,
+ guint query_offset,
+ guint query_size,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (GOA_IS_BACKEND_IMAP_AUTH (auth));
+ g_return_if_fail (host_and_port != NULL);
+ g_return_if_fail (criteria != NULL);
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+ g_async_initable_new_async (GOA_TYPE_BACKEND_IMAP_CLIENT,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ "host-and-port", host_and_port,
+ "use-tls", use_tls,
+ "auth", auth,
+ "criteria", criteria,
+ "query-offset", query_offset,
+ "query-size", query_size,
+ NULL);
+}
+
+/**
+ * goa_backend_imap_client_new_finish:
+ * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to goa_backend_imap_client_new().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with goa_backend_imap_client_new().
+ *
+ * Returns: A #GoaBackendImapClient or %NULL if @error is set.
+ */
+GoaBackendImapClient *
+goa_backend_imap_client_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *ret;
+ GObject *source_object;
+ source_object = g_async_result_get_source_object (res);
+ ret = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), res, error);
+ g_object_unref (source_object);
+ if (ret != NULL)
+ return GOA_BACKEND_IMAP_CLIENT (ret);
+ else
+ return NULL;
+}
+
+/**
+ * goa_backend_imap_client_new_sync:
+ * @host_and_port: The name and optionally port to connect to.
+ * @use_tls: Whether TLS should be used.
+ * @auth: Object used for authenticating the connection.
+ * @criteria: Criteria string used to filter returned messages.
+ * @query_offset: Offset of result window in the array of matching messages.
+ * @query_size: Size of result window in the array of matching messages.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Creates a new IMAP client connecting to the mailbox INBOX at the
+ * server represented by @host_and_port using @auth to authenticate
+ * the IMAP connection. If authentication fails or messages can't be
+ * downloaded, then this constructor returns %NULL and @error will be
+ * set.
+ *
+ * The returned object is used to obtain #GoaBackendImapMessage
+ * instances representing objects on the IMAP server. The messages are
+ * selected by requesting the IMAP server to match all messages in the
+ * mailbox against the @criteria string. Of the matching messages,
+ * @query_size messages starting at offset @query_offset will be
+ * downloaded. Note that the entire message is not downloaded - only
+ * the data represented by #GoaBackendImapMessage (e.g. typically only
+ * hundreds of bytes per message). Use
+ * goa_backend_imap_client_get_messages() to obtain the matching
+ * messages.
+ *
+ * The connection to the IMAP server is live insofar the <ulink
+ * url="http://en.wikipedia.org/wiki/IMAP_IDLE">IMAP IDLE</ulink> (or
+ * polling, if IDLE is not available) command is used to get notified
+ * of new messages - the #GoaBackendImapClient::updated signal is
+ * emitted every time new messages are fetched. Use
+ * goa_backend_imap_client_refresh() to force a refresh.
+ *
+ * Note that the connection can be broken at any time - the
+ * #GoaBackendImapClient::closed signal is emitted this happens. The
+ * object is no longer useful when this happens. Use
+ * goa_backend_imap_client_close() to close the connection manually.
+ *
+ * Also note that this constructor blocks the calling thread - see
+ * goa_backend_imap_client_new() for the async non-blocking version.
+ *
+ * All signals are emitted in the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link> of the thread you are calling this constructor from.
+ *
+ * Valid strings for @criteria includes the empty string (to match all
+ * messages) and <literal>unread</literal> (to match all unread
+ * messages). More options may be added in the future.
+ *
+ * Returns: A #GoaBackendImapClient or %NULL if @error is set.
+ */
+GoaBackendImapClient *
+goa_backend_imap_client_new_sync (const gchar *host_and_port,
+ gboolean use_tls,
+ GoaBackendImapAuth *auth,
+ const gchar *criteria,
+ guint query_offset,
+ guint query_size,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GInitable *ret;
+ g_return_val_if_fail (GOA_IS_BACKEND_IMAP_AUTH (auth), NULL);
+ g_return_val_if_fail (host_and_port != NULL, NULL);
+ g_return_val_if_fail (criteria != NULL, NULL);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+ ret = g_initable_new (GOA_TYPE_BACKEND_IMAP_CLIENT,
+ cancellable,
+ error,
+ "host-and-port", host_and_port,
+ "use-tls", use_tls,
+ "auth", auth,
+ "criteria", criteria,
+ "query-offset", query_offset,
+ "query-size", query_size,
+ NULL);
+ if (ret != NULL)
+ return GOA_BACKEND_IMAP_CLIENT (ret);
+ else
+ return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+parse_int (const gchar *s,
+ gint *out_result)
+{
+ gboolean ret;
+ gchar *endp;
+ gint result;
+
+ g_return_val_if_fail (s != NULL, FALSE);
+
+ ret = FALSE;
+ result = strtol (s, &endp, 0);
+ if (result == 0 && endp == s)
+ goto out;
+
+ if (out_result != NULL)
+ *out_result = result;
+
+ ret = TRUE;
+
+ out:
+ return ret;
+}
+
+static gboolean
+fetch_check (const gchar *data,
+ guint *pos,
+ const gchar *key)
+{
+ gsize key_len;
+ gboolean ret;
+
+ g_return_val_if_fail (data != NULL, FALSE);
+ g_return_val_if_fail (pos != NULL, FALSE);
+ g_return_val_if_fail (key != NULL, FALSE);
+
+ ret = FALSE;
+
+ key_len = strlen (key);
+ if (strncmp (data + *pos, key, key_len) == 0 && data[*pos + key_len] == ' ')
+ {
+ ret = TRUE;
+ *pos += key_len + 1;
+ goto out;
+ }
+ out:
+ return ret;
+}
+
+static gchar **
+fetch_parenthesized_list (const gchar *data,
+ guint *pos)
+{
+ gchar **ret;
+ gchar *s;
+ guint start_pos;
+
+ g_return_val_if_fail (data != NULL, FALSE);
+ g_return_val_if_fail (pos != NULL, FALSE);
+
+ ret = NULL;
+
+ if (data[*pos] != '(')
+ goto out;
+ *pos += 1;
+ start_pos = *pos;
+
+ while (data[*pos] != ')' && data[*pos] != '\0')
+ *pos += 1;
+ if (data[*pos] != ')')
+ goto out;
+
+ s = g_strndup (data + start_pos, *pos - start_pos);
+ ret = g_strsplit (s, " ", -1);
+ g_free (s);
+
+ *pos += 1;
+
+ out:
+ return ret;
+}
+
+static gchar *
+fetch_string (const gchar *data,
+ guint *pos)
+{
+ gchar *ret;
+ guint start_pos;
+
+ g_return_val_if_fail (data != NULL, FALSE);
+ g_return_val_if_fail (pos != NULL, FALSE);
+
+ ret = NULL;
+
+ start_pos = *pos;
+
+ while (data[*pos] != ' ' && data[*pos] != ')' && data[*pos] != '\0')
+ *pos += 1;
+
+ ret = g_strndup (data + start_pos, *pos - start_pos);
+
+ return ret;
+}
+
+static gchar *
+fetch_quoted_string (const gchar *data,
+ guint *pos)
+{
+ gchar *ret;
+ guint start_pos;
+
+ g_return_val_if_fail (data != NULL, FALSE);
+ g_return_val_if_fail (pos != NULL, FALSE);
+
+ ret = NULL;
+
+ if (data[*pos] != '"')
+ goto out;
+ *pos += 1;
+
+ start_pos = *pos;
+
+ while (data[*pos] != '"' && data[*pos] != '\0')
+ *pos += 1;
+
+ ret = g_strndup (data + start_pos, *pos - start_pos);
+
+ *pos += 1;
+
+ out:
+ return ret;
+}
+
+static guint
+lookup_month (const gchar *str)
+{
+ static const gchar *months[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+ guint n;
+ for (n = 0; months[n] != NULL; n++)
+ if (g_strcmp0 (str, months[n]) == 0)
+ return n + 1;
+ return 0;
+}
+
+static gboolean
+fetch_date_time (const gchar *data,
+ guint *pos,
+ gint64 *out_value)
+{
+ gchar *str_value;
+ GTimeZone *tz;
+ GDateTime *dt;
+ gchar month[4];
+ gint mon;
+ gint day, year, hour, min, sec, offset;
+ gboolean ret;
+
+ g_return_val_if_fail (data != NULL, FALSE);
+ g_return_val_if_fail (pos != NULL, FALSE);
+ g_return_val_if_fail (out_value != NULL, FALSE);
+
+ str_value = NULL;
+ tz = NULL;
+ dt = NULL;
+ ret = FALSE;
+
+ str_value = fetch_quoted_string (data, pos);
+ if (str_value == NULL)
+ goto out;
+
+ if (sscanf (str_value,
+ "%02d-%03s-%04d %02d:%02d:%02d %04d",
+ &day, month, &year, &hour, &min, &sec, &offset) != 7)
+ goto out;
+
+ tz = g_time_zone_new (str_value + strlen (str_value) - 5);
+ if (tz == NULL)
+ goto out;
+ mon = lookup_month (month);
+ if (mon == 0)
+ goto out;
+
+ dt = g_date_time_new (tz, year, mon, day, hour, min, (gdouble) sec);
+ if (out_value != NULL)
+ *out_value = g_date_time_to_unix (dt);
+
+ ret = TRUE;
+
+ out:
+ if (dt != NULL)
+ g_date_time_unref (dt);
+ if (tz != NULL)
+ g_time_zone_unref (tz);
+ g_free (str_value);
+ return ret;
+}
+
+static gboolean
+fetch_int (const gchar *data,
+ guint *pos,
+ gint *out_value)
+{
+ gchar *str_value;
+ gboolean ret;
+
+ g_return_val_if_fail (data != NULL, FALSE);
+ g_return_val_if_fail (pos != NULL, FALSE);
+ g_return_val_if_fail (out_value != NULL, FALSE);
+
+ str_value = NULL;
+ ret = FALSE;
+
+ str_value = fetch_string (data, pos);
+ if (str_value == NULL)
+ goto out;
+
+ if (!parse_int (str_value, out_value))
+ goto out;
+
+ ret = TRUE;
+
+ out:
+ g_free (str_value);
+ return ret;
+}
+
+static gchar *
+fetch_literal_string (const gchar *data,
+ guint *pos,
+ guint *out_len)
+{
+ gchar *ret;
+ guint start_pos;
+ guint len;
+
+ g_return_val_if_fail (data != NULL, FALSE);
+ g_return_val_if_fail (pos != NULL, FALSE);
+
+ ret = NULL;
+
+ start_pos = *pos;
+
+ if (data[*pos] != '{')
+ goto out;
+ *pos += 1;
+ while (g_ascii_isdigit (data[*pos]))
+ *pos += 1;
+ if (strncmp (data + *pos, "}\r\n", 3) != 0)
+ goto out;
+ *pos += 3;
+
+ if (!parse_int (data + start_pos + 1, (gint*) &len))
+ goto out;
+
+ ret = g_strndup (data + *pos, len);
+ *pos += len;
+
+ if (out_len != NULL)
+ *out_len = len;
+
+ out:
+ return ret;
+}
+
+/* Simple FETCH response parser only handling a subset of FETCH
+ * responses, see
+ *
+ * http://tools.ietf.org/html/rfc3501#section-7.4.2
+ *
+ * for more details.
+ */
+static void
+handle_fetch_response (GoaBackendImapClient *client,
+ guint message_seqnum,
+ const gchar *data)
+{
+ guint n;
+ gchar **flags_strv;
+ gboolean parsed;
+ gboolean has_uid;
+ gint uid;
+ gint64 internal_date;
+ gchar *thread_id;
+ gchar *rfc822_headers;
+ guint rfc822_headers_len;
+ gchar *excerpt;
+ guint excerpt_len;
+ GoaBackendImapMessageFlags flags;
+ GoaBackendImapMessage *message;
+
+ g_return_if_fail (GOA_IS_BACKEND_IMAP_CLIENT (client));
+ g_return_if_fail (message_seqnum >= 1);
+ g_return_if_fail (data != NULL);
+
+ uid = 0;
+ has_uid = FALSE;
+ thread_id = NULL;
+ flags_strv = NULL;
+ excerpt = NULL;
+ rfc822_headers = NULL;
+ parsed = FALSE;
+
+ if (data[0] != '(')
+ goto out;
+ n = 1;
+ while (data[n] != ')' && data[n] != '\0')
+ {
+ if (fetch_check (data, &n, "X-GM-THRID"))
+ {
+ thread_id = fetch_string (data, &n);
+ if (thread_id == NULL)
+ goto out;
+
+ }
+ else if (fetch_check (data, &n, "UID"))
+ {
+ if (!fetch_int (data, &n, &uid))
+ goto out;
+ has_uid = TRUE;
+ }
+ else if (fetch_check (data, &n, "INTERNALDATE"))
+ {
+ if (!fetch_date_time (data, &n, &internal_date))
+ goto out;
+ }
+ else if (fetch_check (data, &n, "FLAGS"))
+ {
+ flags_strv = fetch_parenthesized_list (data, &n);
+ if (flags_strv == NULL)
+ goto out;
+ }
+ else if (fetch_check (data, &n, "BODY[HEADER.FIELDS (Date From To Cc Subject)]"))
+ {
+ rfc822_headers = fetch_literal_string (data, &n, &rfc822_headers_len);
+ if (rfc822_headers == NULL)
+ goto out;
+ }
+ else if (fetch_check (data, &n, "BODY[TEXT]<0>"))
+ {
+ excerpt = fetch_literal_string (data, &n, &excerpt_len);
+ if (excerpt == NULL)
+ goto out;
+ }
+ else
+ {
+ /* Don't know how to handle unknown data so fail completely */
+ goto out;
+ }
+ /* advance to next value in FETCH response list, if any */
+ while (data[n] == ' ')
+ n++;
+ }
+
+ /* thread_id is optional (it's a GMail extension) */
+ if (!has_uid || flags_strv == NULL || rfc822_headers == NULL || excerpt == NULL)
+ goto out;
+
+ flags = goa_backend_imap_message_flags_from_strv ((const gchar* const *) flags_strv);
+
+ /* OK, message is valid */
+ parsed = TRUE;
+
+ message = goa_backend_imap_message_new ();
+ message->seqnum = message_seqnum;
+ message->uid = ((guint64) client->uidvalidity << 32) | ((guint64) uid);
+ message->flags = flags;
+ message->internal_date = internal_date;
+ /* steal the rfc822_headers string */
+ message->rfc822_headers = rfc822_headers;
+ rfc822_headers = NULL;
+ /* steal the excerpt string */
+ message->excerpt = excerpt;
+ excerpt = NULL;
+
+ client->messages_buildup = g_list_prepend (client->messages_buildup, message);
+
+ out:
+ if (!parsed)
+ {
+ g_debug ("Was unable to parse FETCH response for msg %d with data `%s'", message_seqnum, data);
+ }
+ g_free (rfc822_headers);
+ g_free (excerpt);
+ g_free (thread_id);
+ g_strfreev (flags_strv);
+}
+
+
+static void
+handle_expunge_response (GoaBackendImapClient *client,
+ guint message_seqnum)
+{
+ /* We are currently utterly uninterested in this since we
+ * currently re-request everything in the query window
+ */
+}
+
+static void
+handle_status_response (GoaBackendImapClient *client,
+ const gchar *mailbox,
+ const gchar *data)
+{
+ gchar **items;
+ guint n;
+
+ items = NULL;
+
+ n = 0;
+ items = fetch_parenthesized_list (data, &n);
+ if (items == NULL)
+ goto out;
+
+ for (n = 0; items[n] != NULL && items[n+1] != NULL; n += 2)
+ {
+ gint value;
+ if (g_strcmp0 (items[n], "MESSAGES") == 0 && parse_int (items[n+1], &value))
+ {
+ client->exists = value;
+ }
+ else if (g_strcmp0 (items[n], "UNSEEN") == 0 && parse_int (items[n+1], &value))
+ {
+ client->unseen = value;
+ }
+ else if (g_strcmp0 (items[n], "UIDVALIDITY") == 0 && parse_int (items[n+1], &value))
+ {
+ client->uidvalidity = value;
+ }
+ }
+
+ out:
+ g_strfreev (items);
+}
+
+static gint
+sort_by_seqnum_reverse (gint *a, gint *b)
+{
+ return *b - *a;
+}
+
+static void
+handle_search_response (GoaBackendImapClient *client,
+ const gchar *data)
+{
+ gchar **tokens;
+ GArray *array;
+ guint n;
+
+ array = g_array_sized_new (FALSE,
+ FALSE,
+ sizeof (gint),
+ 100);
+
+ /* TODO: this could be done more efficiently but I'm pretty lazy */
+ tokens = g_strsplit (data, " ", -1);
+ for (n = 0; tokens[n] != NULL; n++)
+ {
+ gint seqnum;
+ seqnum = atoi (tokens[n]);
+ g_array_append_val (array, seqnum);
+ }
+ g_strfreev (tokens);
+
+ /* Sort resulting messages by seqnum so the newest ones are first */
+ g_array_sort (array, (GCompareFunc) sort_by_seqnum_reverse);
+
+ if (client->search_result != NULL)
+ g_free (client->search_result);
+ client->num_search_result = array->len;
+ client->search_result = (gint *) g_array_free (array, FALSE);
+}
+
+static void
+handle_untagged_response (GoaBackendImapClient *client,
+ const gchar *response)
+{
+ gint n;
+ gchar *s;
+ gchar **tokens;
+
+ s = NULL;
+ tokens = NULL;
+
+ s = g_strdup (response + 1); /* skip leading asterix */
+ g_strchug (s); /* and leading whitespace, if any */
+
+ /* special case */
+ if (g_str_has_prefix (s, "CAPABILITY "))
+ {
+ gchar **strv;
+ strv = g_strsplit (s + sizeof "CAPABILITY " - 1, " ", -1);
+ client->caps = capability_flags_from_strv ((const gchar *const *) strv);
+ g_strfreev (strv);
+ goto out;
+ }
+
+ tokens = g_strsplit (s, " ", 3);
+ if (g_strv_length (tokens) < 2)
+ {
+ g_debug ("ignoring short response `%s'", response);
+ goto out;
+ }
+
+ if (g_strcmp0 (tokens[1], "EXISTS") == 0 && parse_int (tokens[0], &n))
+ {
+ client->exists = n;
+ }
+ else if (g_strcmp0 (tokens[1], "FETCH") == 0 && parse_int (tokens[0], &n))
+ {
+ if (tokens[2] != NULL)
+ handle_fetch_response (client, n, tokens[2]);
+ else
+ g_debug ("ignoring fetch response with no data `%s'", response);
+ }
+ else if (g_strcmp0 (tokens[1], "EXPUNGE") == 0 && parse_int (tokens[0], &n))
+ {
+ handle_expunge_response (client, n);
+ }
+ else if (g_strcmp0 (tokens[0], "STATUS") == 0)
+ {
+ handle_status_response (client, tokens[1], tokens[2]);
+ }
+ else if (g_strcmp0 (tokens[0], "SEARCH") == 0)
+ {
+ handle_search_response (client, s + sizeof "SEARCH " - 1);
+ }
+ else
+ {
+ g_debug ("TODO: unhandled untagged response `%s'", response);
+ }
+
+ out:
+ g_strfreev (tokens);
+ g_free (s);
+}
+
+static gchar *
+goa_backend_imap_client_run_command_sync (GoaBackendImapClient *client,
+ const gchar *command,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gchar *s;
+ gchar *tag;
+ gchar *ret;
+ GString *response;
+ gsize len;
+ gboolean is_idle_command;
+ gboolean idle_has_sent_done;
+ GError *local_error;
+
+ g_return_val_if_fail (GOA_IS_BACKEND_IMAP_CLIENT (client), NULL);
+ g_return_val_if_fail (command != NULL, NULL);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ s = NULL;
+ tag = NULL;
+ response = NULL;
+ ret = NULL;
+ is_idle_command = FALSE;
+ idle_has_sent_done = FALSE;
+
+ if (g_strcmp0 (command, "IDLE") == 0)
+ is_idle_command = TRUE;
+
+ tag = g_strdup_printf ("T%05d ", client->tag++);
+ s = g_strconcat (tag, command, "\r\n", NULL);
+ if (!g_data_output_stream_put_string (client->dos, s, cancellable, error))
+ {
+ g_prefix_error (error, "Error putting string: ");
+ goto out;
+ }
+ g_free (s);
+
+ response = g_string_new (NULL);
+ again:
+ local_error = NULL;
+ s = g_data_input_stream_read_line (client->dis, NULL, cancellable, &local_error);
+ if (s == NULL)
+ {
+ if (local_error != NULL)
+ {
+ g_prefix_error (&local_error, "Error reading line: ");
+ /* if doing an IDLE that was cancelled, write the continuation string
+ * anyway, ignoring the cancellable
+ */
+ if ((local_error->domain == G_IO_ERROR && local_error->code == G_IO_ERROR_CANCELLED) && is_idle_command)
+ {
+ if (!g_data_output_stream_put_string (client->dos, "DONE\r\n", NULL, error))
+ {
+ /* if this fails, ignore the cancelled error */
+ g_error_free (local_error);
+ g_prefix_error (error, "Error putting IDLE continuation string: ");
+ goto out;
+ }
+ /* TODO: this way we're ignoring the response to the IDLE command we just
+ * fired off.. it's not a problem per se, but it's annoying to see in
+ * debug output... we could sit around and wait for the response but there's
+ * really no point in doing so
+ */
+ }
+ g_propagate_error (error, local_error);
+ }
+ else
+ {
+ g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED, "No content to read");
+ }
+ goto out;
+ }
+ len = strlen (s);
+ /* So far so good */
+ g_string_append_len (response, s, len);
+
+ /* Could be it's a literal string */
+ if (len >= 3 && s[len-1] == '}')
+ {
+ gint n;
+ n = len - 2;
+ while (g_ascii_isdigit (s[n]) && n >= 0)
+ n--;
+ if (s[n] == '{')
+ {
+ gsize num_read;
+ gsize lit_len;
+ gchar *lit;
+ lit_len = atoi (s + n + 1);
+ /* Don't blindly allocate any big number of bytes */
+ if (lit_len > 10*1024*1024)
+ {
+ g_set_error (error,
+ GOA_ERROR,
+ GOA_ERROR_FAILED,
+ "Refusing to read an additional %" G_GSIZE_FORMAT " bytes for literal string",
+ lit_len);
+ g_free (s);
+ goto out;
+ }
+ lit = g_malloc0 (lit_len + 1);
+ if (!g_input_stream_read_all (G_INPUT_STREAM (client->dis),
+ lit,
+ lit_len,
+ &num_read,
+ cancellable,
+ error))
+ {
+ g_free (lit);
+ g_prefix_error (error,
+ "Requested %" G_GSIZE_FORMAT " bytes for literal string "
+ "but only read %" G_GSSIZE_FORMAT ": ",
+ lit_len, num_read);
+ g_free (s);
+ goto out;
+ }
+ /* include the original CRLF, then the literal string */
+ g_string_append (response, "\r\n");
+ g_string_append_len (response, lit, lit_len);
+ g_free (lit);
+ g_free (s);
+ /* then keep reading */
+ goto again;
+ }
+ }
+
+ if (g_str_has_prefix (response->str, tag))
+ {
+ gint tag_len;
+ tag_len = strlen (tag);
+ if (g_str_has_prefix (response->str + tag_len, "BAD"))
+ {
+ g_set_error (error,
+ GOA_ERROR,
+ GOA_ERROR_FAILED,
+ "BAD response to `%s': %s",
+ command,
+ response->str + tag_len + 4);
+ goto out;
+ }
+ ret = g_strdup (response->str + tag_len);
+ /* TODO: return additional response? */
+ goto out;
+ }
+ else if (g_str_has_prefix (response->str, "*"))
+ {
+ /* untagged */
+ handle_untagged_response (client, response->str);
+ }
+ else
+ {
+ /* otherwise other unhandled response */
+ g_debug ("unhandled response `%s'", response->str);
+ }
+
+ /* If idling, when we receive real data, put the DONE continuation
+ * string so the IDLE command will terminate
+ */
+ if (is_idle_command && !g_str_has_prefix (response->str, "+") && !idle_has_sent_done)
+ {
+ idle_has_sent_done = TRUE;
+ if (!g_data_output_stream_put_string (client->dos, "DONE\r\n", cancellable, error))
+ {
+ g_prefix_error (error, "Error putting IDLE continuation string: ");
+ goto out;
+ }
+ }
+
+ /* reset */
+ g_string_set_size (response, 0);
+ goto again;
+
+ out:
+ if (response != NULL)
+ g_string_free (response, TRUE);
+ g_free (tag);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * goa_backend_imap_client_get_num_unread:
+ * @client: A #GoaBackendImapClient.
+ *
+ * The amount of unread messages in the mailbox.
+ *
+ * Returns: The number of unread messages or -1 if the connection has been closed.
+ */
+gint
+goa_backend_imap_client_get_num_unread (GoaBackendImapClient *client)
+{
+ g_return_val_if_fail (GOA_IS_BACKEND_IMAP_CLIENT (client), -1);
+ if (client->closed)
+ return -1;
+ else
+ return client->unseen;
+}
+
+/**
+ * goa_backend_imap_client_get_num_messages:
+ * @client: A #GoaBackendImapClient.
+ *
+ * The amount of messages in the mailbox.
+ *
+ * Returns: The number of messages or -1 if the connection has been closed.
+ */
+gint
+goa_backend_imap_client_get_num_messages (GoaBackendImapClient *client)
+{
+ g_return_val_if_fail (GOA_IS_BACKEND_IMAP_CLIENT (client), -1);
+ if (client->closed)
+ return -1;
+ else
+ return client->exists;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * goa_backend_imap_client_get_messages:
+ * @client: A #GoaBackendImapClient.
+ *
+ * Gets all messages in the mailbox matching the
+ * #GoaBackendImapClient:criteria, #GoaBackendImapClient:query-offset
+ * and #GoaBackendImapClient:query-size properties as described in the
+ * documentation for goa_backend_imap_client_new_sync().
+ *
+ * Returns: (transfer full) (element-type GoaBackendImapMessage): A
+ * list of messages that should be freed with g_list_free() after each
+ * element has been freed with goa_imap_message_unref().
+ */
+GList *
+goa_backend_imap_client_get_messages (GoaBackendImapClient *client)
+{
+ GList *ret;
+
+ g_return_val_if_fail (GOA_IS_BACKEND_IMAP_CLIENT (client), NULL);
+
+ G_LOCK (messages_lock);
+ ret = g_list_copy (client->messages);
+ g_list_foreach (client->messages, (GFunc) goa_backend_imap_message_ref, NULL);
+ G_UNLOCK (messages_lock);
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * goa_backend_imap_client_refresh_sync:
+ * @client: A #GoaBackendImapClient.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Forcibly refreshes the messages by doing a server roundtrip. The
+ * calling thread is blocked while this is happening.
+ *
+ * Returns: %TRUE if the operation succeeded, %FALSE if @error is set.
+ */
+gboolean
+goa_backend_imap_client_refresh_sync (GoaBackendImapClient *client,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret;
+ gint num_completed_fetches_before;
+
+ g_return_val_if_fail (GOA_IS_BACKEND_IMAP_CLIENT (client), FALSE);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ g_object_ref (client);
+
+ ret = FALSE;
+
+ if (client->closed)
+ {
+ g_set_error (error,
+ GOA_ERROR,
+ GOA_ERROR_FAILED,
+ "Client is not running");
+ goto out;
+ }
+
+ g_mutex_lock (client->num_completed_fetches_mutex);
+ num_completed_fetches_before = client->num_completed_fetches;
+ g_cancellable_cancel (client->worker_cancellable);
+ while (client->num_completed_fetches < num_completed_fetches_before + 1)
+ g_cond_wait (client->num_completed_fetches_cond, client->num_completed_fetches_mutex);
+ if (client->num_completed_fetches == G_MAXINT)
+ {
+ g_set_error (error,
+ GOA_ERROR,
+ GOA_ERROR_FAILED,
+ "Client stopped while refreshing");
+ }
+ else
+ {
+ ret = TRUE;
+ }
+ //g_debug ("yay client->num_completed_fetches=%d", client->num_completed_fetches);
+ g_mutex_unlock (client->num_completed_fetches_mutex);
+
+ out:
+ g_object_unref (client);
+ return ret;
+}
+
+static void
+refresh_in_thread_func (GSimpleAsyncResult *res,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ GError *error;
+ error = NULL;
+ if (!goa_backend_imap_client_refresh_sync (GOA_BACKEND_IMAP_CLIENT (object),
+ cancellable,
+ &error))
+ g_simple_async_result_take_error (res, error);
+}
+
+/**
+ * goa_backend_imap_client_refresh:
+ * @client: A #GoaBackendImapClient.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: Function to call when the request has been satisfied.
+ * @user_data: Data to pass to @callback.
+ *
+ * Async version of goa_backend_imap_client_refresh_sync().
+ *
+ * When the result is ready, @callback will be called in the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link>. You can then use goa_backend_imap_client_refresh_finish()
+ * to get the result.
+ */
+void
+goa_backend_imap_client_refresh (GoaBackendImapClient *client,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_if_fail (GOA_IS_BACKEND_IMAP_CLIENT (client));
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+ simple = g_simple_async_result_new (G_OBJECT (client),
+ callback,
+ user_data,
+ goa_backend_imap_client_refresh);
+ g_simple_async_result_run_in_thread (simple,
+ refresh_in_thread_func,
+ G_PRIORITY_DEFAULT,
+ cancellable);
+ g_object_unref (simple);
+}
+
+/**
+ * goa_backend_imap_client_refresh_finish:
+ * @client: A #GoaBackendImapClient.
+ * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to goa_backend_imap_client_refresh().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with goa_backend_imap_client_refresh().
+ *
+ * Returns: %TRUE if the operation succeeded, %FALSE if @error is set.
+ */
+gboolean
+goa_backend_imap_client_refresh_finish (GoaBackendImapClient *client,
+ GAsyncResult *res,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+ gboolean ret;
+
+ ret = FALSE;
+
+ g_return_val_if_fail (GOA_IS_BACKEND_IMAP_CLIENT (client), FALSE);
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == goa_backend_imap_client_refresh);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ goto out;
+
+ ret = TRUE;
+
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+goa_backend_imap_client_do_login (GoaBackendImapClient *client,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret;
+ gchar *response;
+
+ g_return_val_if_fail (GOA_IS_BACKEND_IMAP_CLIENT (client), FALSE);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ ret = FALSE;
+
+ client->sc = g_socket_client_new ();
+ if (client->use_tls)
+ g_socket_client_set_tls (client->sc, TRUE);
+
+ /* TODO: TLS validation etc etc */
+
+ client->c = g_socket_client_connect_to_host (client->sc,
+ client->host_and_port,
+ client->use_tls ? 993 : 143,
+ cancellable,
+ error);
+ if (client->c == NULL)
+ goto out;
+
+ /* fail quickly */
+ g_socket_set_timeout (g_socket_connection_get_socket (client->c), 30);
+
+ client->dis = g_data_input_stream_new (g_io_stream_get_input_stream (G_IO_STREAM (client->c)));
+ client->dos = g_data_output_stream_new (g_io_stream_get_output_stream (G_IO_STREAM (client->c)));
+ g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (client->dis), FALSE);
+ g_filter_output_stream_set_close_base_stream (G_FILTER_OUTPUT_STREAM (client->dos), FALSE);
+ g_data_input_stream_set_newline_type (client->dis, G_DATA_STREAM_NEWLINE_TYPE_CR_LF);
+
+ /* Authenticate via the passed in auth helper */
+ if (!goa_backend_imap_auth_run_sync (client->auth,
+ client->dis,
+ client->dos,
+ cancellable,
+ error))
+ goto out;
+
+ /* Finally, select the mailbox
+ * TODO: make it possible to select other inboxes
+ */
+ response = goa_backend_imap_client_run_command_sync (client,
+ "SELECT INBOX",
+ cancellable,
+ error);
+ if (response == NULL)
+ goto out;
+ g_free (response);
+
+ ret = TRUE;
+
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+goa_backend_imap_client_do_refresh (GoaBackendImapClient *client,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gchar *response;
+ gboolean ret;
+ GString *request_str;
+ guint num_fetches;
+
+ ret = FALSE;
+
+ /* fail quickly */
+ g_socket_set_timeout (g_socket_connection_get_socket (client->c), 30);
+
+ /* First get an overall status */
+ response = goa_backend_imap_client_run_command_sync (client,
+ "STATUS INBOX (MESSAGES UNSEEN UIDVALIDITY)",
+ cancellable,
+ error);
+ if (response == NULL)
+ goto out;
+ g_free (response);
+
+ if (client->exists == 0)
+ {
+ ret = TRUE; /* that was easy */
+ goto out;
+ }
+
+ num_fetches = 0;
+ /* Calculate FETCH string */
+ request_str = g_string_new ("FETCH ");
+ if (g_strcmp0 (client->criteria, "") == 0)
+ {
+ gint message_seqnum_high;
+ gint message_seqnum_low;
+
+ /* No search criteria => get latest messages */
+
+ /* TODO: use client->query_offset */
+ message_seqnum_high = client->exists;
+ message_seqnum_low = client->exists - client->query_size + 1;
+ if (message_seqnum_low < 1)
+ message_seqnum_low = 1;
+ g_string_append_printf (request_str,
+ "%d:%d",
+ message_seqnum_low,
+ message_seqnum_high);
+ num_fetches = message_seqnum_high - message_seqnum_low + 1;
+ }
+ else
+ {
+ guint n;
+ if (client->search_result != NULL)
+ {
+ client->search_result = NULL;
+ g_free (client->search_result);
+ }
+ response = goa_backend_imap_client_run_command_sync (client,
+ "SEARCH UNSEEN",
+ cancellable,
+ error);
+ if (response == NULL)
+ goto out;
+ g_free (response);
+ for (n = client->query_offset; n < client->num_search_result && n < client->query_size; n++)
+ {
+ /* TODO: maybe compress into ranges - not sure it matters
+ * as the query is really small
+ */
+ if (n > 0)
+ g_string_append_c (request_str, ',');
+ g_string_append_printf (request_str, "%d", client->search_result[n]);
+ num_fetches++;
+ }
+ }
+
+ /* build up messages */
+ g_assert (client->messages_buildup == NULL);
+ if (num_fetches > 0)
+ {
+ g_string_append (request_str,
+ " (FLAGS INTERNALDATE UID X-GM-THRID BODY.PEEK[HEADER.FIELDS (Date From To Cc Subject)] BODY.PEEK[TEXT]<0.200>)");
+ response = goa_backend_imap_client_run_command_sync (client,
+ request_str->str,
+ cancellable,
+ error);
+ g_string_free (request_str, TRUE);
+ if (response == NULL)
+ goto out;
+ g_free (response);
+ }
+ else
+ {
+ g_string_free (request_str, FALSE);
+ }
+
+ /* Sort resulting messages by seqnum */
+ client->messages_buildup = g_list_sort (client->messages_buildup,
+ (GCompareFunc) goa_backend_imap_message_compare_seqnum_reverse);
+ /* replace messages */
+ G_LOCK (messages_lock);
+ g_list_foreach (client->messages, (GFunc) goa_backend_imap_message_unref, NULL);
+ g_list_free (client->messages);
+ client->messages = client->messages_buildup;
+ G_UNLOCK (messages_lock);
+ client->messages_buildup = NULL;
+
+ ret = TRUE;
+
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* runs in the thread that runs the default GMainContext */
+static gboolean
+refresh_while_idling_cb (gpointer user_data)
+{
+ GoaBackendImapClient *client = GOA_BACKEND_IMAP_CLIENT (user_data);
+ goa_backend_imap_client_refresh (client,
+ NULL, /* GCancellable */
+ NULL, /* GAsyncReadyCallback */
+ NULL); /* user_data */
+ return FALSE;
+}
+
+static gboolean
+goa_backend_imap_client_do_idle (GoaBackendImapClient *client,
+ GError **error)
+{
+ GError *local_error;
+ gboolean ret;
+ gchar *response;
+ guint refresh_timeout_id;
+
+ ret = FALSE;
+
+ /* OK, sit around and wait until the mailbox changes (e.g. new mail arriving)... For
+ * we use the IMAP IDLE command, see http://tools.ietf.org/html/rfc2177, if available
+ *
+ * (TODO: handle IDLE not being available)
+ *
+ * Note that this operation can be interrupted by another thread
+ * cancelling
+ *
+ * client->worker_cancellable
+ *
+ * If this happens then we reset the cancellable (so it can be cancelled
+ * again) and then our caller can do another loop (e.g. SEARCH, FETCH all
+ * messages of interest), increasing client->num_completed_fetches (and waking up
+ * waiters) when done.
+ */
+ local_error = NULL;
+ g_cancellable_reset (client->worker_cancellable);
+
+ /* We want a nice long timeout here to conserve battery power so
+ * set socket timeout to infinite and schedule our own refresh.
+ *
+ * Note that IMAP IDLE suggests that we should not sit and wait
+ * too long so we pick 25 minutes here.
+ */
+ g_socket_set_timeout (g_socket_connection_get_socket (client->c), 0);
+
+ /* TODO: hmm.. safe to use the default GMainContext? The only alternative
+ * is to create our own thread
+ */
+ refresh_timeout_id = g_timeout_add (25 * 60 * 1000,
+ refresh_while_idling_cb,
+ client);
+ response = goa_backend_imap_client_run_command_sync (client,
+ "IDLE",
+ client->worker_cancellable,
+ &local_error);
+ g_source_remove (refresh_timeout_id);
+ if (response == NULL)
+ {
+ if ((local_error->domain == G_IO_ERROR && local_error->code == G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (local_error);
+ }
+ else if ((local_error->domain == G_IO_ERROR && local_error->code == G_IO_ERROR_TIMED_OUT))
+ {
+ g_error_free (local_error);
+ }
+ else
+ {
+ g_propagate_error (error, local_error);
+ goto out;
+ }
+ }
+ else
+ {
+ g_free (response);
+ }
+
+ ret = TRUE;
+
+ out:
+ return ret;
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* worker thread */
+static gpointer
+goa_backend_imap_client_worker_thread_func (gpointer user_data)
+{
+ GoaBackendImapClient *client = GOA_BACKEND_IMAP_CLIENT (user_data);
+ GError *error;
+
+ /* This is the main loop where we idle, then refresh, then idle,
+ * then refresh again and around and around she goes...
+ */
+ while (TRUE)
+ {
+ //g_debug ("idling in worker thread");
+ error = NULL;
+ if (!goa_backend_imap_client_do_idle (client, &error))
+ goto out;
+
+ /* exit worker thread if being asked to close */
+ if (client->closed)
+ {
+ error = NULL;
+ goto out;
+ }
+
+ //g_debug ("refreshing in worker thread");
+ error = NULL;
+ if (!goa_backend_imap_client_do_refresh (client, NULL, &error))
+ goto out;
+
+ /* Done with a loop, notify threads waiting on us (if any) */
+ //g_debug ("notifying waiters");
+ g_mutex_lock (client->num_completed_fetches_mutex);
+ client->num_completed_fetches += 1;
+ g_cond_broadcast (client->num_completed_fetches_cond);
+ g_mutex_unlock (client->num_completed_fetches_mutex);
+
+ /* let the user know that we completed a loop and we're now idling */
+ //g_debug ("emitting ::updated signal");
+ emit_updated_in_idle (client);
+ }
+
+ out:
+ //g_debug ("worker done");
+ emit_closed_in_idle (client, error);
+ if (error != NULL)
+ g_error_free (error);
+ g_object_unref (client);
+ return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * goa_backend_imap_client_get_closed:
+ * @client: A #GoaBackendImapClient.
+ *
+ * Gets if @client is closed.
+ *
+ * Returns: %TRUE if the connection to the IMAP server has been closed, %FALSE otherwise.
+ */
+gboolean
+goa_backend_imap_client_get_closed (GoaBackendImapClient *client)
+{
+ g_return_val_if_fail (GOA_IS_BACKEND_IMAP_CLIENT (client), FALSE);
+ return client->closed;
+}
+
+/**
+ * goa_backend_imap_client_close:
+ * @client: A #GoaBackendImapClient.
+ *
+ * Closes the connection used by @client.
+ */
+void
+goa_backend_imap_client_close (GoaBackendImapClient *client)
+{
+ g_return_if_fail (GOA_IS_BACKEND_IMAP_CLIENT (client));
+ client->closed = TRUE;
+ g_cancellable_cancel (client->worker_cancellable);
+ /* TODO: could join on client->worker_thread ... */
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+goa_backend_imap_client_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GoaBackendImapClient *client = GOA_BACKEND_IMAP_CLIENT (initable);
+ gboolean ret;
+
+ ret = FALSE;
+
+ if (!goa_backend_imap_client_do_login (client, cancellable, error))
+ goto out;
+
+ if (!goa_backend_imap_client_do_refresh (client, cancellable, error))
+ goto out;
+
+ /* OK, made it this far - create a worker thread for idling and refreshing */
+ client->worker_thread = g_thread_create (goa_backend_imap_client_worker_thread_func,
+ g_object_ref (client),
+ TRUE, /* joinable */
+ error);
+ if (client->worker_thread == NULL)
+ goto out;
+
+ ret = TRUE;
+
+ out:
+ return ret;
+}
+
+static void
+goa_backend_imap_client__g_initable_iface_init (GInitableIface *iface)
+{
+ iface->init = goa_backend_imap_client_initable_init;
+}
+
+static void
+goa_backend_imap_client__g_async_initable_iface_init (GAsyncInitableIface *iface)
+{
+ /* use default implementation that runs the GInitable code in a thread */
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/src/goa/goabackendimapclient.h b/src/goa/goabackendimapclient.h
new file mode 100644
index 0000000..45000f9
--- /dev/null
+++ b/src/goa/goabackendimapclient.h
@@ -0,0 +1,80 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 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: David Zeuthen <davidz@redhat.com>
+ */
+
+#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION)
+#error "Only <goa/goabackend.h> can be included directly."
+#endif
+
+#ifndef __GOA_BACKEND_IMAP_CLIENT_H__
+#define __GOA_BACKEND_IMAP_CLIENT_H__
+
+#include <goa/goabackendtypes.h>
+
+G_BEGIN_DECLS
+
+#define GOA_TYPE_BACKEND_IMAP_CLIENT (goa_backend_imap_client_get_type ())
+#define GOA_BACKEND_IMAP_CLIENT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_BACKEND_IMAP_CLIENT, GoaBackendImapClient))
+#define GOA_IS_BACKEND_IMAP_CLIENT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_BACKEND_IMAP_CLIENT))
+
+GType goa_backend_imap_client_get_type (void) G_GNUC_CONST;
+void goa_backend_imap_client_new (const gchar *host_and_port,
+ gboolean use_tls,
+ GoaBackendImapAuth *auth,
+ const gchar *criteria,
+ guint query_offset,
+ guint query_size,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GoaBackendImapClient *goa_backend_imap_client_new_finish (GAsyncResult *res,
+ GError **error);
+GoaBackendImapClient *goa_backend_imap_client_new_sync (const gchar *host_and_port,
+ gboolean use_tls,
+ GoaBackendImapAuth *auth,
+ const gchar *criteria,
+ guint query_offset,
+ guint query_size,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean goa_backend_imap_client_get_closed (GoaBackendImapClient *client);
+void goa_backend_imap_client_close (GoaBackendImapClient *client);
+
+gint goa_backend_imap_client_get_num_unread (GoaBackendImapClient *client);
+gint goa_backend_imap_client_get_num_messages (GoaBackendImapClient *client);
+GList *goa_backend_imap_client_get_messages (GoaBackendImapClient *client);
+
+void goa_backend_imap_client_refresh (GoaBackendImapClient *client,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean goa_backend_imap_client_refresh_finish (GoaBackendImapClient *client,
+ GAsyncResult *res,
+ GError **error);
+gboolean goa_backend_imap_client_refresh_sync (GoaBackendImapClient *client,
+ GCancellable *cancellable,
+ GError **error);
+
+
+G_END_DECLS
+
+#endif /* __GOA_BACKEND_IMAP_CLIENT_H__ */
diff --git a/src/goa/goabackendimapmail.c b/src/goa/goabackendimapmail.c
new file mode 100644
index 0000000..bf812e5
--- /dev/null
+++ b/src/goa/goabackendimapmail.c
@@ -0,0 +1,551 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 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: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#include <rest/oauth-proxy.h>
+#include <json-glib/json-glib.h>
+
+#include "goabackendimapauth.h"
+#include "goabackendimapclient.h"
+#include "goabackendimapmessage.h"
+#include "goabackendimapmail.h"
+
+/**
+ * GoaBackendImapMail:
+ *
+ * The #GoaBackendImapMail structure contains only private data and should
+ * only be accessed using the provided API.
+ */
+struct _GoaBackendImapMail
+{
+ /*< private >*/
+ GoaMailSkeleton parent_instance;
+
+ gchar *host_and_port;
+ gboolean use_tls;
+ GoaBackendImapAuth *auth;
+};
+
+typedef struct _GoaBackendImapMailClass GoaBackendImapMailClass;
+
+struct _GoaBackendImapMailClass
+{
+ GoaMailSkeletonClass parent_class;
+};
+
+enum
+{
+ PROP_0,
+ PROP_HOST_AND_PORT,
+ PROP_USE_TLS,
+ PROP_AUTH
+};
+
+/**
+ * SECTION:goabackendimapmail
+ * @title: GoaBackendImapMail
+ * @short_description: Implementation of the #GoaMail interface for IMAP servers
+ *
+ * #GoaBackendImapMail is an implementation of the #GoaMail D-Bus
+ * interface that uses a #GoaBackendImapClient instance to speak to a
+ * remote IMAP server.
+ */
+
+static void goa_backend_imap_mail__goa_mail_iface_init (GoaMailIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GoaBackendImapMail, goa_backend_imap_mail, GOA_TYPE_MAIL_SKELETON,
+ G_IMPLEMENT_INTERFACE (GOA_TYPE_MAIL, goa_backend_imap_mail__goa_mail_iface_init));
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+goa_backend_imap_mail_finalize (GObject *object)
+{
+ GoaBackendImapMail *mail = GOA_BACKEND_IMAP_MAIL (object);
+
+ g_free (mail->host_and_port);
+ g_object_unref (mail->auth);
+
+ G_OBJECT_CLASS (goa_backend_imap_mail_parent_class)->finalize (object);
+}
+
+static void
+goa_backend_imap_mail_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GoaBackendImapMail *mail = GOA_BACKEND_IMAP_MAIL (object);
+
+ switch (prop_id)
+ {
+ case PROP_HOST_AND_PORT:
+ g_value_set_string (value, mail->host_and_port);
+ break;
+
+ case PROP_USE_TLS:
+ g_value_set_boolean (value, mail->use_tls);
+ break;
+
+ case PROP_AUTH:
+ g_value_set_object (value, mail->auth);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+goa_backend_imap_mail_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GoaBackendImapMail *mail = GOA_BACKEND_IMAP_MAIL (object);
+
+ switch (prop_id)
+ {
+ case PROP_HOST_AND_PORT:
+ mail->host_and_port = g_value_dup_string (value);
+ break;
+
+ case PROP_USE_TLS:
+ mail->use_tls = g_value_get_boolean (value);
+ break;
+
+ case PROP_AUTH:
+ mail->auth = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+goa_backend_imap_mail_init (GoaBackendImapMail *mail)
+{
+ /* Ensure D-Bus method invocations run in their own thread */
+ g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (mail),
+ G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD);
+}
+
+static void
+goa_backend_imap_mail_class_init (GoaBackendImapMailClass *klass)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->finalize = goa_backend_imap_mail_finalize;
+ gobject_class->set_property = goa_backend_imap_mail_set_property;
+ gobject_class->get_property = goa_backend_imap_mail_get_property;
+
+ g_object_class_install_property (gobject_class,
+ PROP_HOST_AND_PORT,
+ g_param_spec_string ("host-and-port",
+ "host-and-port",
+ "host-and-port",
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_USE_TLS,
+ g_param_spec_boolean ("use-tls",
+ "use-tls",
+ "use-tls",
+ TRUE,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_AUTH,
+ g_param_spec_object ("auth",
+ "auth",
+ "auth",
+ GOA_TYPE_BACKEND_IMAP_AUTH,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * goa_backend_imap_mail_new:
+ * @host_and_port: The name and optionally port to connect to.
+ * @use_tls: Whether TLS should be used.
+ * @auth: Object used for authenticating the connection.
+ *
+ * Creates a new #GoaMail object.
+ *
+ * Returns: (type GoaBackendImapMail): A new #GoaMail instance.
+ */
+GoaMail *
+goa_backend_imap_mail_new (const gchar *host_and_port,
+ gboolean use_tls,
+ GoaBackendImapAuth *auth)
+{
+ g_return_val_if_fail (host_and_port != NULL, NULL);
+ return GOA_MAIL (g_object_new (GOA_TYPE_BACKEND_IMAP_MAIL,
+ "host-and-port", host_and_port,
+ "use-tls", use_tls,
+ "auth", auth,
+ NULL));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void on_imap_client_updated (GoaBackendImapClient *client,
+ gpointer user_data);
+
+static void on_imap_client_closed (GoaBackendImapClient *client,
+ gpointer user_data);
+
+typedef struct
+{
+ volatile gint ref_count;
+
+ GoaBackendImapMail *mail;
+ GoaMailQuery *query;
+ GCancellable *cancellable;
+ GoaObject *object;
+ GoaBackendImapClient *imap_client;
+
+ guint name_watcher_id;
+} QueryData;
+
+#if 0
+static QueryData *
+query_data_ref (QueryData *data)
+{
+ g_atomic_int_inc (&data->ref_count);
+ return data;
+}
+#endif
+
+static void
+query_data_unref (QueryData *data)
+{
+ if (g_atomic_int_dec_and_test (&data->ref_count))
+ {
+ if (data->name_watcher_id)
+ g_bus_unwatch_name (data->name_watcher_id);
+ g_object_unref (data->mail);
+ g_object_unref (data->query);
+ g_object_unref (data->cancellable);
+ if (data->imap_client != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (data->imap_client, G_CALLBACK (on_imap_client_updated), data);
+ g_signal_handlers_disconnect_by_func (data->imap_client, G_CALLBACK (on_imap_client_closed), data);
+ g_object_unref (data->imap_client);
+ }
+ g_slice_free (QueryData, data);
+ }
+}
+
+static void
+nuke_query (QueryData *data)
+{
+ /* yippee ki yay motherfucker */
+
+ /* unexport the D-Bus object */
+ g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (data->query));
+ /* shutdown the IMAP client */
+ if (data->imap_client != NULL)
+ goa_backend_imap_client_close (data->imap_client);
+ query_data_unref (data);
+}
+
+static void
+on_query_owner_vanished (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ QueryData *data = user_data;
+ nuke_query (data);
+}
+
+static gboolean
+mail_query_create_imap_client_sync (QueryData *data,
+ GError **error);
+
+/* runs in thread dedicated to the method invocation */
+static gboolean
+mail_query_on_handle_close (GoaMailQuery *query,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ QueryData *data = user_data;
+ nuke_query (data);
+ return TRUE;
+}
+
+/* runs in thread dedicated to the method invocation */
+static gboolean
+mail_query_on_handle_refresh (GoaMailQuery *query,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ QueryData *data = user_data;
+ GError *error;
+
+ if (data->imap_client == NULL)
+ {
+ error = NULL;
+ if (!mail_query_create_imap_client_sync (data, &error))
+ {
+ g_prefix_error (&error, "Error creating IMAP client: ");
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ g_error_free (error);
+ goto out;
+ }
+ goa_mail_query_complete_refresh (query, invocation);
+ goto out;
+ }
+
+ error = NULL;
+ if (!goa_backend_imap_client_refresh_sync (data->imap_client,
+ NULL, /* GCancellable */
+ &error))
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ g_error_free (error);
+ }
+ else
+ {
+ goa_mail_query_complete_refresh (query, invocation);
+ }
+ out:
+ return TRUE;
+}
+
+
+static void
+on_imap_client_closed (GoaBackendImapClient *client,
+ gpointer user_data)
+{
+ QueryData *data = user_data;
+
+ //g_debug ("##### on_imap_client_closed");
+
+ goa_mail_query_set_connected (data->query, FALSE);
+ g_signal_handlers_disconnect_by_func (data->imap_client, G_CALLBACK (on_imap_client_updated), data);
+ g_signal_handlers_disconnect_by_func (data->imap_client, G_CALLBACK (on_imap_client_closed), data);
+ g_object_unref (data->imap_client);
+ data->imap_client = NULL;
+}
+
+static void
+on_imap_client_updated (GoaBackendImapClient *client,
+ gpointer user_data)
+{
+ QueryData *data = user_data;
+ GList *messages;
+ GList *l;
+ GVariantBuilder builder;
+
+ //g_debug ("##### on_imap_client_updated");
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(stsssia{sv})"));
+
+ messages = goa_backend_imap_client_get_messages (data->imap_client);
+ for (l = messages; l != NULL; l = l->next)
+ {
+ GoaBackendImapMessage *message = l->data;
+ gchar guid[20];
+ const gchar *from;
+ const gchar *subject;
+ GoaBackendImapMessageFlags imap_flags;
+ gint flags;
+ GVariantBuilder extras_builder;
+
+ from = goa_backend_imap_message_lookup_header (message, "From");
+ subject = goa_backend_imap_message_lookup_header (message, "Subject");
+ imap_flags = goa_backend_imap_message_get_flags (message);
+
+ flags = 0;
+ if (!(imap_flags & GOA_BACKEND_IMAP_MESSAGE_FLAGS_SEEN))
+ flags |= 1;
+
+ g_variant_builder_init (&extras_builder, G_VARIANT_TYPE_VARDICT);
+
+ g_snprintf (guid, sizeof guid, "%" G_GUINT64_FORMAT,
+ goa_backend_imap_message_get_uid (message));
+
+ g_variant_builder_add (&builder, "(stsssia{sv})",
+ guid,
+ goa_backend_imap_message_get_internal_date (message),
+ from != NULL ? from : _("No Sender"),
+ subject != NULL ? subject : _("No Subject"),
+ goa_backend_imap_message_get_excerpt (message),
+ flags,
+ &extras_builder);
+ }
+ goa_mail_query_set_result (data->query, g_variant_builder_end (&builder));
+ goa_mail_query_set_num_unread (data->query, goa_backend_imap_client_get_num_unread (data->imap_client));
+ goa_mail_query_set_num_messages (data->query, goa_backend_imap_client_get_num_messages (data->imap_client));
+ g_dbus_interface_skeleton_flush (G_DBUS_INTERFACE_SKELETON (data->query));
+
+ g_list_foreach (messages, (GFunc) goa_backend_imap_message_ref, NULL);
+ g_list_free (messages);
+}
+
+static gboolean
+mail_query_create_imap_client_sync (QueryData *data,
+ GError **error)
+{
+ gboolean ret;
+
+ g_return_val_if_fail (data->imap_client == NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ ret = FALSE;
+
+ /* gets ourselves an IMAP client - it's not running yet */
+ data->imap_client = goa_backend_imap_client_new_sync (data->mail->host_and_port,
+ data->mail->use_tls,
+ data->mail->auth,
+ goa_mail_query_get_criteria (data->query),
+ 0, /* offset */
+ goa_mail_query_get_max_size (data->query),
+ NULL, /* GCancellable */
+ error);
+ if (data->imap_client == NULL)
+ goto out;
+
+ /* initial update */
+ on_imap_client_updated (data->imap_client, data);
+ goa_mail_query_set_connected (data->query, TRUE);
+
+ /* subsequent updates */
+ g_signal_connect (data->imap_client,
+ "updated",
+ G_CALLBACK (on_imap_client_updated),
+ data);
+
+ g_signal_connect (data->imap_client,
+ "closed",
+ G_CALLBACK (on_imap_client_closed),
+ data);
+
+ ret = TRUE;
+
+ out:
+ return ret;
+}
+
+/* runs in thread dedicated to the method invocation */
+static gboolean
+handle_create_query (GoaMail *_mail,
+ GDBusMethodInvocation *invocation,
+ const gchar *criteria,
+ gint max_size)
+{
+ GoaBackendImapMail *mail = GOA_BACKEND_IMAP_MAIL (_mail);
+ gchar *query_object_path;
+ GError *error;
+ QueryData *data;
+ static gint _g_query_count = 0;
+
+ query_object_path = NULL;
+
+ data = g_slice_new0 (QueryData);
+ data->ref_count = 1;
+ data->mail = g_object_ref (mail);
+ data->query = goa_mail_query_skeleton_new ();
+ data->cancellable = g_cancellable_new ();
+
+ goa_mail_query_set_criteria (data->query, criteria);
+ goa_mail_query_set_max_size (data->query, max_size);
+
+ /* Create the IMAP client - it's not fatal if the client is not
+ * working (could be the case if there is no network) since the user
+ * can call Refresh() to bring it up again.
+ */
+ error = NULL;
+ if (!mail_query_create_imap_client_sync (data, &error))
+ {
+ g_error_free (error);
+ }
+
+ g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (data->query),
+ G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD);
+ g_signal_connect (data->query,
+ "handle-refresh",
+ G_CALLBACK (mail_query_on_handle_refresh),
+ data);
+ g_signal_connect (data->query,
+ "handle-close",
+ G_CALLBACK (mail_query_on_handle_close),
+ data);
+
+ query_object_path = g_strdup_printf ("/org/gnome/OnlineAccounts/mail_queries/%d", _g_query_count++);
+
+ error = NULL;
+ if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (data->query),
+ g_dbus_method_invocation_get_connection (invocation),
+ query_object_path,
+ &error))
+ {
+ g_prefix_error (&error, "Error exporting mail query: ");
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ query_data_unref (data);
+ goto out;
+ }
+
+ data->name_watcher_id = g_bus_watch_name_on_connection (g_dbus_method_invocation_get_connection (invocation),
+ g_dbus_method_invocation_get_sender (invocation),
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ NULL, /* name_appeared_handler */
+ on_query_owner_vanished,
+ data,
+ NULL);
+
+ /* TODO: set up things so only caller can access the created object? */
+
+ goa_mail_complete_create_query (GOA_MAIL (mail), invocation, query_object_path);
+
+ out:
+ g_free (query_object_path);
+ return TRUE; /* invocation was handled */
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+goa_backend_imap_mail__goa_mail_iface_init (GoaMailIface *iface)
+{
+ iface->handle_create_query = handle_create_query;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/src/goa/goabackendimapmail.h b/src/goa/goabackendimapmail.h
new file mode 100644
index 0000000..2d87aaf
--- /dev/null
+++ b/src/goa/goabackendimapmail.h
@@ -0,0 +1,45 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 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: David Zeuthen <davidz@redhat.com>
+ */
+
+#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION)
+#error "Only <goa/goabackend.h> can be included directly."
+#endif
+
+#ifndef __GOA_BACKEND_IMAP_MAIL_H__
+#define __GOA_BACKEND_IMAP_MAIL_H__
+
+#include <goa/goabackendtypes.h>
+
+G_BEGIN_DECLS
+
+#define GOA_TYPE_BACKEND_IMAP_MAIL (goa_backend_imap_mail_get_type ())
+#define GOA_BACKEND_IMAP_MAIL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_BACKEND_IMAP_MAIL, GoaBackendImapMail))
+#define GOA_IS_BACKEND_IMAP_MAIL(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_BACKEND_IMAP_MAIL))
+
+GType goa_backend_imap_mail_get_type (void) G_GNUC_CONST;
+GoaMail *goa_backend_imap_mail_new (const gchar *host_and_port,
+ gboolean use_tls,
+ GoaBackendImapAuth *auth);
+
+G_END_DECLS
+
+#endif /* __GOA_BACKEND_IMAP_MAIL_H__ */
diff --git a/src/goa/goabackendimapmessage.c b/src/goa/goabackendimapmessage.c
new file mode 100644
index 0000000..10e710c
--- /dev/null
+++ b/src/goa/goabackendimapmessage.c
@@ -0,0 +1,263 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 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: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+#include <stdlib.h>
+
+#include "goabackendimapprivate.h"
+#include "goabackendimapmessage.h"
+
+G_LOCK_DEFINE_STATIC (message_lock);
+
+/**
+ * SECTION:goabackendimapmessage
+ * @title: GoaBackendImapMessage
+ * @short_description: Message stored on an IMAP server
+ *
+ * The #GoaBackendImapMessage type is a boxed type representing a
+ * message stored on an IMAP server. See the #GoaBackendImapClient
+ * type for more details.
+ */
+
+G_DEFINE_BOXED_TYPE (GoaBackendImapMessage, goa_backend_imap_message,
+ goa_backend_imap_message_ref,
+ goa_backend_imap_message_unref);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+GoaBackendImapMessageFlags
+goa_backend_imap_message_flags_from_strv (const gchar *const *strings)
+{
+ GoaBackendImapMessageFlags ret;
+ guint n;
+
+ g_return_val_if_fail (strings != NULL, GOA_BACKEND_IMAP_MESSAGE_FLAGS_NONE);
+
+ ret = GOA_BACKEND_IMAP_MESSAGE_FLAGS_NONE;
+ for (n = 0; strings[n] != NULL; n++)
+ {
+ if (g_strcmp0 (strings[n], "\\Seen") == 0)
+ ret |= GOA_BACKEND_IMAP_MESSAGE_FLAGS_SEEN;
+ else if (g_strcmp0 (strings[n], "\\Answered") == 0)
+ ret |= GOA_BACKEND_IMAP_MESSAGE_FLAGS_ANSWERED;
+ else if (g_strcmp0 (strings[n], "\\Flagged") == 0)
+ ret |= GOA_BACKEND_IMAP_MESSAGE_FLAGS_FLAGGED;
+ else if (g_strcmp0 (strings[n], "\\Deleted") == 0)
+ ret |= GOA_BACKEND_IMAP_MESSAGE_FLAGS_DELETED;
+ else if (g_strcmp0 (strings[n], "\\Draft") == 0)
+ ret |= GOA_BACKEND_IMAP_MESSAGE_FLAGS_DRAFT;
+ else if (g_strcmp0 (strings[n], "\\Recent") == 0)
+ ret |= GOA_BACKEND_IMAP_MESSAGE_FLAGS_RECENT;
+ else
+ g_debug ("TODO: unhandled flag `%s'", strings[n]);
+ }
+ return ret;
+}
+
+gint
+goa_backend_imap_message_compare_seqnum_reverse (const GoaBackendImapMessage *a,
+ const GoaBackendImapMessage *b)
+{
+ return b->seqnum - a->seqnum;
+}
+
+/**
+ * goa_backend_imap_message_get_flags:
+ * @message: A #GoaBackendImapMessage.
+ *
+ * Gets the flags for @message.
+ *
+ * Returns: Flags from the #GoaBackendImapMessageFlags enumeration.
+ */
+GoaBackendImapMessageFlags
+goa_backend_imap_message_get_flags (GoaBackendImapMessage *message)
+{
+ return message->flags;
+}
+
+/**
+ * goa_backend_imap_message_get_uid:
+ * @message: A #GoaBackendImapMessage.
+ *
+ * Gets the unique id for @message.
+ *
+ * This includes the <ulink
+ * url="http://tools.ietf.org/html/rfc3501#section-2.3.1.1">unique
+ * identifier validity value</ulink> in the upper 32 bits.
+ *
+ * Returns: Unique id.
+ */
+guint64
+goa_backend_imap_message_get_uid (GoaBackendImapMessage *message)
+{
+ return message->uid;
+}
+
+/**
+ * goa_backend_imap_message_get_internal_date:
+ * @message: A #GoaBackendImapMessage.
+ *
+ * Gets the <ulink
+ * url="http://tools.ietf.org/html/rfc3501#section-2.3.3">internal
+ * date</ulink> for @message.
+ *
+ * Returns: The internal date represented as seconds since the Epoch, Jan 1 1970 0:00 UTC.
+ */
+gint64
+goa_backend_imap_message_get_internal_date (GoaBackendImapMessage *message)
+{
+ return message->internal_date;
+}
+
+/**
+ * goa_backend_imap_message_get_headers:
+ * @message: A #GoaBackendImapMessage.
+ *
+ * Gets the subset of <ulink
+ * url="http://tools.ietf.org/html/rfc2822">RFC 2822</ulink> headers
+ * extracted with the message. See also
+ * goa_backend_imap_message_lookup_header().
+ *
+ * Returns: A string representing the headers.
+ */
+const gchar *
+goa_backend_imap_message_get_headers (GoaBackendImapMessage *message)
+{
+ return message->rfc822_headers;
+}
+
+/**
+ * goa_backend_imap_message_get_excerpt:
+ * @message: A #GoaBackendImapMessage.
+ *
+ * Gets an excerpt of the body of @message - the excerpt typically
+ * doesn't exceed 200 characters.
+ *
+ * Returns: An excerpt of the message body.
+ */
+const gchar *
+goa_backend_imap_message_get_excerpt (GoaBackendImapMessage *message)
+{
+ return message->excerpt;
+}
+
+static GHashTable *
+parse_rfc822_headers (const gchar *rfc822_headers)
+{
+ GHashTable *ret;
+ gchar **lines;
+ guint n;
+
+ ret = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+ lines = g_strsplit (rfc822_headers, "\r\n", -1);
+ for (n = 0; lines[n] != NULL; n++)
+ {
+ const gchar *line = lines[n];
+ const gchar *s;
+
+ if (line[0] == '\0')
+ continue;
+
+ s = strstr (line, ": ");
+ if (s != NULL)
+ {
+ gchar *key;
+ gchar *value;
+ key = g_strndup (line, s - line);
+ value = g_strdup (s + 2);
+ g_hash_table_insert (ret, key, value);
+ }
+ else
+ g_debug ("%s: mysterious line `%s'", G_STRFUNC, line);
+ }
+ g_strfreev (lines);
+
+ return ret;
+}
+
+/**
+ * goa_backend_imap_message_lookup_header:
+ * @message: A #GoaBackendImapMessage.
+ * @header: Header to lookup.
+ *
+ * Convenience function to lookup a header on @message.
+ *
+ * Returns: The value corresponding to @header or %NULL if not found.
+ */
+const gchar *
+goa_backend_imap_message_lookup_header (GoaBackendImapMessage *message,
+ const gchar *header)
+{
+ g_return_val_if_fail (header != NULL, NULL);
+
+ G_LOCK (message_lock);
+ if (message->headers_hash == NULL)
+ message->headers_hash = parse_rfc822_headers (message->rfc822_headers);
+ G_UNLOCK (message_lock);
+
+ return g_hash_table_lookup (message->headers_hash, header);
+}
+
+GoaBackendImapMessage *
+goa_backend_imap_message_new (void)
+{
+ GoaBackendImapMessage *message;
+ message = g_slice_new0 (GoaBackendImapMessage);
+ message->ref_count = 1;
+ return message;
+}
+
+/**
+ * goa_backend_imap_message_ref:
+ * @message: A #GoaBackendImapMessage.
+ *
+ * Increases the reference count of @message.
+ *
+ * Returns: @message.
+ */
+GoaBackendImapMessage *
+goa_backend_imap_message_ref (GoaBackendImapMessage *message)
+{
+ g_atomic_int_inc (&message->ref_count);
+ return message;
+}
+
+/**
+ * goa_backend_imap_message_unref:
+ * @message: A #GoaBackendImapMessage.
+ *
+ * Decreases the reference count of @message. When the reference count
+ * hits 0, the resources used by @message are freed.
+ */
+void
+goa_backend_imap_message_unref (GoaBackendImapMessage *message)
+{
+ if (g_atomic_int_dec_and_test (&message->ref_count))
+ {
+ g_free (message->excerpt);
+ g_free (message->rfc822_headers);
+ if (message->headers_hash != NULL)
+ g_hash_table_unref (message->headers_hash);
+ g_slice_free (GoaBackendImapMessage, message);
+ }
+}
diff --git a/src/goa/goabackendimapmessage.h b/src/goa/goabackendimapmessage.h
new file mode 100644
index 0000000..7e5648a
--- /dev/null
+++ b/src/goa/goabackendimapmessage.h
@@ -0,0 +1,49 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 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: David Zeuthen <davidz@redhat.com>
+ */
+
+#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION)
+#error "Only <goa/goabackend.h> can be included directly."
+#endif
+
+#ifndef __GOA_BACKEND_IMAP_MESSAGE_H__
+#define __GOA_BACKEND_IMAP_MESSAGE_H__
+
+#include <goa/goabackendtypes.h>
+
+G_BEGIN_DECLS
+
+#define GOA_TYPE_BACKEND_IMAP_MESSAGE (goa_backend_imap_message_get_type)
+
+GType goa_backend_imap_message_get_type (void) G_GNUC_CONST;
+GoaBackendImapMessage *goa_backend_imap_message_ref (GoaBackendImapMessage *message);
+void goa_backend_imap_message_unref (GoaBackendImapMessage *message);
+GoaBackendImapMessageFlags goa_backend_imap_message_get_flags (GoaBackendImapMessage *message);
+guint64 goa_backend_imap_message_get_uid (GoaBackendImapMessage *message);
+gint64 goa_backend_imap_message_get_internal_date (GoaBackendImapMessage *message);
+const gchar *goa_backend_imap_message_get_headers (GoaBackendImapMessage *message);
+const gchar *goa_backend_imap_message_get_excerpt (GoaBackendImapMessage *message);
+const gchar *goa_backend_imap_message_lookup_header (GoaBackendImapMessage *message,
+ const gchar *header);
+
+G_END_DECLS
+
+#endif /* __GOA_BACKEND_IMAP_MESSAGE_H__ */
diff --git a/src/goa/goabackendimapprivate.h b/src/goa/goabackendimapprivate.h
new file mode 100644
index 0000000..005fcd1
--- /dev/null
+++ b/src/goa/goabackendimapprivate.h
@@ -0,0 +1,63 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 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: David Zeuthen <davidz@redhat.com>
+ */
+
+#if !defined (GOA_BACKEND_COMPILATION)
+#error "This is a private header."
+#endif
+
+#ifndef __GOA_BACKEND_IMAP_PRIVATE_H__
+#define __GOA_BACKEND_IMAP_PRIVATE_H__
+
+#include <goa/goabackendtypes.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GoaBackendImapMessage:
+ *
+ * The #GoaBackendImapMessage structure contains only private data and
+ * should only be accessed using the provided API.
+ */
+struct _GoaBackendImapMessage
+{
+ volatile gint ref_count;
+ gint seqnum;
+ guint64 uid;
+ GoaBackendImapMessageFlags flags;
+ gint64 internal_date;
+ gchar *rfc822_headers;
+ gchar *excerpt;
+
+ /* calculated on-demand */
+ GHashTable *headers_hash;
+};
+
+GoaBackendImapMessage *goa_backend_imap_message_new (void);
+
+gint goa_backend_imap_message_compare_seqnum_reverse (const GoaBackendImapMessage *a,
+ const GoaBackendImapMessage *b);
+
+GoaBackendImapMessageFlags goa_backend_imap_message_flags_from_strv (const gchar *const *strings);
+
+G_END_DECLS
+
+#endif /* __GOA_BACKEND_IMAP_PRIVATE_H__ */
diff --git a/src/goa/goabackendoauthprovider.c b/src/goa/goabackendoauthprovider.c
index 2e66aab..a6ef272 100644
--- a/src/goa/goabackendoauthprovider.c
+++ b/src/goa/goabackendoauthprovider.c
@@ -1596,6 +1596,9 @@ goa_backend_oauth_provider_get_access_token_do_one (GetAccessTokenData *data)
* loop</link> this method was called from. You can then call
* goa_backend_oauth_provider_get_access_token_finish() to get the
* result of the operation.
+ *
+ * See goa_backend_oauth_provider_get_access_token_sync() for the
+ * synchronous, blocking version of this method.
*/
void
goa_backend_oauth_provider_get_access_token (GoaBackendOAuthProvider *provider,
@@ -1712,6 +1715,90 @@ goa_backend_oauth_provider_get_access_token_finish (GoaBackendOAuthProvider *p
/* ---------------------------------------------------------------------------------------------------- */
+typedef struct
+{
+ GAsyncResult *res;
+ GMainContext *context;
+ GMainLoop *loop;
+} GetAccessTokenSyncData;
+
+static void
+get_access_token_sync_cb (GoaBackendOAuthProvider *provider,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GetAccessTokenSyncData *data = user_data;
+ data->res = g_object_ref (res);
+ g_main_loop_quit (data->loop);
+}
+
+/**
+ * goa_backend_oauth_provider_get_access_token_sync:
+ * @provider: A #GoaBackendOAuthProvider.
+ * @object: A #GoaObject.
+ * @force_refresh: If set to %TRUE, forces a refresh of the access token, if possible.
+ * @out_access_token_secret: (out): The secret for the return access token.
+ * @out_access_token_expires_in: (out): Return location for how many seconds the returned token is valid for (0 if unknown) or %NULL.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously gets an access token for @object. The calling thread
+ * is blocked while the operation is pending.
+ *
+ * See goa_backend_oauth_provider_get_access_token() for the
+ * asynchronous non-blocking version and more details.
+ *
+ * Returns: The access token or %NULL if error is set. The returned
+ * string must be freed with g_free().
+ */
+gchar *
+goa_backend_oauth_provider_get_access_token_sync (GoaBackendOAuthProvider *provider,
+ GoaObject *object,
+ gboolean force_refresh,
+ gchar **out_access_token_secret,
+ gint *out_access_token_expires_in,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GetAccessTokenSyncData *data;
+ gchar *ret;
+
+ g_return_val_if_fail (GOA_IS_BACKEND_OAUTH_PROVIDER (provider), NULL);
+ g_return_val_if_fail (GOA_IS_OBJECT (object), NULL);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ data = g_new0 (GetAccessTokenSyncData, 1);
+ data->context = g_main_context_new ();
+ data->loop = g_main_loop_new (data->context, FALSE);
+
+ g_main_context_push_thread_default (data->context);
+
+ goa_backend_oauth_provider_get_access_token (provider,
+ object,
+ force_refresh,
+ cancellable,
+ (GAsyncReadyCallback) get_access_token_sync_cb,
+ data);
+ g_main_loop_run (data->loop);
+ ret = goa_backend_oauth_provider_get_access_token_finish (provider,
+ out_access_token_secret,
+ out_access_token_expires_in,
+ data->res,
+ error);
+
+ g_main_context_pop_thread_default (data->context);
+
+ g_main_context_unref (data->context);
+ g_main_loop_unref (data->loop);
+ g_object_unref (data->res);
+ g_free (data);
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
static gboolean on_handle_get_access_token (GoaOAuthBased *object,
GDBusMethodInvocation *invocation,
gpointer user_data);
diff --git a/src/goa/goabackendoauthprovider.h b/src/goa/goabackendoauthprovider.h
index 3fc4a01..22c00b9 100644
--- a/src/goa/goabackendoauthprovider.h
+++ b/src/goa/goabackendoauthprovider.h
@@ -28,6 +28,7 @@
#define __GOA_BACKEND_OAUTH_PROVIDER_H__
#include <goa/goabackendtypes.h>
+#include <goa/goabackendprovider.h>
G_BEGIN_DECLS
@@ -136,6 +137,13 @@ gchar *goa_backend_oauth_provider_get_access_token_finish (GoaBackendOAut
gint *out_access_token_expires_in,
GAsyncResult *res,
GError **error);
+gchar *goa_backend_oauth_provider_get_access_token_sync (GoaBackendOAuthProvider *provider,
+ GoaObject *object,
+ gboolean force_refresh,
+ gchar **out_access_token_secret,
+ gint *out_access_token_expires_in,
+ GCancellable *cancellable,
+ GError **error);
/* ---------------------------------------------------------------------------------------------------- */
diff --git a/src/goa/goabackendprovider.c b/src/goa/goabackendprovider.c
index 7b60515..02e3053 100644
--- a/src/goa/goabackendprovider.c
+++ b/src/goa/goabackendprovider.c
@@ -301,6 +301,79 @@ goa_backend_provider_ensure_credentials_finish (GoaBackendProvider *provider,
return GOA_BACKEND_PROVIDER_GET_CLASS (provider)->ensure_credentials_finish (provider, out_expires_in, res, error);
}
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GAsyncResult *res;
+ GMainContext *context;
+ GMainLoop *loop;
+} EnsureCredentialsSyncData;
+
+static void
+ensure_credentials_sync_cb (GoaBackendOAuthProvider *provider,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ EnsureCredentialsSyncData *data = user_data;
+ data->res = g_object_ref (res);
+ g_main_loop_quit (data->loop);
+}
+
+/**
+ * goa_backend_provider_ensure_credentials_sync:
+ * @provider: A #GoaBackendProvider.
+ * @object: A #GoaObject with a #GoaAccount interface.
+ * @out_expires_in: (out): Return location for how long the expired credentials are good for (0 if unknown) or %NULL.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Like goa_backend_provider_ensure_credentials() but blocks the
+ * calling thread until an answer is received.
+ *
+ * Returns: %TRUE if the credentials for the passed #GoaObject are valid, %FALSE if @error is set.
+ */
+gboolean
+goa_backend_provider_ensure_credentials_sync (GoaBackendProvider *provider,
+ GoaObject *object,
+ gint *out_expires_in,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EnsureCredentialsSyncData *data;
+ gboolean ret;
+
+ g_return_val_if_fail (GOA_IS_BACKEND_PROVIDER (provider), FALSE);
+ g_return_val_if_fail (GOA_IS_OBJECT (object), FALSE);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ data = g_new0 (EnsureCredentialsSyncData, 1);
+ data->context = g_main_context_new ();
+ data->loop = g_main_loop_new (data->context, FALSE);
+
+ g_main_context_push_thread_default (data->context);
+
+ goa_backend_provider_ensure_credentials (provider,
+ object,
+ cancellable,
+ (GAsyncReadyCallback) ensure_credentials_sync_cb,
+ data);
+ g_main_loop_run (data->loop);
+ ret = goa_backend_provider_ensure_credentials_finish (provider,
+ out_expires_in,
+ data->res,
+ error);
+
+ g_main_context_pop_thread_default (data->context);
+
+ g_main_context_unref (data->context);
+ g_main_loop_unref (data->loop);
+ g_object_unref (data->res);
+ g_free (data);
+ return ret;
+}
+
static void
goa_backend_provider_ensure_credentials_real (GoaBackendProvider *provider,
GoaObject *object,
diff --git a/src/goa/goabackendprovider.h b/src/goa/goabackendprovider.h
index d8902bf..955d311 100644
--- a/src/goa/goabackendprovider.h
+++ b/src/goa/goabackendprovider.h
@@ -90,7 +90,7 @@ struct _GoaBackendProviderClass
const gchar *group,
GError **error);
- /* virtual but with defaut implementation */
+ /* virtual but with default implementation */
void (*ensure_credentials) (GoaBackendProvider *provider,
GoaObject *object,
GCancellable *cancellable,
@@ -158,6 +158,12 @@ gboolean goa_backend_provider_ensure_credentials_finish (GoaBackendProvider *pr
GAsyncResult *res,
GError **error);
+gboolean goa_backend_provider_ensure_credentials_sync (GoaBackendProvider *provider,
+ GoaObject *object,
+ gint *out_expires_in,
+ GCancellable *cancellable,
+ GError **error);
+
/**
* GOA_BACKEND_PROVIDER_EXTENSION_POINT_NAME:
diff --git a/src/goa/goabackendtypes.h b/src/goa/goabackendtypes.h
index 4fba4ab..d94df65 100644
--- a/src/goa/goabackendtypes.h
+++ b/src/goa/goabackendtypes.h
@@ -28,6 +28,7 @@
#define __GOA_BACKEND_TYPES_H__
#include <goa/goa.h>
+#include <goa/goabackendenums.h>
#include <gtk/gtk.h>
G_BEGIN_DECLS
@@ -53,6 +54,21 @@ typedef struct _GoaBackendYahooProvider GoaBackendYahooProvider;
struct _GoaBackendTwitterProvider;
typedef struct _GoaBackendTwitterProvider GoaBackendTwitterProvider;
+struct _GoaBackendImapAuth;
+typedef struct _GoaBackendImapAuth GoaBackendImapAuth;
+
+struct _GoaBackendImapAuthOAuth;
+typedef struct _GoaBackendImapAuthOAuth GoaBackendImapAuthOAuth;
+
+struct _GoaBackendImapClient;
+typedef struct _GoaBackendImapClient GoaBackendImapClient;
+
+struct _GoaBackendImapMessage;
+typedef struct _GoaBackendImapMessage GoaBackendImapMessage;
+
+struct _GoaBackendImapMail;
+typedef struct _GoaBackendImapMail GoaBackendImapMail;
+
G_END_DECLS
#endif /* __GOA_BACKEND_TYPES_H__ */