[gnome-shell/datetime] Get events from Evolution



commit 41b0e0832e460a064cf9bf67ad31cf2065b6ae04
Author: David Zeuthen <davidz redhat com>
Date:   Tue Jan 25 15:42:10 2011 -0500

    Get events from Evolution
    
    This commit copies existing and deployed (thus, working!) GPLv2 code
    from gnome-panel into src/calendar-client. Please keep in sync.
    
    Signed-off-by: David Zeuthen <davidz redhat com>

 configure.ac                           |    8 +
 js/ui/calendar.js                      |   32 +-
 src/Makefile.am                        |    5 +-
 src/calendar-client/README             |    1 +
 src/calendar-client/calendar-client.c  | 2169 ++++++++++++++++++++++++++++++++
 src/calendar-client/calendar-client.h  |  149 +++
 src/calendar-client/calendar-debug.h   |   52 +
 src/calendar-client/calendar-sources.c |  658 ++++++++++
 src/calendar-client/calendar-sources.h |   66 +
 src/shell-evolution-event-source.c     |  157 +++-
 src/shell-evolution-event-source.h     |   17 +-
 11 files changed, 3281 insertions(+), 33 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index b3c0b24..f4469c0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -66,6 +66,10 @@ GJS_MIN_VERSION=0.7.8
 MUTTER_MIN_VERSION=2.91.4
 GTK_MIN_VERSION=2.91.7
 GIO_MIN_VERSION=2.25.9
+LIBECAL_REQUIRED=1.6.0
+LIBEDATASERVER_REQUIRED=1.2.0
+LIBEDATASERVERUI_REQUIRED=1.2.0
+
 
 # Collect more than 20 libraries for a prize!
 PKG_CHECK_MODULES(MUTTER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION
@@ -113,6 +117,10 @@ PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 2.90.0],
 	 AC_SUBST([HAVE_BLUETOOTH],[0])
 	 AC_MSG_RESULT([no])])
 
+PKG_CHECK_MODULES(LIBECAL, libecal-1.2 >= $LIBECAL_REQUIRED libedataserver-1.2 >= $LIBEDATASERVER_REQUIRED libedataserverui-1.2 >= $LIBEDATASERVERUI_REQUIRED)
+AC_SUBST(LIBECAL_CFLAGS)
+AC_SUBST(LIBECAL_LIBS)
+
 MUTTER_BIN_DIR=`$PKG_CONFIG --variable=exec_prefix mutter-plugins`/bin
 # FIXME: metacity-plugins.pc should point directly to its .gir file
 MUTTER_LIB_DIR=`$PKG_CONFIG --variable=libdir mutter-plugins`
diff --git a/js/ui/calendar.js b/js/ui/calendar.js
index d38e247..457d04e 100644
--- a/js/ui/calendar.js
+++ b/js/ui/calendar.js
@@ -175,6 +175,9 @@ EmptyEventSource.prototype = {
     _init: function() {
     },
 
+    requestRange: function(begin, end) {
+    },
+
     getEvents: function(begin, end) {
         let result = [];
         return result;
@@ -199,12 +202,16 @@ EvolutionEventSource.prototype = {
         }));
     },
 
+    requestRange: function(begin, end) {
+        this._native.request_range(begin.getTime(), end.getTime());
+    },
+
     getEvents: function(begin, end) {
         let result = [];
         let nativeEvents = this._native.get_events(begin.getTime(), end.getTime());
         for (let n = 0; n < nativeEvents.length; n++) {
             let nativeEvent = nativeEvents[n];
-            result.push(new CalendarEvent(new Date(nativeEvent.date), nativeEvent.summary, nativeEvent.all_day));
+            result.push(new CalendarEvent(new Date(nativeEvent.msec_begin), nativeEvent.summary, nativeEvent.all_day));
         }
         return result;
     },
@@ -296,6 +303,9 @@ FakeEventSource.prototype = {
         Mainloop.timeout_add(5000, Lang.bind(this, this._updateTransientEvent));
     },
 
+    requestRange: function(begin, end) {
+    },
+
     getEvents: function(begin, end) {
         let result = [];
         //log('begin:' + begin);
@@ -332,7 +342,7 @@ Signals.addSignalMethods(FakeEventSource.prototype);
 
 // Calendar:
 // @eventSource: is an object implementing the EventSource API, e.g. the
-// getEvents(), hasEvents() methods and the ::changed signal.
+// requestRange(), getEvents(), hasEvents() methods and the ::changed signal.
 function Calendar(eventSource) {
     this._init(eventSource);
 }
@@ -516,13 +526,14 @@ Calendar.prototype = {
             children[i].destroy();
 
         // Start at the beginning of the week before the start of the month
-        let iter = new Date(this.selectedDate);
-        iter.setDate(1);
-        iter.setSeconds(0);
-        iter.setHours(12);
-        let daysToWeekStart = (7 + iter.getDay() - this._weekStart) % 7;
-        iter.setTime(iter.getTime() - daysToWeekStart * MSECS_IN_DAY);
-
+        let beginDate = new Date(this.selectedDate);
+        beginDate.setDate(1);
+        beginDate.setSeconds(0);
+        beginDate.setHours(12);
+        let daysToWeekStart = (7 + beginDate.getDay() - this._weekStart) % 7;
+        beginDate.setTime(beginDate.getTime() - daysToWeekStart * MSECS_IN_DAY);
+
+        let iter = new Date(beginDate);
         let row = 2;
         while (true) {
             let button = new St.Button({ label: iter.getDate().toString() });
@@ -574,6 +585,9 @@ Calendar.prototype = {
                 row++;
             }
         }
+        // Signal to the event source that we are interested in events
+        // only from this date range
+        this._eventSource.requestRange(beginDate, iter);
     }
 };
 
diff --git a/src/Makefile.am b/src/Makefile.am
index f40febc..a7a38d8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -27,6 +27,7 @@ include Makefile-gdmuser.am
 include Makefile-st.am
 include Makefile-tray.am
 include Makefile-gvc.am
+include Makefile-calendar-client.am
 
 gnome_shell_cflags =				\
 	$(MUTTER_PLUGIN_CFLAGS)			\
@@ -214,7 +215,9 @@ libgnome_shell_la_LIBADD =	\
 	libst-1.0.la       	\
 	libgdmuser-1.0.la	\
 	libtray.la		\
-	libgvc.la
+	libgvc.la		\
+	libcalendar-client.la	\
+	$(NULL)
 
 libgnome_shell_la_CPPFLAGS = $(gnome_shell_cflags)
 
diff --git a/src/calendar-client/README b/src/calendar-client/README
new file mode 100644
index 0000000..ad9b5e3
--- /dev/null
+++ b/src/calendar-client/README
@@ -0,0 +1 @@
+Please keep in sync with gnome-panel.
diff --git a/src/calendar-client/calendar-client.c b/src/calendar-client/calendar-client.c
new file mode 100644
index 0000000..bbdb473
--- /dev/null
+++ b/src/calendar-client/calendar-client.c
@@ -0,0 +1,2169 @@
+/*
+ * Copyright (C) 2004 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Authors:
+ *     Mark McLoughlin  <mark skynet ie>
+ *     William Jon McCann  <mccann jhu edu>
+ *     Martin Grimme  <martin pycage de>
+ *     Christian Kellner  <gicmo xatom net>
+ */
+
+#include <config.h>
+
+#include "calendar-client.h"
+
+#include <libintl.h>
+#include <string.h>
+#define HANDLE_LIBICAL_MEMORY
+#include <libecal/e-cal.h>
+#include <libecal/e-cal-time-util.h>
+#include <libecal/e-cal-recur.h>
+
+#include "calendar-sources.h"
+
+#undef CALENDAR_ENABLE_DEBUG
+#include "calendar-debug.h"
+
+#define CALENDAR_CONFIG_PREFIX   "/apps/evolution/calendar"
+#define CALENDAR_CONFIG_TIMEZONE CALENDAR_CONFIG_PREFIX "/display/timezone"
+
+#ifndef _
+#define _(x) gettext(x)
+#endif
+
+#ifndef N_
+#define N_(x) x
+#endif
+
+#define CALENDAR_CLIENT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CALENDAR_TYPE_CLIENT, CalendarClientPrivate))
+
+typedef struct _CalendarClientQuery  CalendarClientQuery;
+typedef struct _CalendarClientSource CalendarClientSource;
+
+struct _CalendarClientQuery
+{
+  ECalView   *view;
+  GHashTable *events;
+};
+
+struct _CalendarClientSource
+{
+  CalendarClient      *client;
+  ECal                *source;
+
+  CalendarClientQuery  completed_query;
+  CalendarClientQuery  in_progress_query;
+
+  guint                changed_signal_id;
+
+  guint                query_completed : 1;
+  guint                query_in_progress : 1;
+};
+
+struct _CalendarClientPrivate
+{
+  CalendarSources     *calendar_sources;
+
+  GSList              *appointment_sources;
+  GSList              *task_sources;
+
+  icaltimezone        *zone;
+
+  guint                zone_listener;
+  GConfClient         *gconf_client;
+
+  guint                day;
+  guint                month;
+  guint                year;
+};
+
+static void calendar_client_class_init   (CalendarClientClass *klass);
+static void calendar_client_init         (CalendarClient      *client);
+static void calendar_client_finalize     (GObject             *object);
+static void calendar_client_set_property (GObject             *object,
+					  guint                prop_id,
+					  const GValue        *value,
+					  GParamSpec          *pspec);
+static void calendar_client_get_property (GObject             *object,
+					  guint                prop_id,
+					  GValue              *value,
+					  GParamSpec          *pspec);
+
+static GSList *calendar_client_update_sources_list         (CalendarClient       *client,
+							    GSList               *sources,
+							    GSList               *esources,
+							    guint                 changed_signal_id);
+static void    calendar_client_appointment_sources_changed (CalendarClient       *client);
+static void    calendar_client_task_sources_changed        (CalendarClient       *client);
+
+static void calendar_client_stop_query  (CalendarClient       *client,
+					 CalendarClientSource *source,
+					 CalendarClientQuery  *query);
+static void calendar_client_start_query (CalendarClient       *client,
+					 CalendarClientSource *source,
+					 const char           *query);
+
+static void calendar_client_source_finalize (CalendarClientSource *source);
+static void calendar_client_query_finalize  (CalendarClientQuery  *query);
+
+static void
+calendar_client_update_appointments (CalendarClient *client);
+static void
+calendar_client_update_tasks (CalendarClient *client);
+
+enum
+{
+  PROP_O,
+  PROP_DAY,
+  PROP_MONTH,
+  PROP_YEAR
+};
+
+enum
+{
+  APPOINTMENTS_CHANGED,
+  TASKS_CHANGED,
+  LAST_SIGNAL
+};
+
+static GObjectClass *parent_class = NULL;
+static guint         signals [LAST_SIGNAL] = { 0, };
+
+GType
+calendar_client_get_type (void)
+{
+  static GType client_type = 0;
+  
+  if (!client_type)
+    {
+      static const GTypeInfo client_info =
+      {
+	sizeof (CalendarClientClass),
+	NULL,		/* base_init */
+	NULL,		/* base_finalize */
+	(GClassInitFunc) calendar_client_class_init,
+	NULL,           /* class_finalize */
+	NULL,		/* class_data */
+	sizeof (CalendarClient),
+	0,		/* n_preallocs */
+	(GInstanceInitFunc) calendar_client_init,
+      };
+      
+      client_type = g_type_register_static (G_TYPE_OBJECT,
+					    "CalendarClient",
+					    &client_info, 0);
+    }
+  
+  return client_type;
+}
+
+static void
+calendar_client_class_init (CalendarClientClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+
+  parent_class = g_type_class_peek_parent (klass);
+
+  gobject_class->finalize     = calendar_client_finalize;
+  gobject_class->set_property = calendar_client_set_property;
+  gobject_class->get_property = calendar_client_get_property;
+
+  g_type_class_add_private (klass, sizeof (CalendarClientPrivate));
+
+  g_object_class_install_property (gobject_class,
+				   PROP_DAY,
+				   g_param_spec_uint ("day",
+						      "Day",
+						      "The currently monitored day between 1 and 31 (0 denotes unset)",
+						      0, G_MAXUINT, 0,
+						      G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class,
+				   PROP_MONTH,
+				   g_param_spec_uint ("month",
+						      "Month",
+						      "The currently monitored month between 0 and 11",
+						      0, G_MAXUINT, 0,
+						      G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class,
+				   PROP_YEAR,
+				   g_param_spec_uint ("year",
+						      "Year",
+						      "The currently monitored year",
+						      0, G_MAXUINT, 0,
+						      G_PARAM_READWRITE));
+
+  signals [APPOINTMENTS_CHANGED] =
+    g_signal_new ("appointments-changed",
+		  G_TYPE_FROM_CLASS (gobject_class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (CalendarClientClass, tasks_changed),
+		  NULL,
+		  NULL,
+		  g_cclosure_marshal_VOID__VOID,
+		  G_TYPE_NONE,
+		  0);
+
+  signals [TASKS_CHANGED] =
+    g_signal_new ("tasks-changed",
+		  G_TYPE_FROM_CLASS (gobject_class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (CalendarClientClass, tasks_changed),
+		  NULL,
+		  NULL,
+		  g_cclosure_marshal_VOID__VOID,
+		  G_TYPE_NONE,
+		  0);
+}
+
+/* Timezone code adapted from evolution/calendar/gui/calendar-config.c */
+/* The current timezone, e.g. "Europe/London". It may be NULL, in which case
+   you should assume UTC. */
+static gchar *
+calendar_client_config_get_timezone (GConfClient *gconf_client)
+{
+  char *location;
+
+  location = gconf_client_get_string (gconf_client,
+                                      CALENDAR_CONFIG_TIMEZONE,
+                                      NULL);
+
+  return location;
+}
+
+static icaltimezone *
+calendar_client_config_get_icaltimezone (GConfClient *gconf_client)
+{
+  char         *location;
+  icaltimezone *zone = NULL;
+	
+  location = calendar_client_config_get_timezone (gconf_client);
+  if (!location)
+    return icaltimezone_get_utc_timezone ();
+
+  zone = icaltimezone_get_builtin_timezone (location);
+  g_free (location);
+	
+  return zone;
+}
+
+static void
+calendar_client_set_timezone (CalendarClient *client) 
+{
+  GSList *l;
+  GSList *esources;
+
+  client->priv->zone = calendar_client_config_get_icaltimezone (client->priv->gconf_client);
+
+  esources = calendar_sources_get_appointment_sources (client->priv->calendar_sources);
+  for (l = esources; l; l = l->next) {
+    ECal *source = l->data;
+			
+    e_cal_set_default_timezone (source, client->priv->zone, NULL);
+  }
+}
+
+static void
+calendar_client_timezone_changed_cb (GConfClient    *gconf_client,
+                                     guint           id,
+                                     GConfEntry     *entry,
+                                     CalendarClient *client)
+{
+  calendar_client_set_timezone (client);
+}
+
+static void
+cal_opened_cb (ECal                 *ecal,
+               ECalendarStatus       status,
+               CalendarClientSource *cl_source)
+{
+  ECalSourceType  s_type;
+  CalendarClient *client = cl_source->client;
+
+  s_type = e_cal_get_source_type (ecal);
+
+  if (status == E_CALENDAR_STATUS_BUSY &&
+      e_cal_get_load_state (ecal) == E_CAL_LOAD_NOT_LOADED)
+    {
+      e_cal_open_async (ecal, FALSE);
+      return;
+    }
+  
+  g_signal_handlers_disconnect_by_func (ecal, cal_opened_cb, cl_source);
+
+  if (status != E_CALENDAR_STATUS_OK)
+    {
+      if (s_type == E_CAL_SOURCE_TYPE_EVENT)
+        client->priv->appointment_sources = g_slist_remove (client->priv->appointment_sources,
+                                                            cl_source);
+      else
+        client->priv->task_sources = g_slist_remove (client->priv->task_sources,
+                                                     cl_source);
+
+      calendar_client_source_finalize (cl_source);
+      g_free (cl_source);
+
+      return;
+    }
+
+  if (s_type == E_CAL_SOURCE_TYPE_EVENT)
+    calendar_client_update_appointments (client);
+  else
+    calendar_client_update_tasks (client);
+}
+
+static void
+load_calendars (CalendarClient    *client,
+                CalendarEventType  type) 
+{
+  GSList *l, *clients;
+
+  switch (type)
+    {
+      case CALENDAR_EVENT_APPOINTMENT:
+        clients = client->priv->appointment_sources;
+        break;
+      case CALENDAR_EVENT_TASK:
+        clients = client->priv->task_sources;
+        break;
+      default:
+        g_assert_not_reached ();
+    }
+
+  for (l = clients; l != NULL; l = l->next)
+    {
+      ECal *ecal;	
+      CalendarClientSource *cl_source = l->data;
+
+      ecal = cl_source->source;
+
+      if (e_cal_get_load_state (ecal) == E_CAL_LOAD_LOADED)
+        continue;
+
+      g_signal_connect (G_OBJECT (ecal), "cal_opened",
+                        G_CALLBACK (cal_opened_cb), cl_source);
+      e_cal_open_async (ecal, TRUE);
+    }
+}
+
+static void
+calendar_client_init (CalendarClient *client)
+{
+  GSList *esources;
+
+  client->priv = CALENDAR_CLIENT_GET_PRIVATE (client);
+
+  client->priv->calendar_sources = calendar_sources_get ();
+  client->priv->gconf_client = gconf_client_get_default ();
+
+  esources = calendar_sources_get_appointment_sources (client->priv->calendar_sources);
+  client->priv->appointment_sources =
+    calendar_client_update_sources_list (client, NULL, esources, signals [APPOINTMENTS_CHANGED]);
+
+  esources = calendar_sources_get_task_sources (client->priv->calendar_sources);
+  client->priv->task_sources =
+    calendar_client_update_sources_list (client, NULL, esources, signals [TASKS_CHANGED]);
+ 
+  /* set the timezone before loading the clients */ 
+  calendar_client_set_timezone (client);
+  load_calendars (client, CALENDAR_EVENT_APPOINTMENT);
+  load_calendars (client, CALENDAR_EVENT_TASK);
+
+  g_signal_connect_swapped (client->priv->calendar_sources,
+			    "appointment-sources-changed",
+			    G_CALLBACK (calendar_client_appointment_sources_changed),
+			    client);
+  g_signal_connect_swapped (client->priv->calendar_sources,
+			    "task-sources-changed",
+			    G_CALLBACK (calendar_client_task_sources_changed),
+			    client);
+
+  gconf_client_add_dir (client->priv->gconf_client,
+			CALENDAR_CONFIG_PREFIX,
+			GCONF_CLIENT_PRELOAD_NONE,
+			NULL);
+
+  client->priv->zone_listener = gconf_client_notify_add (client->priv->gconf_client,
+                                                         CALENDAR_CONFIG_TIMEZONE,
+                                                         (GConfClientNotifyFunc) calendar_client_timezone_changed_cb,
+                                                         client, NULL, NULL);
+
+  client->priv->day   = -1;
+  client->priv->month = -1;
+  client->priv->year  = -1;
+}
+
+static void
+calendar_client_finalize (GObject *object)
+{
+  CalendarClient *client = CALENDAR_CLIENT (object);
+  GSList         *l;
+
+  if (client->priv->zone_listener)
+    {
+      gconf_client_notify_remove (client->priv->gconf_client,
+                                  client->priv->zone_listener);
+      client->priv->zone_listener = 0;
+    }
+
+  gconf_client_remove_dir (client->priv->gconf_client,
+                           CALENDAR_CONFIG_PREFIX,
+                           NULL);
+
+  if (client->priv->gconf_client)
+    g_object_unref (client->priv->gconf_client);
+  client->priv->gconf_client = NULL;
+
+  for (l = client->priv->appointment_sources; l; l = l->next)
+    {
+      calendar_client_source_finalize (l->data);
+      g_free (l->data);
+    }
+  g_slist_free (client->priv->appointment_sources);
+  client->priv->appointment_sources = NULL;
+
+  for (l = client->priv->task_sources; l; l = l->next)
+    {
+      calendar_client_source_finalize (l->data);
+      g_free (l->data);
+    }
+  g_slist_free (client->priv->task_sources);
+  client->priv->task_sources = NULL;
+
+  if (client->priv->calendar_sources)
+    g_object_unref (client->priv->calendar_sources);
+  client->priv->calendar_sources = NULL;
+
+  if (G_OBJECT_CLASS (parent_class)->finalize)
+    G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+calendar_client_set_property (GObject      *object,
+			      guint         prop_id,
+			      const GValue *value,
+			      GParamSpec   *pspec)
+{
+  CalendarClient *client = CALENDAR_CLIENT (object);
+
+  switch (prop_id)
+    {
+    case PROP_DAY:
+      calendar_client_select_day (client, g_value_get_uint (value));
+      break;
+    case PROP_MONTH:
+      calendar_client_select_month (client,
+				    g_value_get_uint (value),
+				    client->priv->year);
+      break;
+    case PROP_YEAR:
+      calendar_client_select_month (client,
+				    client->priv->month,
+				    g_value_get_uint (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+calendar_client_get_property (GObject    *object,
+			      guint       prop_id,
+			      GValue     *value,
+			      GParamSpec *pspec)
+{
+  CalendarClient *client = CALENDAR_CLIENT (object);
+
+  switch (prop_id)
+    {
+    case PROP_DAY:
+      g_value_set_uint (value, client->priv->day);
+      break;
+    case PROP_MONTH:
+      g_value_set_uint (value, client->priv->month);
+      break;
+    case PROP_YEAR:
+      g_value_set_uint (value, client->priv->year);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+CalendarClient *
+calendar_client_new (void)
+{
+  return g_object_new (CALENDAR_TYPE_CLIENT, NULL);
+}
+
+/* @day and @month can happily be out of range as
+ * mktime() will normalize them correctly. From mktime(3):
+ *
+ * "If structure members are outside their legal interval,
+ *  they will be normalized (so that, e.g., 40 October is
+ *  changed into 9 November)."
+ *
+ * "What?", you say, "Something useful in libc?"
+ */
+static inline time_t
+make_time_for_day_begin (int day,
+			 int month,
+			 int year)
+{
+  struct tm localtime_tm = { 0, };
+
+  localtime_tm.tm_mday  = day;
+  localtime_tm.tm_mon   = month;
+  localtime_tm.tm_year  = year - 1900;
+  localtime_tm.tm_isdst = -1;
+
+  return mktime (&localtime_tm);
+}
+
+static inline char *
+make_isodate_for_day_begin (int day,
+			    int month,
+			    int year)
+{
+  time_t utctime;
+
+  utctime = make_time_for_day_begin (day, month, year);
+
+  return utctime != -1 ? isodate_from_time_t (utctime) : NULL;
+}
+
+static time_t
+get_time_from_property (icalcomponent         *ical,
+			icalproperty_kind      prop_kind,
+			struct icaltimetype (* get_prop_func) (const icalproperty *prop),
+                        icaltimezone          *default_zone)
+{
+  icalproperty        *prop;
+  struct icaltimetype  ical_time;
+  icalparameter       *param;
+  icaltimezone        *timezone = NULL;
+  
+  prop = icalcomponent_get_first_property (ical, prop_kind);
+  if (!prop)
+    return 0;
+
+  ical_time = get_prop_func (prop);
+
+  param = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER);
+  if (param)
+    timezone = icaltimezone_get_builtin_timezone_from_tzid (icalparameter_get_tzid (param));
+  else if (icaltime_is_utc (ical_time))
+    timezone = icaltimezone_get_utc_timezone ();
+  else 
+    timezone = default_zone;
+
+  return icaltime_as_timet_with_zone (ical_time, timezone);
+}
+
+static char *
+get_ical_uid (icalcomponent *ical)
+{
+  return g_strdup (icalcomponent_get_uid (ical));
+}
+
+static char *
+get_ical_rid (icalcomponent *ical)
+{
+  icalproperty        *prop;
+  struct icaltimetype  ical_time;
+  
+  prop = icalcomponent_get_first_property (ical, ICAL_RECURRENCEID_PROPERTY);
+  if (!prop)
+    return NULL;
+
+  ical_time = icalproperty_get_recurrenceid (prop);
+
+  return icaltime_is_valid_time (ical_time) && !icaltime_is_null_time (ical_time) ? 
+    g_strdup (icaltime_as_ical_string (ical_time)) : NULL;
+}
+
+static char *
+get_ical_summary (icalcomponent *ical)
+{
+  icalproperty *prop;
+
+  prop = icalcomponent_get_first_property (ical, ICAL_SUMMARY_PROPERTY);
+  if (!prop)
+    return NULL;
+
+  return g_strdup (icalproperty_get_summary (prop));
+}
+
+static char *
+get_ical_description (icalcomponent *ical)
+{
+  icalproperty *prop;
+
+  prop = icalcomponent_get_first_property (ical, ICAL_DESCRIPTION_PROPERTY);
+  if (!prop)
+    return NULL;
+
+  return g_strdup (icalproperty_get_description (prop));
+}
+
+static inline time_t
+get_ical_start_time (icalcomponent *ical,
+                     icaltimezone  *default_zone)
+{
+  return get_time_from_property (ical,
+				 ICAL_DTSTART_PROPERTY,
+				 icalproperty_get_dtstart,
+                                 default_zone);
+}
+
+static inline time_t
+get_ical_end_time (icalcomponent *ical,
+                   icaltimezone  *default_zone)
+{
+  return get_time_from_property (ical,
+				 ICAL_DTEND_PROPERTY,
+				 icalproperty_get_dtend,
+                                 default_zone);
+}
+
+static gboolean
+get_ical_is_all_day (icalcomponent *ical,
+                     time_t         start_time,
+                     icaltimezone  *default_zone)
+{
+  icalproperty            *prop;
+  struct tm               *start_tm;
+  time_t                   end_time;
+  struct icaldurationtype  duration;
+  struct icaltimetype      start_icaltime;
+
+  start_icaltime = icalcomponent_get_dtstart (ical);
+  if (start_icaltime.is_date)
+    return TRUE;
+
+  start_tm = gmtime (&start_time);
+  if (start_tm->tm_sec  != 0 ||
+      start_tm->tm_min  != 0 ||
+      start_tm->tm_hour != 0)
+    return FALSE;
+
+  if ((end_time = get_ical_end_time (ical, default_zone)))
+    return (end_time - start_time) % 86400 == 0;
+
+  prop = icalcomponent_get_first_property (ical, ICAL_DURATION_PROPERTY);
+  if (!prop)
+    return FALSE;
+
+  duration = icalproperty_get_duration (prop);
+
+  return icaldurationtype_as_int (duration) % 86400 == 0;
+}
+
+static inline time_t
+get_ical_due_time (icalcomponent *ical,
+                   icaltimezone  *default_zone)
+{
+  return get_time_from_property (ical,
+				 ICAL_DUE_PROPERTY,
+				 icalproperty_get_due,
+                                 default_zone);
+}
+
+static guint
+get_ical_percent_complete (icalcomponent *ical)
+{
+  icalproperty *prop;
+  icalproperty_status status;
+  int           percent_complete;
+
+  status = icalcomponent_get_status (ical);
+  if (status == ICAL_STATUS_COMPLETED)
+    return 100;
+
+  prop = icalcomponent_get_first_property (ical, ICAL_COMPLETED_PROPERTY);
+  if (prop)
+    return 100;
+
+  prop = icalcomponent_get_first_property (ical, ICAL_PERCENTCOMPLETE_PROPERTY);
+  if (!prop)
+    return 0;
+
+  percent_complete = icalproperty_get_percentcomplete (prop);
+
+  return CLAMP (percent_complete, 0, 100);
+}
+
+static inline time_t
+get_ical_completed_time (icalcomponent *ical,
+                         icaltimezone  *default_zone)
+{
+  return get_time_from_property (ical,
+				 ICAL_COMPLETED_PROPERTY,
+				 icalproperty_get_completed,
+                                 default_zone);
+}
+
+static int
+get_ical_priority (icalcomponent *ical)
+{
+  icalproperty *prop;
+
+  prop = icalcomponent_get_first_property (ical, ICAL_PRIORITY_PROPERTY);
+  if (!prop)
+    return -1;
+
+  return icalproperty_get_priority (prop);
+}
+
+static char *
+get_source_color (ECal *esource)
+{
+  ESource *source;
+
+  g_return_val_if_fail (E_IS_CAL (esource), NULL);
+
+  source = e_cal_get_source (esource);
+
+  return g_strdup (e_source_peek_color_spec (source));
+}
+
+static gchar *
+get_source_uri (ECal *esource)
+{
+    ESource *source;
+    gchar   *string;
+    gchar  **list;
+
+    g_return_val_if_fail (E_IS_CAL (esource), NULL);
+
+    source = e_cal_get_source (esource);
+    string = g_strdup (e_source_get_uri (source));
+    if (string) {
+        list = g_strsplit (string, ":", 2);
+        g_free (string);
+
+        if (list[0]) {
+            string = g_strdup (list[0]);
+            g_strfreev (list);
+            return string;
+        }
+	g_strfreev (list);
+    }
+    return NULL;
+}
+
+static inline int
+null_safe_strcmp (const char *a,
+		  const char *b)
+{
+  return (!a && !b) ? 0 : (a && !b) || (!a && b) ? 1 : strcmp (a, b);
+}
+
+static inline gboolean
+calendar_appointment_equal (CalendarAppointment *a,
+			    CalendarAppointment *b)
+{
+  GSList *la, *lb;
+
+  if (g_slist_length (a->occurrences) != g_slist_length (b->occurrences))
+      return FALSE;
+
+  for (la = a->occurrences, lb = b->occurrences; la && lb; la = la->next, lb = lb->next)
+    {
+      CalendarOccurrence *oa = la->data;
+      CalendarOccurrence *ob = lb->data;
+
+      if (oa->start_time != ob->start_time ||
+	  oa->end_time   != ob->end_time)
+	return FALSE;
+    }
+
+  return
+    null_safe_strcmp (a->uid,          b->uid)          == 0 &&
+    null_safe_strcmp (a->uri,          b->uri)          == 0 &&
+    null_safe_strcmp (a->summary,      b->summary)      == 0 &&
+    null_safe_strcmp (a->description,  b->description)  == 0 &&
+    null_safe_strcmp (a->color_string, b->color_string) == 0 &&
+    a->start_time == b->start_time                         &&
+    a->end_time   == b->end_time                           &&
+    a->is_all_day == b->is_all_day;
+}
+
+static void
+calendar_appointment_copy (CalendarAppointment *appointment,
+			   CalendarAppointment *appointment_copy)
+{
+  GSList *l;
+
+  g_assert (appointment != NULL);
+  g_assert (appointment_copy != NULL);
+
+  appointment_copy->occurrences = g_slist_copy (appointment->occurrences);
+  for (l = appointment_copy->occurrences; l; l = l->next)
+    {
+      CalendarOccurrence *occurrence = l->data;
+      CalendarOccurrence *occurrence_copy;
+
+      occurrence_copy             = g_new0 (CalendarOccurrence, 1);
+      occurrence_copy->start_time = occurrence->start_time;
+      occurrence_copy->end_time   = occurrence->end_time;
+
+      l->data = occurrence_copy;
+    }
+
+  appointment_copy->uid          = g_strdup (appointment->uid);
+  appointment_copy->uri          = g_strdup (appointment->uri);
+  appointment_copy->summary      = g_strdup (appointment->summary);
+  appointment_copy->description  = g_strdup (appointment->description);
+  appointment_copy->color_string = g_strdup (appointment->color_string);
+  appointment_copy->start_time   = appointment->start_time;
+  appointment_copy->end_time     = appointment->end_time;
+  appointment_copy->is_all_day   = appointment->is_all_day;
+}
+
+static void
+calendar_appointment_finalize (CalendarAppointment *appointment)
+{
+  GSList *l;
+
+  for (l = appointment->occurrences; l; l = l->next)
+    g_free (l->data);
+  g_slist_free (appointment->occurrences);
+  appointment->occurrences = NULL;
+
+  g_free (appointment->uid);
+  appointment->uid = NULL;
+
+  g_free (appointment->rid);
+  appointment->rid = NULL;
+
+  g_free (appointment->uri);
+  appointment->uri = NULL;
+
+  g_free (appointment->summary);
+  appointment->summary = NULL;
+
+  g_free (appointment->description);
+  appointment->description = NULL;
+
+  g_free (appointment->color_string);
+  appointment->color_string = NULL;
+
+  appointment->start_time = 0;
+  appointment->is_all_day = FALSE;
+}
+
+static void
+calendar_appointment_init (CalendarAppointment  *appointment,
+			   icalcomponent        *ical,
+                           CalendarClientSource *source,
+                           icaltimezone         *default_zone)
+{
+  appointment->uid          = get_ical_uid (ical);
+  appointment->rid          = get_ical_rid (ical);
+  appointment->uri          = get_source_uri (source->source);
+  appointment->summary      = get_ical_summary (ical);
+  appointment->description  = get_ical_description (ical);
+  appointment->color_string = get_source_color (source->source);
+  appointment->start_time   = get_ical_start_time (ical, default_zone);
+  appointment->end_time     = get_ical_end_time (ical, default_zone);
+  appointment->is_all_day   = get_ical_is_all_day (ical,
+                                                   appointment->start_time,
+                                                   default_zone);
+}
+
+static icaltimezone *
+resolve_timezone_id (const char *tzid,
+		     ECal       *source)
+{
+  icaltimezone *retval;
+
+  retval = icaltimezone_get_builtin_timezone_from_tzid (tzid);
+  if (!retval)
+    {
+      e_cal_get_timezone (source, tzid, &retval, NULL);
+    }
+
+  return retval;
+}
+
+static gboolean
+calendar_appointment_collect_occurrence (ECalComponent  *component,
+					 time_t          occurrence_start,
+					 time_t          occurrence_end,
+					 gpointer        data)
+{
+  CalendarOccurrence *occurrence;
+  GSList **collect_loc = data;
+
+  occurrence             = g_new0 (CalendarOccurrence, 1);
+  occurrence->start_time = occurrence_start;
+  occurrence->end_time   = occurrence_end;
+
+  *collect_loc = g_slist_prepend (*collect_loc, occurrence);
+
+  return TRUE;
+}
+
+static void
+calendar_appointment_generate_ocurrences (CalendarAppointment *appointment,
+					  icalcomponent       *ical,
+					  ECal                *source,
+					  time_t               start,
+					  time_t               end,
+                                          icaltimezone        *default_zone)
+{
+  ECalComponent *ecal;
+
+  g_assert (appointment->occurrences == NULL);
+
+  ecal = e_cal_component_new ();
+  e_cal_component_set_icalcomponent (ecal,
+				     icalcomponent_new_clone (ical));
+
+  e_cal_recur_generate_instances (ecal,
+				  start,
+				  end,
+				  calendar_appointment_collect_occurrence,
+				  &appointment->occurrences,
+				  (ECalRecurResolveTimezoneFn) resolve_timezone_id,
+				  source,
+				  default_zone);
+
+  g_object_unref (ecal);
+
+  appointment->occurrences = g_slist_reverse (appointment->occurrences);
+}
+
+static inline gboolean
+calendar_task_equal (CalendarTask *a,
+		     CalendarTask *b)
+{
+  return
+    null_safe_strcmp (a->uid,          b->uid)          == 0 &&
+    null_safe_strcmp (a->summary,      b->summary)      == 0 &&
+    null_safe_strcmp (a->description,  b->description)  == 0 &&
+    null_safe_strcmp (a->color_string, b->color_string) == 0 &&
+    a->start_time       == b->start_time                   &&
+    a->due_time         == b->due_time                     &&
+    a->percent_complete == b->percent_complete             &&
+    a->completed_time   == b->completed_time               &&
+    a->priority         == b->priority;
+}
+
+static void
+calendar_task_copy (CalendarTask *task,
+		    CalendarTask *task_copy)
+{
+  g_assert (task != NULL);
+  g_assert (task_copy != NULL);
+
+  task_copy->uid              = g_strdup (task->uid);
+  task_copy->summary          = g_strdup (task->summary);
+  task_copy->description      = g_strdup (task->description);
+  task_copy->color_string     = g_strdup (task->color_string);
+  task_copy->start_time       = task->start_time;
+  task_copy->due_time         = task->due_time;
+  task_copy->percent_complete = task->percent_complete;
+  task_copy->completed_time   = task->completed_time;
+  task_copy->priority         = task->priority;
+}
+
+static void
+calendar_task_finalize (CalendarTask *task)
+{
+  g_free (task->uid);
+  task->uid = NULL;
+
+  g_free (task->summary);
+  task->summary = NULL;
+
+  g_free (task->description);
+  task->description = NULL;
+
+  g_free (task->color_string);
+  task->color_string = NULL;
+
+  task->percent_complete = 0;
+}
+
+static void
+calendar_task_init (CalendarTask         *task,
+		    icalcomponent        *ical,
+                    CalendarClientSource *source,
+                    icaltimezone         *default_zone)
+{
+  task->uid              = get_ical_uid (ical);
+  task->summary          = get_ical_summary (ical);
+  task->description      = get_ical_description (ical);
+  task->color_string     = get_source_color (source->source);
+  task->start_time       = get_ical_start_time (ical, default_zone);
+  task->due_time         = get_ical_due_time (ical, default_zone);
+  task->percent_complete = get_ical_percent_complete (ical);
+  task->completed_time   = get_ical_completed_time (ical, default_zone);
+  task->priority         = get_ical_priority (ical);
+}
+
+void
+calendar_event_free (CalendarEvent *event)
+{
+  switch (event->type)
+    {
+    case CALENDAR_EVENT_APPOINTMENT:
+      calendar_appointment_finalize (CALENDAR_APPOINTMENT (event));
+      break;
+    case CALENDAR_EVENT_TASK:
+      calendar_task_finalize (CALENDAR_TASK (event));
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+
+  g_free (event);
+}
+
+static CalendarEvent *
+calendar_event_new (icalcomponent        *ical,
+                    CalendarClientSource *source,
+                    icaltimezone         *default_zone)
+{
+  CalendarEvent *event;
+
+  event = g_new0 (CalendarEvent, 1);
+
+  switch (icalcomponent_isa (ical))
+    {
+    case ICAL_VEVENT_COMPONENT:
+      event->type = CALENDAR_EVENT_APPOINTMENT;
+      calendar_appointment_init (CALENDAR_APPOINTMENT (event),
+                                 ical,
+                                 source,
+                                 default_zone);
+      break;
+    case ICAL_VTODO_COMPONENT:
+      event->type = CALENDAR_EVENT_TASK;
+      calendar_task_init (CALENDAR_TASK (event),
+                          ical,
+                          source,
+                          default_zone);
+      break;
+    default:
+      g_warning ("Unknown calendar component type: %d\n",
+                 icalcomponent_isa (ical));
+      g_free (event);
+      return NULL;
+    }
+
+  return event;
+}
+
+static CalendarEvent *
+calendar_event_copy (CalendarEvent *event)
+{
+  CalendarEvent *retval;
+
+  if (!event)
+    return NULL;
+
+  retval = g_new0 (CalendarEvent, 1);
+
+  retval->type = event->type;
+
+  switch (event->type)
+    {
+    case CALENDAR_EVENT_APPOINTMENT:
+      calendar_appointment_copy (CALENDAR_APPOINTMENT (event),
+				 CALENDAR_APPOINTMENT (retval));
+      break;
+    case CALENDAR_EVENT_TASK:
+      calendar_task_copy (CALENDAR_TASK (event),
+			  CALENDAR_TASK (retval));
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+
+  return retval;
+}
+
+static char *
+calendar_event_get_uid (CalendarEvent *event)
+{
+  switch (event->type)
+    {
+    case CALENDAR_EVENT_APPOINTMENT:
+      return g_strdup_printf ("%s%s", CALENDAR_APPOINTMENT (event)->uid, CALENDAR_APPOINTMENT (event)->rid ? CALENDAR_APPOINTMENT (event)->rid : ""); 
+      break;
+    case CALENDAR_EVENT_TASK:
+      return g_strdup (CALENDAR_TASK (event)->uid);
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+
+  return NULL;
+}
+
+static gboolean
+calendar_event_equal (CalendarEvent *a,
+		      CalendarEvent *b)
+{
+  if (!a && !b)
+    return TRUE;
+
+  if ((a && !b) || (!a && b))
+    return FALSE;
+
+  if (a->type != b->type)
+    return FALSE;
+
+  switch (a->type)
+    {
+    case CALENDAR_EVENT_APPOINTMENT:
+      return calendar_appointment_equal (CALENDAR_APPOINTMENT (a),
+					 CALENDAR_APPOINTMENT (b));
+    case CALENDAR_EVENT_TASK:
+      return calendar_task_equal (CALENDAR_TASK (a),
+				  CALENDAR_TASK (b));
+    default:
+      break;
+    }
+ 
+  g_assert_not_reached ();
+
+  return FALSE;
+}
+
+static void
+calendar_event_generate_ocurrences (CalendarEvent *event,
+				    icalcomponent *ical,
+				    ECal          *source,
+				    time_t         start,
+				    time_t         end,
+                                    icaltimezone  *default_zone)
+{
+  if (event->type != CALENDAR_EVENT_APPOINTMENT)
+    return;
+
+  calendar_appointment_generate_ocurrences (CALENDAR_APPOINTMENT (event),
+					    ical,
+					    source,
+					    start,
+					    end,
+                                            default_zone);
+}
+
+static inline void
+calendar_event_debug_dump (CalendarEvent *event)
+{
+#ifdef CALENDAR_ENABLE_DEBUG
+  switch (event->type)
+    {
+    case CALENDAR_EVENT_APPOINTMENT:
+      {
+	char   *start_str;
+	char   *end_str;
+	GSList *l;
+
+	start_str = CALENDAR_APPOINTMENT (event)->start_time ?
+	                    isodate_from_time_t (CALENDAR_APPOINTMENT (event)->start_time) :
+	                    g_strdup ("(undefined)");
+	end_str = CALENDAR_APPOINTMENT (event)->end_time ?
+	                    isodate_from_time_t (CALENDAR_APPOINTMENT (event)->end_time) :
+	                    g_strdup ("(undefined)");
+	  
+	dprintf ("Appointment: uid '%s', summary '%s', description '%s', "
+		 "start_time '%s', end_time '%s', is_all_day %s\n",
+		 CALENDAR_APPOINTMENT (event)->uid,
+		 CALENDAR_APPOINTMENT (event)->summary,
+		 CALENDAR_APPOINTMENT (event)->description,
+		 start_str,
+		 end_str,
+		 CALENDAR_APPOINTMENT (event)->is_all_day ? "(true)" : "(false)");
+
+	g_free (start_str);
+	g_free (end_str);
+
+	dprintf ("  Occurrences:\n");
+	for (l = CALENDAR_APPOINTMENT (event)->occurrences; l; l = l->next)
+	  {
+	    CalendarOccurrence *occurrence = l->data;
+
+	    start_str = occurrence->start_time ?
+	      isodate_from_time_t (occurrence->start_time) :
+	      g_strdup ("(undefined)");
+	    
+	    end_str = occurrence->end_time ?
+	      isodate_from_time_t (occurrence->end_time) :
+	      g_strdup ("(undefined)");
+
+	    dprintf ("    start_time '%s', end_time '%s'\n",
+		     start_str, end_str);
+
+	    g_free (start_str);
+	    g_free (end_str);
+	  }
+      }
+      break;
+    case CALENDAR_EVENT_TASK:
+      {
+	char *start_str;
+	char *due_str;
+	char *completed_str;
+
+	start_str = CALENDAR_TASK (event)->start_time ?
+	                    isodate_from_time_t (CALENDAR_TASK (event)->start_time) :
+	                    g_strdup ("(undefined)");
+	due_str = CALENDAR_TASK (event)->due_time ?
+	                    isodate_from_time_t (CALENDAR_TASK (event)->due_time) :
+	                    g_strdup ("(undefined)");
+	completed_str = CALENDAR_TASK (event)->completed_time ?
+	                    isodate_from_time_t (CALENDAR_TASK (event)->completed_time) :
+	                    g_strdup ("(undefined)");
+
+	dprintf ("Task: uid '%s', summary '%s', description '%s', "
+		 "start_time '%s', due_time '%s', percent_complete %d, completed_time '%s'\n",
+		 CALENDAR_TASK (event)->uid,
+		 CALENDAR_TASK (event)->summary,
+		 CALENDAR_TASK (event)->description,
+		 start_str,
+		 due_str,
+		 CALENDAR_TASK (event)->percent_complete,
+		 completed_str);
+
+	g_free (completed_str);
+      }
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+#endif
+}
+
+static inline CalendarClientQuery *
+goddamn_this_is_crack (CalendarClientSource *source,
+		       ECalView             *view,
+		       gboolean             *emit_signal)
+{
+  g_assert (view != NULL);
+
+  if (source->completed_query.view == view)
+    {
+      if (emit_signal)
+	*emit_signal = TRUE;
+      return &source->completed_query;
+    }
+  else if (source->in_progress_query.view == view)
+    {
+      if (emit_signal)
+	*emit_signal = FALSE;
+      return &source->in_progress_query;
+    }
+
+  g_assert_not_reached ();
+
+  return NULL;
+}
+
+static void
+calendar_client_handle_query_completed (CalendarClientSource *source,
+					ECalendarStatus       status,
+					ECalView             *view)
+{
+  CalendarClientQuery *query;
+
+  query = goddamn_this_is_crack (source, view, NULL);
+  
+  dprintf ("Query %p completed: %s\n", query, e_cal_get_error_message (status));
+
+  if (status != E_CALENDAR_STATUS_OK)
+    {
+      g_warning ("Calendar query failed: %s\n",
+		 e_cal_get_error_message (status));
+      calendar_client_stop_query (source->client, source, query);
+      return;
+    }
+
+  g_assert (source->query_in_progress != FALSE);
+  g_assert (query == &source->in_progress_query);
+
+  calendar_client_query_finalize (&source->completed_query);
+
+  source->completed_query = source->in_progress_query;
+  source->query_completed = TRUE;
+
+  source->query_in_progress        = FALSE;
+  source->in_progress_query.view   = NULL;
+  source->in_progress_query.events = NULL;
+
+  g_signal_emit (source->client, source->changed_signal_id, 0);
+}
+
+static void
+calendar_client_handle_query_result (CalendarClientSource *source,
+				     GList                *objects,
+				     ECalView             *view)
+{
+  CalendarClientQuery *query;
+  CalendarClient      *client;
+  gboolean             emit_signal;
+  gboolean             events_changed;
+  GList               *l;
+  time_t               month_begin;
+  time_t               month_end;
+
+  client = source->client;
+
+  query = goddamn_this_is_crack (source, view, &emit_signal);
+
+  dprintf ("Query %p result: %d objects:\n",
+	   query, g_list_length (objects));
+
+  month_begin = make_time_for_day_begin (1,
+					 client->priv->month,
+					 client->priv->year);
+
+  month_end = make_time_for_day_begin (1,
+				       client->priv->month + 1,
+				       client->priv->year);
+
+  events_changed = FALSE;
+  for (l = objects; l; l = l->next)
+    {
+      CalendarEvent *event;
+      CalendarEvent *old_event;
+      icalcomponent *ical = l->data;
+      char          *uid;
+      
+      event = calendar_event_new (ical, source, client->priv->zone);
+      if (!event)
+	      continue;
+
+      calendar_event_generate_ocurrences (event,
+					  ical,
+					  source->source,
+					  month_begin,
+					  month_end,
+                                          client->priv->zone);
+
+      uid = calendar_event_get_uid (event);
+      
+      old_event = g_hash_table_lookup (query->events, uid);
+
+      if (!calendar_event_equal (event, old_event))
+	{
+ 	  dprintf ("Event %s: ", old_event ? "modified" : "added");
+
+	  calendar_event_debug_dump (event);
+
+	  g_hash_table_replace (query->events, uid, event);
+
+	  events_changed = TRUE;
+	}
+      else
+	{
+	  g_free (uid);
+	}		
+    }
+
+  if (emit_signal && events_changed)
+    {
+      g_signal_emit (source->client, source->changed_signal_id, 0);
+    }
+}
+
+static gboolean
+check_object_remove (gpointer key,
+                     gpointer value,
+                     gpointer data)
+{
+  char             *uid = data;
+  ssize_t           len;
+
+  len = strlen (uid);
+  
+  if (len <= strlen (key) && strncmp (uid, key, len) == 0)
+    {
+      dprintf ("Event removed: ");
+
+      calendar_event_debug_dump (value);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+calendar_client_handle_objects_removed (CalendarClientSource *source,
+					GList                *ids,
+					ECalView             *view)
+{
+  CalendarClientQuery *query;
+  gboolean             emit_signal;
+  gboolean             events_changed;
+  GList               *l;
+
+  query = goddamn_this_is_crack (source, view, &emit_signal);
+
+  events_changed = FALSE;
+  for (l = ids; l; l = l->next)
+    {
+      CalendarEvent   *event;
+      ECalComponentId *id = l->data;
+      char            *uid = g_strdup_printf ("%s%s", id->uid, id->rid ? id->rid : "");
+
+      if (!id->rid || !(*id->rid))
+	{
+	  int size = g_hash_table_size (query->events);
+
+	  g_hash_table_foreach_remove (query->events, check_object_remove, id->uid);
+
+		if (size != g_hash_table_size (query->events))
+			events_changed = TRUE;		
+	}
+      else if ((event = g_hash_table_lookup (query->events, uid)))
+	{
+	  dprintf ("Event removed: ");
+
+	  calendar_event_debug_dump (event);
+
+	  g_assert (g_hash_table_remove (query->events, uid));
+
+	  events_changed = TRUE;
+	}
+      g_free (uid);
+    }
+
+  if (emit_signal && events_changed)
+    {
+      g_signal_emit (source->client, source->changed_signal_id, 0);
+    }
+}
+
+static void
+calendar_client_query_finalize (CalendarClientQuery *query)
+{
+  if (query->view)
+    g_object_unref (query->view);
+  query->view = NULL;
+
+  if (query->events)
+    g_hash_table_destroy (query->events);
+  query->events = NULL;
+}
+
+static void
+calendar_client_stop_query (CalendarClient       *client,
+			    CalendarClientSource *source,
+			    CalendarClientQuery  *query)
+{
+  if (query == &source->in_progress_query)
+    {
+      dprintf ("Stopping in progress query %p\n", query);
+
+      g_assert (source->query_in_progress != FALSE);
+
+      source->query_in_progress = FALSE;
+    }
+  else if (query == &source->completed_query)
+    {
+      dprintf ("Stopping completed query %p\n", query);
+
+      g_assert (source->query_completed != FALSE);
+
+      source->query_completed = FALSE;
+    }
+  else
+    g_assert_not_reached ();
+  
+  calendar_client_query_finalize (query);
+}
+
+static void
+calendar_client_start_query (CalendarClient       *client,
+			     CalendarClientSource *source,
+			     const char           *query)
+{
+  ECalView *view = NULL;
+  GError   *error = NULL;
+
+  if (!e_cal_get_query (source->source, query, &view, &error))
+    {
+      g_warning ("Error preparing the query: '%s': %s\n",
+		 query, error->message);
+      g_error_free (error);
+      return;
+    }
+
+  g_assert (view != NULL);
+
+  if (source->query_in_progress)
+    calendar_client_stop_query (client, source, &source->in_progress_query);
+  
+  dprintf ("Starting query %p: '%s'\n", &source->in_progress_query, query);
+
+  source->query_in_progress        = TRUE;
+  source->in_progress_query.view   = view;
+  source->in_progress_query.events =
+    g_hash_table_new_full (g_str_hash,
+			   g_str_equal,
+			   g_free,
+			   (GDestroyNotify) calendar_event_free);
+
+  g_signal_connect_swapped (view, "objects-added",
+			    G_CALLBACK (calendar_client_handle_query_result),
+			    source);
+  g_signal_connect_swapped (view, "objects-modified",
+			    G_CALLBACK (calendar_client_handle_query_result),
+			    source);
+  g_signal_connect_swapped (view, "objects-removed",
+			    G_CALLBACK (calendar_client_handle_objects_removed),
+			    source);
+  g_signal_connect_swapped (view, "view-done",
+			    G_CALLBACK (calendar_client_handle_query_completed),
+			    source);
+
+  e_cal_view_start (view);
+}
+
+static void
+calendar_client_update_appointments (CalendarClient *client)
+{
+  GSList *l;
+  char   *query;
+  char   *month_begin;
+  char   *month_end;
+
+  if (client->priv->month == -1 ||
+      client->priv->year  == -1)
+    return;
+
+  month_begin = make_isodate_for_day_begin (1,
+					    client->priv->month,
+					    client->priv->year);
+
+  month_end = make_isodate_for_day_begin (1,
+					  client->priv->month + 1,
+					  client->priv->year);
+
+  query = g_strdup_printf ("occur-in-time-range? (make-time \"%s\") "
+			                        "(make-time \"%s\")",
+			   month_begin, month_end);
+
+  for (l = client->priv->appointment_sources; l; l = l->next)
+    {
+      CalendarClientSource *cs = l->data;
+                  
+      if (e_cal_get_load_state (cs->source) != E_CAL_LOAD_LOADED)  
+        continue;
+
+      calendar_client_start_query (client, cs, query);
+    }
+
+  g_free (month_begin);
+  g_free (month_end);
+  g_free (query);
+}
+
+/* FIXME:
+ * perhaps we should use evo's "hide_completed_tasks" pref?
+ */
+static void
+calendar_client_update_tasks (CalendarClient *client)
+{
+  GSList *l;
+  char   *query;
+
+#ifdef FIX_BROKEN_TASKS_QUERY
+  /* FIXME: this doesn't work for tasks without a start or
+   *        due date
+   *        Look at filter_task() to see the behaviour we
+   *        want.
+   */
+  
+  char   *day_begin;
+  char   *day_end;
+
+  if (client->priv->day   == -1 ||
+      client->priv->month == -1 ||
+      client->priv->year  == -1)
+    return;
+
+  day_begin = make_isodate_for_day_begin (client->priv->day,
+					  client->priv->month,
+					  client->priv->year);
+
+  day_end = make_isodate_for_day_begin (client->priv->day + 1,
+					client->priv->month,
+					client->priv->year);
+  if (!day_begin || !day_end)
+    {
+      g_warning ("Cannot run query with invalid date: %dd %dy %dm\n",
+		 client->priv->day,
+		 client->priv->month,
+		 client->priv->year);
+      g_free (day_begin);
+      g_free (day_end);
+      return;
+    }
+  
+  query = g_strdup_printf ("(and (occur-in-time-range? (make-time \"%s\") "
+                                                      "(make-time \"%s\")) "
+                             "(or (not is-completed?) "
+                               "(and (is-completed?) "
+                                    "(not (completed-before? (make-time \"%s\"))))))",
+			   day_begin, day_end, day_begin);
+#else
+  query = g_strdup ("#t");
+#endif /* FIX_BROKEN_TASKS_QUERY */
+
+  for (l = client->priv->task_sources; l; l = l->next)
+    {
+      CalendarClientSource *cs = l->data;
+
+      if (e_cal_get_load_state (cs->source) != E_CAL_LOAD_LOADED)  
+        continue;
+
+      calendar_client_start_query (client, cs, query);
+    }
+
+#ifdef FIX_BROKEN_TASKS_QUERY
+  g_free (day_begin);
+  g_free (day_end);
+#endif
+  g_free (query);
+}
+
+static void
+calendar_client_source_finalize (CalendarClientSource *source)
+{
+  source->client = NULL;
+
+  if (source->source) {
+    g_signal_handlers_disconnect_by_func (source->source,
+                                          cal_opened_cb, source);
+    g_object_unref (source->source);
+  }
+  source->source = NULL;
+
+  calendar_client_query_finalize (&source->completed_query);
+  calendar_client_query_finalize (&source->in_progress_query);
+  
+  source->query_completed   = FALSE;
+  source->query_in_progress = FALSE;
+}
+
+static int
+compare_calendar_sources (CalendarClientSource *s1,
+			  CalendarClientSource *s2)
+{
+  return (s1->source == s2->source) ? 0 : 1;
+}
+
+static GSList *
+calendar_client_update_sources_list (CalendarClient *client,
+				     GSList         *sources,
+				     GSList         *esources,
+				     guint           changed_signal_id)
+{
+  GSList *retval, *l;
+
+  retval = NULL;
+
+  for (l = esources; l; l = l->next)
+    {
+      CalendarClientSource  dummy_source;
+      CalendarClientSource *new_source;
+      GSList               *s;
+      ECal                 *esource = l->data;
+
+      dummy_source.source = esource;
+
+      dprintf ("update_sources_list: adding client %s: ",
+	       e_source_peek_uid (e_cal_get_source (esource)));
+
+      if ((s = g_slist_find_custom (sources,
+				    &dummy_source,
+				    (GCompareFunc) compare_calendar_sources)))
+	{
+	  dprintf ("already on list\n");
+	  new_source = s->data;
+	  sources = g_slist_delete_link (sources, s);
+	}
+      else
+	{
+	  dprintf ("added\n");
+	  new_source                    = g_new0 (CalendarClientSource, 1);
+	  new_source->client            = client;
+	  new_source->source            = g_object_ref (esource);
+	  new_source->changed_signal_id = changed_signal_id;
+	}
+
+      retval = g_slist_prepend (retval, new_source);
+    }
+
+  for (l = sources; l; l = l->next)
+    {
+      CalendarClientSource *source = l->data;
+
+      dprintf ("Removing client %s from list\n",
+	       e_source_peek_uid (e_cal_get_source (source->source)));
+
+      calendar_client_source_finalize (source);
+      g_free (source);
+    }
+  g_slist_free (sources);
+
+  return retval;
+}
+
+static void
+calendar_client_appointment_sources_changed (CalendarClient  *client)
+{
+  GSList *esources;
+
+  dprintf ("appointment_sources_changed: updating ...\n");
+
+  esources = calendar_sources_get_appointment_sources (client->priv->calendar_sources);
+
+  client->priv->appointment_sources = 
+    calendar_client_update_sources_list (client,
+					 client->priv->appointment_sources,
+					 esources,
+					 signals [APPOINTMENTS_CHANGED]);
+
+  load_calendars (client, CALENDAR_EVENT_APPOINTMENT);
+  calendar_client_update_appointments (client);
+}
+
+static void
+calendar_client_task_sources_changed (CalendarClient  *client)
+{
+  GSList *esources;
+
+  dprintf ("task_sources_changed: updating ...\n");
+
+  esources = calendar_sources_get_task_sources (client->priv->calendar_sources);
+
+  client->priv->task_sources = 
+    calendar_client_update_sources_list (client,
+					 client->priv->task_sources,
+					 esources,
+					 signals [TASKS_CHANGED]);
+
+  load_calendars (client, CALENDAR_EVENT_TASK);
+  calendar_client_update_tasks (client);
+}
+
+void
+calendar_client_get_date (CalendarClient *client,
+                          guint          *year,
+                          guint          *month,
+                          guint          *day)
+{
+  g_return_if_fail (CALENDAR_IS_CLIENT (client));
+
+  if (year)
+    *year = client->priv->year;
+
+  if (month)
+    *month = client->priv->month;
+
+  if (day)
+    *day = client->priv->day;
+}
+
+void
+calendar_client_select_month (CalendarClient *client,
+			      guint           month,
+			      guint           year)
+{
+  g_return_if_fail (CALENDAR_IS_CLIENT (client));
+  g_return_if_fail (month <= 11);
+
+  if (client->priv->year != year || client->priv->month != month)
+    {
+      client->priv->month = month;
+      client->priv->year  = year;
+
+      calendar_client_update_appointments (client);
+      calendar_client_update_tasks (client);
+
+      g_object_freeze_notify (G_OBJECT (client));
+      g_object_notify (G_OBJECT (client), "month");
+      g_object_notify (G_OBJECT (client), "year");
+      g_object_thaw_notify (G_OBJECT (client));
+    }
+}
+
+void
+calendar_client_select_day (CalendarClient *client,
+			    guint           day)
+{
+  g_return_if_fail (CALENDAR_IS_CLIENT (client));
+  g_return_if_fail (day <= 31);
+
+  if (client->priv->day != day)
+    {
+      client->priv->day = day;
+
+      /* don't need to update appointments unless
+       * the selected month changes
+       */
+#ifdef FIX_BROKEN_TASKS_QUERY
+      calendar_client_update_tasks (client);
+#endif
+
+      g_object_notify (G_OBJECT (client), "day");
+    }
+}
+
+typedef struct
+{
+  CalendarClient *client;
+  GSList         *events;
+  time_t          start_time;
+  time_t          end_time;
+} FilterData;
+
+typedef void (* CalendarEventFilterFunc) (const char    *uid,
+					  CalendarEvent *event,
+					  FilterData    *filter_data);
+
+static void
+filter_appointment (const char    *uid,
+		    CalendarEvent *event,
+		    FilterData    *filter_data)
+{
+  GSList *occurrences, *l;
+
+  if (event->type != CALENDAR_EVENT_APPOINTMENT)
+    return;
+
+  occurrences = CALENDAR_APPOINTMENT (event)->occurrences;
+  CALENDAR_APPOINTMENT (event)->occurrences = NULL;
+
+  for (l = occurrences; l; l = l->next)
+    {
+      CalendarOccurrence *occurrence = l->data;
+      time_t start_time = occurrence->start_time;
+      time_t end_time   = occurrence->end_time;
+
+      if ((start_time >= filter_data->start_time &&
+           start_time < filter_data->end_time) ||
+          (start_time <= filter_data->start_time &&
+           (end_time - 1) > filter_data->start_time))
+	{
+	  CalendarEvent *new_event;
+
+	  new_event = calendar_event_copy (event);
+	      
+	  CALENDAR_APPOINTMENT (new_event)->start_time = occurrence->start_time;
+	  CALENDAR_APPOINTMENT (new_event)->end_time   = occurrence->end_time;
+	      
+	  filter_data->events = g_slist_prepend (filter_data->events, new_event);
+	}
+    }
+
+  CALENDAR_APPOINTMENT (event)->occurrences = occurrences;
+}
+
+static void
+filter_task (const char    *uid,
+	     CalendarEvent *event,
+	     FilterData    *filter_data)
+{
+#ifdef FIX_BROKEN_TASKS_QUERY
+  CalendarTask *task;
+#endif
+
+  if (event->type != CALENDAR_EVENT_TASK)
+    return;
+
+#ifdef FIX_BROKEN_TASKS_QUERY
+  task = CALENDAR_TASK (event);
+
+  if (task->start_time && task->start_time > filter_data->start_time)
+    return;
+
+  if (task->completed_time && 
+      (task->completed_time < filter_data->start_time ||
+       task->completed_time > filter_data->end_time))
+    return;
+#endif /* FIX_BROKEN_TASKS_QUERY */
+
+  filter_data->events = g_slist_prepend (filter_data->events,
+					 calendar_event_copy (event));
+}
+
+static GSList *
+calendar_client_filter_events (CalendarClient          *client,
+			       GSList                  *sources,
+			       CalendarEventFilterFunc  filter_func,
+			       time_t                   start_time,
+			       time_t                   end_time)
+{
+  FilterData  filter_data;
+  GSList     *l;
+  GSList     *retval;
+
+  if (!sources)
+    return NULL;
+
+  filter_data.client     = client;
+  filter_data.events     = NULL;
+  filter_data.start_time = start_time;
+  filter_data.end_time   = end_time;
+
+  retval = NULL;
+  for (l = sources; l; l = l->next)
+    {
+      CalendarClientSource *source = l->data;
+
+      if (source->query_completed)
+	{
+	  filter_data.events = NULL;
+	  g_hash_table_foreach (source->completed_query.events,
+				(GHFunc) filter_func,
+				&filter_data);
+
+	  filter_data.events = g_slist_reverse (filter_data.events);
+
+	  retval = g_slist_concat (retval, filter_data.events);
+	}
+    }
+
+  return retval;
+}
+
+GSList *
+calendar_client_get_events (CalendarClient    *client,
+			    CalendarEventType  event_mask)
+{
+  GSList *appointments;
+  GSList *tasks;
+  time_t  day_begin;
+  time_t  day_end;
+
+  g_return_val_if_fail (CALENDAR_IS_CLIENT (client), NULL);
+  g_return_val_if_fail (client->priv->day   != -1 &&
+			client->priv->month != -1 &&
+			client->priv->year  != -1, NULL);
+
+  day_begin = make_time_for_day_begin (client->priv->day,
+				       client->priv->month,
+				       client->priv->year);
+  day_end   = make_time_for_day_begin (client->priv->day + 1,
+				       client->priv->month,
+				       client->priv->year);
+
+  appointments = NULL;
+  if (event_mask & CALENDAR_EVENT_APPOINTMENT)
+    {
+      appointments = calendar_client_filter_events (client,
+						    client->priv->appointment_sources,
+						    filter_appointment,
+						    day_begin,
+						    day_end);
+    }
+
+  tasks = NULL;
+  if (event_mask & CALENDAR_EVENT_TASK)
+    {
+      tasks = calendar_client_filter_events (client,
+					     client->priv->task_sources,
+					     filter_task,
+					     day_begin,
+					     day_end);
+    }
+
+  return g_slist_concat (appointments, tasks);
+}
+
+static inline int
+day_from_time_t (time_t t)
+{
+  struct tm *tm = localtime (&t);
+
+  g_assert (tm == NULL || (tm->tm_mday >=1 && tm->tm_mday <= 31));
+
+  return tm ? tm->tm_mday : 0;
+}
+
+void
+calendar_client_foreach_appointment_day (CalendarClient  *client,
+					 CalendarDayIter  iter_func,
+					 gpointer         user_data)
+{
+  GSList   *appointments, *l;
+  gboolean  marked_days [32] = { FALSE, };
+  time_t    month_begin;
+  time_t    month_end;
+  int       i;
+
+  g_return_if_fail (CALENDAR_IS_CLIENT (client));
+  g_return_if_fail (iter_func != NULL);
+  g_return_if_fail (client->priv->month != -1 &&
+		    client->priv->year  != -1);
+
+  month_begin = make_time_for_day_begin (1,
+					 client->priv->month,
+					 client->priv->year);
+  month_end   = make_time_for_day_begin (1,
+					 client->priv->month + 1,
+					 client->priv->year);
+  
+  appointments = calendar_client_filter_events (client,
+						client->priv->appointment_sources,
+						filter_appointment,
+						month_begin,
+						month_end);
+  for (l = appointments; l; l = l->next)
+    {
+      CalendarAppointment *appointment = l->data;
+
+      if (appointment->start_time)
+        {
+          time_t day_time = appointment->start_time;
+
+          if (day_time >= month_begin)
+            marked_days [day_from_time_t (day_time)] = TRUE;
+      
+          if (appointment->end_time)
+            {
+              int day_offset;
+              int duration = appointment->end_time - appointment->start_time;
+	      /* mark the days for the appointment, no need to add an extra one when duration is a multiple of 86400 */
+              for (day_offset = 1; day_offset <= duration / 86400 && duration != day_offset * 86400; day_offset++)
+                {
+                  time_t day_tm = appointment->start_time + day_offset * 86400;
+
+                  if (day_tm > month_end)
+                    break;
+                  if (day_tm >= month_begin)
+                    marked_days [day_from_time_t (day_tm)] = TRUE;
+                }
+            }
+        }
+      calendar_event_free (CALENDAR_EVENT (appointment));
+    }
+
+  g_slist_free (appointments);
+
+  for (i = 1; i < 32; i++)
+    {
+      if (marked_days [i])
+	iter_func (client, i, user_data);
+    }
+}
+
+void
+calendar_client_set_task_completed (CalendarClient *client,
+				    char           *task_uid,
+				    gboolean        task_completed,
+				    guint           percent_complete)
+{
+  GSList              *l;
+  ECal                *esource;
+  icalcomponent       *ical;
+  icalproperty        *prop;
+  icalproperty_status  status;
+
+  g_return_if_fail (CALENDAR_IS_CLIENT (client));
+  g_return_if_fail (task_uid != NULL);
+  g_return_if_fail (task_completed == FALSE || percent_complete == 100);
+
+  ical = NULL;
+  esource = NULL;
+  for (l = client->priv->task_sources; l; l = l->next)
+    {
+      CalendarClientSource *source = l->data;
+
+      esource = source->source;
+      e_cal_get_object (esource, task_uid, NULL, &ical, NULL);
+      if (ical)
+	break;
+    }
+
+  if (!ical)
+    {
+      g_warning ("Cannot locate task with uid = '%s'\n", task_uid);
+      return;
+    }
+
+  g_assert (esource != NULL);
+
+  /* Completed time */
+  prop = icalcomponent_get_first_property (ical,
+					   ICAL_COMPLETED_PROPERTY);
+  if (task_completed)
+    {
+      struct icaltimetype  completed_time;
+
+      completed_time = icaltime_current_time_with_zone (client->priv->zone);
+      if (!prop)
+	{
+	  icalcomponent_add_property (ical,
+				      icalproperty_new_completed (completed_time));
+	}
+      else
+	{
+	  icalproperty_set_completed (prop, completed_time);
+	}
+    }
+  else if (prop)
+    {
+      icalcomponent_remove_property (ical, prop);
+    }
+
+  /* Percent complete */
+  prop = icalcomponent_get_first_property (ical,
+					   ICAL_PERCENTCOMPLETE_PROPERTY);
+  if (!prop)
+    {
+      icalcomponent_add_property (ical,
+				  icalproperty_new_percentcomplete (percent_complete));
+    }
+  else
+    {
+      icalproperty_set_percentcomplete (prop, percent_complete);
+    }
+
+  /* Status */
+  status = task_completed ? ICAL_STATUS_COMPLETED : ICAL_STATUS_NEEDSACTION;
+  prop = icalcomponent_get_first_property (ical, ICAL_STATUS_PROPERTY);
+  if (prop)
+    {
+      icalproperty_set_status (prop, status);
+    }
+  else
+    {
+      icalcomponent_add_property (ical,
+				  icalproperty_new_status (status));
+    }
+
+  e_cal_modify_object (esource, ical, CALOBJ_MOD_ALL, NULL);
+}
diff --git a/src/calendar-client/calendar-client.h b/src/calendar-client/calendar-client.h
new file mode 100644
index 0000000..3ae3b2f
--- /dev/null
+++ b/src/calendar-client/calendar-client.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2004 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Authors:
+ *     Mark McLoughlin  <mark skynet ie>
+ *     William Jon McCann  <mccann jhu edu>
+ *     Martin Grimme  <martin pycage de>
+ *     Christian Kellner  <gicmo xatom net>
+ */
+
+#ifndef __CALENDAR_CLIENT_H__
+#define __CALENDAR_CLIENT_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+  CALENDAR_EVENT_APPOINTMENT = 1 << 0,
+  CALENDAR_EVENT_TASK        = 1 << 1,
+  CALENDAR_EVENT_ALL         = (1 << 2) - 1
+} CalendarEventType;
+
+#define CALENDAR_TYPE_CLIENT        (calendar_client_get_type ())
+#define CALENDAR_CLIENT(o)          (G_TYPE_CHECK_INSTANCE_CAST ((o), CALENDAR_TYPE_CLIENT, CalendarClient))
+#define CALENDAR_CLIENT_CLASS(k)    (G_TYPE_CHECK_CLASS_CAST ((k), CALENDAR_TYPE_CLIENT, CalendarClientClass))
+#define CALENDAR_IS_CLIENT(o)       (G_TYPE_CHECK_INSTANCE_TYPE ((o), CALENDAR_TYPE_CLIENT))
+#define CALENDAR_IS_CLIENT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CALENDAR_TYPE_CLIENT))
+#define CALENDAR_CLIENT_GET_CLASS(o)(G_TYPE_INSTANCE_GET_CLASS ((o), CALENDAR_TYPE_CLIENT, CalendarClientClass))
+
+typedef struct _CalendarClient        CalendarClient;
+typedef struct _CalendarClientClass   CalendarClientClass;
+typedef struct _CalendarClientPrivate CalendarClientPrivate;
+
+struct _CalendarClient
+{
+  GObject                parent;
+  CalendarClientPrivate *priv;
+};
+
+struct _CalendarClientClass
+{
+  GObjectClass    parent_class;
+
+  void         (* appointments_changed) (CalendarClient *client);
+  void         (* tasks_changed)        (CalendarClient *client);
+};
+
+
+typedef struct
+{
+  time_t start_time;
+  time_t end_time;
+} CalendarOccurrence;
+
+typedef struct
+{
+  char   *uid;
+  char   *rid;
+  char   *uri;
+  char   *summary;
+  char   *description;
+  char   *color_string;
+  time_t  start_time;
+  time_t  end_time;
+  guint   is_all_day : 1;
+
+  /* Only used internally */
+  GSList *occurrences;
+} CalendarAppointment;
+
+typedef struct
+{
+  char  *uid;
+  char  *summary;
+  char  *description;
+  char  *color_string;
+  char  *url;
+  time_t start_time;
+  time_t due_time;
+  guint  percent_complete;
+  time_t completed_time;
+  int    priority;
+} CalendarTask;
+
+typedef struct
+{
+  union
+  {
+    CalendarAppointment appointment;
+    CalendarTask        task;
+  } event;
+  CalendarEventType type;
+} CalendarEvent;
+
+#define CALENDAR_EVENT(e)       ((CalendarEvent *)(e))
+#define CALENDAR_APPOINTMENT(e) ((CalendarAppointment *)(e))
+#define CALENDAR_TASK(e)        ((CalendarTask *)(e))
+
+typedef void (* CalendarDayIter) (CalendarClient *client,
+				  guint           day,
+				  gpointer        user_data);
+
+
+GType           calendar_client_get_type                (void) G_GNUC_CONST;
+CalendarClient *calendar_client_new                     (void);
+
+void            calendar_client_get_date                (CalendarClient      *client,
+							 guint               *year,
+                                                         guint               *month,
+							 guint               *day);
+void            calendar_client_select_month            (CalendarClient      *client,
+							 guint                month,
+							 guint                year);
+void            calendar_client_select_day              (CalendarClient      *client,
+							 guint                day);
+
+GSList         *calendar_client_get_events              (CalendarClient      *client,
+							 CalendarEventType    event_mask);
+void            calendar_client_foreach_appointment_day (CalendarClient      *client,
+							 CalendarDayIter      iter_func,
+							 gpointer             user_data);
+
+void            calendar_client_set_task_completed       (CalendarClient     *client,
+							  char               *task_uid,
+							  gboolean            task_completed,
+							  guint               percent_complete);
+
+void calendar_event_free (CalendarEvent *event);
+
+G_END_DECLS
+
+#endif /* __CALENDAR_CLIENT_H__ */
diff --git a/src/calendar-client/calendar-debug.h b/src/calendar-client/calendar-debug.h
new file mode 100644
index 0000000..61ad156
--- /dev/null
+++ b/src/calendar-client/calendar-debug.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2004 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Authors:
+ *     Mark McLoughlin  <mark skynet ie>
+ */
+
+#ifndef __CALENDAR_DEBUG_H__
+#define __CALENDAR_DEBUG_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#ifdef CALENDAR_ENABLE_DEBUG
+
+#include <stdio.h>
+
+#ifdef G_HAVE_ISO_VARARGS
+#  define dprintf(...) fprintf (stderr, __VA_ARGS__);
+#elif defined(G_HAVE_GNUC_VARARGS)
+#  define dprintf(args...) fprintf (stderr, args);
+#endif
+
+#else /* if !defined (CALENDAR_DEBUG) */
+
+#ifdef G_HAVE_ISO_VARARGS
+#  define dprintf(...)
+#elif defined(G_HAVE_GNUC_VARARGS)
+#  define dprintf(args...)
+#endif
+
+#endif /* CALENDAR_ENABLE_DEBUG */
+
+G_END_DECLS
+
+#endif /* __CALENDAR_DEBUG_H__ */
diff --git a/src/calendar-client/calendar-sources.c b/src/calendar-client/calendar-sources.c
new file mode 100644
index 0000000..fa1fcac
--- /dev/null
+++ b/src/calendar-client/calendar-sources.c
@@ -0,0 +1,658 @@
+/*
+ * Copyright (C) 2004 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Authors:
+ *     Mark McLoughlin  <mark skynet ie>
+ *     William Jon McCann  <mccann jhu edu>
+ *     Martin Grimme  <martin pycage de>
+ *     Christian Kellner  <gicmo xatom net>
+ */
+
+#include <config.h>
+
+#include "calendar-sources.h"
+
+#include <libintl.h>
+#include <string.h>
+#include <gconf/gconf-client.h>
+#define HANDLE_LIBICAL_MEMORY
+#include <libecal/e-cal.h>
+#include <libedataserver/e-source-list.h>
+#include <libedataserverui/e-passwords.h>
+
+#undef CALENDAR_ENABLE_DEBUG
+#include "calendar-debug.h"
+
+#ifndef _
+#define _(x) gettext(x)
+#endif
+
+#ifndef N_
+#define N_(x) x
+#endif
+
+#define CALENDAR_SOURCES_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CALENDAR_TYPE_SOURCES, CalendarSourcesPrivate))
+
+#define CALENDAR_SOURCES_EVO_DIR                          "/apps/evolution"
+#define CALENDAR_SOURCES_APPOINTMENT_SOURCES_KEY          CALENDAR_SOURCES_EVO_DIR "/calendar/sources"
+#define CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_DIR CALENDAR_SOURCES_EVO_DIR "/calendar/display"
+#define CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_KEY CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_DIR "/selected_calendars"
+#define CALENDAR_SOURCES_TASK_SOURCES_KEY                 CALENDAR_SOURCES_EVO_DIR "/tasks/sources"
+#define CALENDAR_SOURCES_SELECTED_TASK_SOURCES_DIR        CALENDAR_SOURCES_EVO_DIR "/calendar/tasks"
+#define CALENDAR_SOURCES_SELECTED_TASK_SOURCES_KEY        CALENDAR_SOURCES_SELECTED_TASK_SOURCES_DIR "/selected_tasks"
+
+typedef struct _CalendarSourceData CalendarSourceData;
+
+struct _CalendarSourceData
+{
+  ECalSourceType   source_type;
+  CalendarSources *sources;
+  guint            changed_signal;
+
+  GSList          *clients;
+  GSList          *selected_sources;
+  ESourceList     *esource_list;
+
+  guint            selected_sources_listener;
+  char            *selected_sources_dir;
+
+  guint            timeout_id;
+
+  guint            loaded : 1;
+};
+
+struct _CalendarSourcesPrivate
+{
+  CalendarSourceData  appointment_sources;
+  CalendarSourceData  task_sources;
+
+  GConfClient        *gconf_client;
+};
+
+static void calendar_sources_class_init (CalendarSourcesClass *klass);
+static void calendar_sources_init       (CalendarSources      *sources);
+static void calendar_sources_finalize   (GObject             *object);
+
+static void backend_died_cb (ECal *client, CalendarSourceData *source_data);
+static void calendar_sources_esource_list_changed (ESourceList        *source_list,
+                                                   CalendarSourceData *source_data);
+
+enum
+{
+  APPOINTMENT_SOURCES_CHANGED,
+  TASK_SOURCES_CHANGED,
+  LAST_SIGNAL
+};
+static guint signals [LAST_SIGNAL] = { 0, };
+
+static GObjectClass    *parent_class = NULL;
+static CalendarSources *calendar_sources_singleton = NULL;
+
+GType
+calendar_sources_get_type (void)
+{
+  static GType sources_type = 0;
+  
+  if (!sources_type)
+    {
+      static const GTypeInfo sources_info =
+      {
+	sizeof (CalendarSourcesClass),
+	NULL,		/* base_init */
+	NULL,		/* base_finalize */
+	(GClassInitFunc) calendar_sources_class_init,
+	NULL,           /* class_finalize */
+	NULL,		/* class_data */
+	sizeof (CalendarSources),
+	0,		/* n_preallocs */
+	(GInstanceInitFunc) calendar_sources_init,
+      };
+      
+      sources_type = g_type_register_static (G_TYPE_OBJECT,
+					     "CalendarSources",
+					     &sources_info, 0);
+    }
+  
+  return sources_type;
+}
+
+static void
+calendar_sources_class_init (CalendarSourcesClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+
+  parent_class = g_type_class_peek_parent (klass);
+
+  gobject_class->finalize = calendar_sources_finalize;
+
+  g_type_class_add_private (klass, sizeof (CalendarSourcesPrivate));
+
+  signals [APPOINTMENT_SOURCES_CHANGED] =
+    g_signal_new ("appointment-sources-changed",
+		  G_TYPE_FROM_CLASS (gobject_class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (CalendarSourcesClass,
+				   appointment_sources_changed),
+		  NULL,
+		  NULL,
+		  g_cclosure_marshal_VOID__VOID,
+		  G_TYPE_NONE,
+		  0);
+
+  signals [TASK_SOURCES_CHANGED] =
+    g_signal_new ("task-sources-changed",
+		  G_TYPE_FROM_CLASS (gobject_class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (CalendarSourcesClass,
+				   task_sources_changed),
+		  NULL,
+		  NULL,
+		  g_cclosure_marshal_VOID__VOID,
+		  G_TYPE_NONE,
+		  0);
+}
+
+static void
+calendar_sources_init (CalendarSources *sources)
+{
+  sources->priv = CALENDAR_SOURCES_GET_PRIVATE (sources);
+
+  sources->priv->appointment_sources.source_type    = E_CAL_SOURCE_TYPE_EVENT;
+  sources->priv->appointment_sources.sources        = sources;
+  sources->priv->appointment_sources.changed_signal = signals [APPOINTMENT_SOURCES_CHANGED];
+  sources->priv->appointment_sources.timeout_id     = 0;
+
+  sources->priv->task_sources.source_type    = E_CAL_SOURCE_TYPE_TODO;
+  sources->priv->task_sources.sources        = sources;
+  sources->priv->task_sources.changed_signal = signals [TASK_SOURCES_CHANGED];
+  sources->priv->task_sources.timeout_id     = 0;
+
+  sources->priv->gconf_client = gconf_client_get_default ();
+}
+
+static void
+calendar_sources_finalize_source_data (CalendarSources    *sources,
+				       CalendarSourceData *source_data)
+{
+  if (source_data->loaded)
+    {
+      GSList *l;
+
+      if (source_data->selected_sources_dir)
+	{
+	  gconf_client_remove_dir (sources->priv->gconf_client,
+				   source_data->selected_sources_dir,
+				   NULL);
+
+	  g_free (source_data->selected_sources_dir);
+	  source_data->selected_sources_dir = NULL;
+	}
+
+      if (source_data->selected_sources_listener)
+	{
+	  gconf_client_notify_remove (sources->priv->gconf_client,
+				      source_data->selected_sources_listener);
+	  source_data->selected_sources_listener = 0;
+	}
+
+      for (l = source_data->clients; l; l = l->next)
+        {
+          g_signal_handlers_disconnect_by_func (G_OBJECT (l->data),
+                                                G_CALLBACK (backend_died_cb),
+                                                source_data);
+          g_object_unref (l->data);
+        }
+      g_slist_free (source_data->clients);
+      source_data->clients = NULL;
+
+      if (source_data->esource_list)
+        {
+          g_signal_handlers_disconnect_by_func (source_data->esource_list,
+                                                G_CALLBACK (calendar_sources_esource_list_changed),
+                                                source_data);
+          g_object_unref (source_data->esource_list);
+	}
+      source_data->esource_list = NULL;
+
+      for (l = source_data->selected_sources; l; l = l->next)
+	g_free (l->data);
+      g_slist_free (source_data->selected_sources);
+      source_data->selected_sources = NULL;
+
+      if (source_data->timeout_id != 0)
+        {
+          g_source_remove (source_data->timeout_id);
+          source_data->timeout_id = 0;
+        }
+
+      source_data->loaded = FALSE;
+    }
+}
+
+static void
+calendar_sources_finalize (GObject *object)
+{
+  CalendarSources *sources = CALENDAR_SOURCES (object);
+
+  calendar_sources_finalize_source_data (sources, &sources->priv->appointment_sources);
+  calendar_sources_finalize_source_data (sources, &sources->priv->task_sources);
+
+  if (sources->priv->gconf_client)
+    g_object_unref (sources->priv->gconf_client);
+  sources->priv->gconf_client = NULL;
+
+  if (G_OBJECT_CLASS (parent_class)->finalize)
+    G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+CalendarSources *
+calendar_sources_get (void)
+{
+  gpointer singleton_location = &calendar_sources_singleton;
+
+  if (calendar_sources_singleton)
+    return g_object_ref (calendar_sources_singleton);
+
+  calendar_sources_singleton = g_object_new (CALENDAR_TYPE_SOURCES, NULL);
+  g_object_add_weak_pointer (G_OBJECT (calendar_sources_singleton),
+			     singleton_location);
+
+  return calendar_sources_singleton;
+}
+
+static gboolean
+is_source_selected (ESource *esource,
+		    GSList  *selected_sources)
+{
+  const char *uid;
+  GSList     *l;
+
+  uid = e_source_peek_uid (esource);
+
+  for (l = selected_sources; l; l = l->next)
+    {
+      const char *source = l->data;
+
+      if (!strcmp (source, uid))
+	return TRUE;
+    }
+
+  return FALSE;
+}
+
+static char *
+auth_func_cb (ECal       *ecal,
+	      const char *prompt,
+	      const char *key,
+	      gpointer    user_data)
+{
+	ESource *source;
+	const gchar *auth_domain;
+	const gchar *component_name;
+
+	source = e_cal_get_source (ecal);
+	auth_domain = e_source_get_property (source, "auth-domain");
+	component_name = auth_domain ? auth_domain : "Calendar";
+
+	return e_passwords_get_password (component_name, key);
+}
+
+/* The clients are just created here but not loaded */
+static ECal *
+get_ecal_from_source (ESource        *esource,
+		      ECalSourceType  source_type,
+		      GSList         *existing_clients)
+{
+  ECal *retval;
+
+  if (existing_clients)
+    {
+      GSList *l;
+
+      for (l = existing_clients; l; l = l->next)
+	{
+	  ECal *client = E_CAL (l->data);
+
+	  if (e_source_equal (esource, e_cal_get_source (client)))
+	    {
+	      dprintf ("        load_esource: found existing source ... returning that\n");
+
+	      return g_object_ref (client);
+	    }
+	}
+    }
+
+  retval = e_cal_new (esource, source_type);
+  if (!retval)
+    {
+      g_warning ("Could not load source '%s' from '%s'\n",
+		 e_source_peek_name (esource),
+		 e_source_peek_relative_uri (esource));
+      return NULL;
+    }
+
+  e_cal_set_auth_func (retval, auth_func_cb, NULL);
+
+  return retval;
+}
+
+/* - Order doesn't matter
+ * - Can just compare object pointers since we
+ *   re-use client connections
+ */
+static gboolean
+compare_ecal_lists (GSList *a,
+		    GSList *b)
+{
+  GSList *l;
+
+  if (g_slist_length (a) != g_slist_length (b))
+    return FALSE;
+
+  for (l = a; l; l = l->next)
+    {
+      if (!g_slist_find (b, l->data))
+	return FALSE;
+    }
+
+  return TRUE;
+}
+
+static inline void
+debug_dump_selected_sources (GSList *selected_sources)
+{
+#ifdef CALENDAR_ENABLE_DEBUG
+  GSList *l;
+
+  dprintf ("Selected sources:\n");
+  for (l = selected_sources; l; l = l->next)
+    {
+      char *source = l->data;
+
+      dprintf ("  %s\n", source);
+    }
+  dprintf ("\n");
+#endif
+}
+
+static inline void
+debug_dump_ecal_list (GSList *ecal_list)
+{
+#ifdef CALENDAR_ENABLE_DEBUG
+  GSList *l;
+
+  dprintf ("Loaded clients:\n");
+  for (l = ecal_list; l; l = l->next)
+    {
+      ECal    *client = l->data;
+      ESource *source = e_cal_get_source (client);
+
+      dprintf ("  %s %s %s\n",
+	       e_source_peek_uid (source),
+	       e_source_peek_name (source),
+	       e_cal_get_uri (client));
+    }
+#endif
+}
+
+static void
+calendar_sources_load_esource_list (CalendarSourceData *source_data);
+
+static gboolean
+backend_restart (gpointer data)
+{
+  CalendarSourceData *source_data = data;
+
+  calendar_sources_load_esource_list (source_data);
+
+  source_data->timeout_id = 0;
+    
+  return FALSE;
+}
+
+static void
+backend_died_cb (ECal *client, CalendarSourceData *source_data)
+{
+  const char *uristr;
+
+  source_data->clients = g_slist_remove (source_data->clients, client);
+  if (g_slist_length (source_data->clients) < 1) 
+    {
+      g_slist_free (source_data->clients);
+      source_data->clients = NULL;
+    }
+  uristr = e_cal_get_uri (client);
+  g_warning ("The calendar backend for %s has crashed.", uristr);
+
+  if (source_data->timeout_id != 0)
+    {
+      g_source_remove (source_data->timeout_id);
+      source_data->timeout_id = 0;
+    }
+
+  source_data->timeout_id = g_timeout_add_seconds (2, backend_restart,
+		  				   source_data);
+}
+
+static void
+calendar_sources_load_esource_list (CalendarSourceData *source_data)
+{
+  GSList  *clients = NULL;
+  GSList  *groups, *l;
+  gboolean emit_signal = FALSE;
+
+  g_return_if_fail (source_data->esource_list != NULL);
+
+  debug_dump_selected_sources (source_data->selected_sources);
+
+  dprintf ("Source groups:\n");
+  groups = e_source_list_peek_groups (source_data->esource_list);
+  for (l = groups; l; l = l->next)
+    {
+      GSList *esources, *s;
+
+      dprintf ("  %s\n", e_source_group_peek_uid (l->data));
+      dprintf ("    sources:\n");
+
+      esources = e_source_group_peek_sources (l->data);
+      for (s = esources; s; s = s->next)
+	{
+	  ESource *esource = E_SOURCE (s->data);
+	  ECal    *client;
+
+	  dprintf ("      type = '%s' uid = '%s', name = '%s', relative uri = '%s': \n",
+                   source_data->source_type == E_CAL_SOURCE_TYPE_EVENT ? "appointment" : "task",
+		   e_source_peek_uid (esource),
+		   e_source_peek_name (esource),
+		   e_source_peek_relative_uri (esource));
+
+	  if (is_source_selected (esource, source_data->selected_sources) &&
+	      (client = get_ecal_from_source (esource, source_data->source_type, source_data->clients)))
+	    {
+	      clients = g_slist_prepend (clients, client);
+	    }
+	}
+    }
+  dprintf ("\n");
+
+  if (source_data->loaded && 
+      !compare_ecal_lists (source_data->clients, clients))
+    emit_signal = TRUE;
+
+  for (l = source_data->clients; l; l = l->next)
+    {
+      g_signal_handlers_disconnect_by_func (G_OBJECT (l->data),
+                                            G_CALLBACK (backend_died_cb),
+                                            source_data);
+
+      g_object_unref (l->data);
+    }
+  g_slist_free (source_data->clients);
+  source_data->clients = g_slist_reverse (clients);
+
+  /* connect to backend_died after we disconnected the previous signal
+   * handlers. If we do it before, we'll lose some handlers (for clients that
+   * were already there before) */
+  for (l = source_data->clients; l; l = l->next)
+    {
+      g_signal_connect (G_OBJECT (l->data), "backend_died",
+                        G_CALLBACK (backend_died_cb), source_data);
+    }
+
+  if (emit_signal) 
+    {
+      dprintf ("Emitting %s-sources-changed signal\n",
+	       source_data->source_type == E_CAL_SOURCE_TYPE_EVENT ? "appointment" : "task");
+      g_signal_emit (source_data->sources, source_data->changed_signal, 0);
+    }
+
+  debug_dump_ecal_list (source_data->clients);
+}
+
+static void
+calendar_sources_esource_list_changed (ESourceList        *source_list,
+				       CalendarSourceData *source_data)
+				       
+{
+  dprintf ("ESourceList changed, reloading\n");
+
+  calendar_sources_load_esource_list (source_data);
+}
+
+static void
+calendar_sources_selected_sources_notify (GConfClient        *client,
+					  guint               cnx_id,
+					  GConfEntry         *entry,
+					  CalendarSourceData *source_data)
+{
+  GSList *l;
+
+  if (!entry->value ||
+      entry->value->type != GCONF_VALUE_LIST ||
+      gconf_value_get_list_type (entry->value) != GCONF_VALUE_STRING)
+    return;
+
+  dprintf ("Selected sources key (%s) changed, reloading\n", entry->key);
+
+  for (l = source_data->selected_sources; l; l = l->next)
+    g_free (l->data);
+  source_data->selected_sources = NULL;
+
+  for (l = gconf_value_get_list (entry->value); l; l = l->next)
+    {
+      const char *source = gconf_value_get_string (l->data);
+
+      source_data->selected_sources = 
+	g_slist_prepend (source_data->selected_sources,
+			 g_strdup (source));
+    }
+  source_data->selected_sources =
+    g_slist_reverse (source_data->selected_sources);
+
+  calendar_sources_load_esource_list (source_data);
+}
+
+static void
+calendar_sources_load_sources (CalendarSources    *sources,
+			       CalendarSourceData *source_data,
+			       const char         *sources_key,
+			       const char         *selected_sources_key,
+			       const char         *selected_sources_dir)
+{
+  GConfClient *gconf_client;
+  GError      *error;
+
+  dprintf ("---------------------------\n");
+  dprintf ("Loading sources:\n");
+  dprintf ("  sources_key: %s\n", sources_key);
+  dprintf ("  selected_sources_key: %s\n", selected_sources_key);
+  dprintf ("  selected_sources_dir: %s\n", selected_sources_dir);
+
+  gconf_client = sources->priv->gconf_client;
+
+  error = NULL;
+  source_data->selected_sources = gconf_client_get_list (gconf_client,
+							 selected_sources_key,
+							 GCONF_VALUE_STRING,
+							 &error);
+  if (error)
+    {
+      g_warning ("Failed to get selected sources from '%s': %s\n",
+		 selected_sources_key,
+		 error->message);
+      g_error_free (error);
+      return;
+    }
+
+  gconf_client_add_dir (gconf_client,
+			selected_sources_dir,
+			GCONF_CLIENT_PRELOAD_NONE,
+			NULL);
+  source_data->selected_sources_dir = g_strdup (selected_sources_dir);
+
+  source_data->selected_sources_listener =
+    gconf_client_notify_add (gconf_client,
+			     selected_sources_dir,
+			     (GConfClientNotifyFunc) calendar_sources_selected_sources_notify,
+			     source_data, NULL, NULL);
+
+  source_data->esource_list = e_source_list_new_for_gconf (gconf_client, sources_key);
+  g_signal_connect (source_data->esource_list, "changed",
+		    G_CALLBACK (calendar_sources_esource_list_changed),
+		    source_data);
+
+  calendar_sources_load_esource_list (source_data);
+
+  source_data->loaded = TRUE;
+
+  dprintf ("---------------------------\n");
+}
+
+GSList *
+calendar_sources_get_appointment_sources (CalendarSources *sources)
+{
+  g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL);
+
+  if (!sources->priv->appointment_sources.loaded)
+    {
+      calendar_sources_load_sources (sources,
+				     &sources->priv->appointment_sources,
+				     CALENDAR_SOURCES_APPOINTMENT_SOURCES_KEY,
+				     CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_KEY,
+				     CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_DIR);
+    }
+  
+  return sources->priv->appointment_sources.clients;
+}
+
+GSList *
+calendar_sources_get_task_sources (CalendarSources *sources)
+{
+  g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL);
+
+  if (!sources->priv->task_sources.loaded)
+    {
+      calendar_sources_load_sources (sources,
+				     &sources->priv->task_sources,
+				     CALENDAR_SOURCES_TASK_SOURCES_KEY,
+				     CALENDAR_SOURCES_SELECTED_TASK_SOURCES_KEY,
+				     CALENDAR_SOURCES_SELECTED_TASK_SOURCES_DIR);
+    }
+
+  return sources->priv->task_sources.clients;
+}
diff --git a/src/calendar-client/calendar-sources.h b/src/calendar-client/calendar-sources.h
new file mode 100644
index 0000000..52741ab
--- /dev/null
+++ b/src/calendar-client/calendar-sources.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2004 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Authors:
+ *     Mark McLoughlin  <mark skynet ie>
+ *     William Jon McCann  <mccann jhu edu>
+ *     Martin Grimme  <martin pycage de>
+ *     Christian Kellner  <gicmo xatom net>
+ */
+
+#ifndef __CALENDAR_SOURCES_H__
+#define __CALENDAR_SOURCES_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define CALENDAR_TYPE_SOURCES        (calendar_sources_get_type ())
+#define CALENDAR_SOURCES(o)          (G_TYPE_CHECK_INSTANCE_CAST ((o), CALENDAR_TYPE_SOURCES, CalendarSources))
+#define CALENDAR_SOURCES_CLASS(k)    (G_TYPE_CHECK_CLASS_CAST ((k), CALENDAR_TYPE_SOURCES, CalendarSourcesClass))
+#define CALENDAR_IS_SOURCES(o)       (G_TYPE_CHECK_INSTANCE_TYPE ((o), CALENDAR_TYPE_SOURCES))
+#define CALENDAR_IS_SOURCES_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CALENDAR_TYPE_SOURCES))
+#define CALENDAR_SOURCES_GET_CLASS(o)(G_TYPE_INSTANCE_GET_CLASS ((o), CALENDAR_TYPE_SOURCES, CalendarSourcesClass))
+
+typedef struct _CalendarSources        CalendarSources;
+typedef struct _CalendarSourcesClass   CalendarSourcesClass;
+typedef struct _CalendarSourcesPrivate CalendarSourcesPrivate;
+
+struct _CalendarSources
+{
+  GObject                 parent;
+  CalendarSourcesPrivate *priv;
+};
+
+struct _CalendarSourcesClass
+{
+  GObjectClass    parent_class;
+
+  void         (* appointment_sources_changed) (CalendarSources *sources);
+  void         (* task_sources_changed)        (CalendarSources *sources);
+};
+
+
+GType            calendar_sources_get_type                (void) G_GNUC_CONST;
+CalendarSources *calendar_sources_get                     (void);
+GSList          *calendar_sources_get_appointment_sources (CalendarSources *sources);
+GSList          *calendar_sources_get_task_sources        (CalendarSources *sources);
+
+G_END_DECLS
+
+#endif /* __CALENDAR_SOURCES_H__ */
diff --git a/src/shell-evolution-event-source.c b/src/shell-evolution-event-source.c
index a4efa0b..3c080e2 100644
--- a/src/shell-evolution-event-source.c
+++ b/src/shell-evolution-event-source.c
@@ -2,6 +2,7 @@
 
 #include "config.h"
 
+#include "calendar-client/calendar-client.h"
 #include "shell-evolution-event-source.h"
 
 
@@ -12,7 +13,10 @@ struct _ShellEvolutionEventSourceClass
 
 struct _ShellEvolutionEventSource {
   GObject parent;
-
+  CalendarClient *client;
+  /* The month that we are currently requesting events from */
+  gint req_year;
+  gint req_mon; /* starts at 1, not zero */
 };
 
 /* Signals */
@@ -27,13 +31,52 @@ static guint signals[LAST_SIGNAL] = { 0 };
 G_DEFINE_TYPE (ShellEvolutionEventSource, shell_evolution_event_source, G_TYPE_OBJECT);
 
 static void
+on_tasks_changed (CalendarClient *client,
+                  gpointer        user_data)
+{
+  ShellEvolutionEventSource *source = SHELL_EVOLUTION_EVENT_SOURCE (user_data);
+  /* g_print ("on tasks changed\n"); */
+  g_signal_emit (source, signals[CHANGED_SIGNAL], 0);
+}
+
+static void
+on_appointments_changed (CalendarClient *client,
+                         gpointer        user_data)
+{
+  ShellEvolutionEventSource *source = SHELL_EVOLUTION_EVENT_SOURCE (user_data);
+  /* g_print ("on appointments changed\n"); */
+  g_signal_emit (source, signals[CHANGED_SIGNAL], 0);
+}
+
+static void
 shell_evolution_event_source_init (ShellEvolutionEventSource *source)
 {
+  source->client = calendar_client_new ();
+  g_signal_connect (source->client,
+                    "tasks-changed",
+                    G_CALLBACK (on_tasks_changed),
+                    source);
+  g_signal_connect (source->client,
+                    "appointments-changed",
+                    G_CALLBACK (on_appointments_changed),
+                    source);
+}
+
+static void
+shell_evolution_event_source_finalize (GObject *object)
+{
+  ShellEvolutionEventSource *source = SHELL_EVOLUTION_EVENT_SOURCE (object);
+  g_object_unref (source->client);
+  G_OBJECT_CLASS (shell_evolution_event_source_parent_class)->finalize (object);
 }
 
 static void
 shell_evolution_event_source_class_init (ShellEvolutionEventSourceClass *klass)
 {
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize = shell_evolution_event_source_finalize;
+
   signals[CHANGED_SIGNAL] =
     g_signal_new ("changed",
                   G_TYPE_FROM_CLASS (klass),
@@ -51,36 +94,118 @@ shell_evolution_event_source_new (void)
   return SHELL_EVOLUTION_EVENT_SOURCE (g_object_new (SHELL_TYPE_EVOLUTION_EVENT_SOURCE, NULL));
 }
 
+void
+shell_evolution_event_source_request_range (ShellEvolutionEventSource *source,
+                                            gint64                     msec_begin,
+                                            gint64                     msec_end)
+{
+  GDateTime *middle;
+
+  /* The CalendarClient type is a convenience wrapper on top of
+   * Evolution Data Server. It is based on the assumption that only
+   * a single month is shown at a time.
+   *
+   * To avoid reimplemting all the work already done in CalendarClient
+   * we make the same assumption. This means that we only show events
+   * in the month that is in the middle of @msec_begin and
+   * @msec_end. Since the Shell displays a month at a time (plus the
+   * days before and after) it works out just fine.
+   */
+
+  middle = g_date_time_new_from_unix_utc ((msec_begin + msec_end) / 2 / 1000);
+  g_date_time_get_ymd (middle, &source->req_year, &source->req_mon, NULL);
+  g_date_time_unref (middle);
+  calendar_client_select_month (source->client, source->req_mon - 1, source->req_year);
+}
+
+static gint
+event_cmp (gconstpointer a,
+           gconstpointer b)
+{
+  const ShellEvolutionEvent *ea;
+  const ShellEvolutionEvent *eb;
+
+  ea = a;
+  eb = b;
+  if (ea->msec_begin < eb->msec_begin)
+    return -1;
+  else if (ea->msec_begin > eb->msec_begin)
+    return 1;
+  else
+    return 0;
+}
+
 /**
  * shell_evolution_event_source_get_events:
  * @source: A #ShellEvolutionEventSource.
- * @date_begin: Start date (milli-seconds since Epoch).
- * @date_end: End date (milli-seconds since Epoch).
+ * @msec_begin: Start date (milli-seconds since Epoch).
+ * @msec_end: End date (milli-seconds since Epoch).
  *
- * Gets all events that occur between @date_begin and @date_end.
+ * Gets all events that occur between @msec_begin and @msec_end.
  *
  * Returns: (element-type ShellEvolutionEvent) (transfer full): List of events.
  */
 GList *
 shell_evolution_event_source_get_events  (ShellEvolutionEventSource *source,
-                                          gint64                     date_begin,
-                                          gint64                     date_end)
+                                          gint64                     msec_begin,
+                                          gint64                     msec_end)
 {
   GList *result;
+  GDateTime *cur_date;
+  GDateTime *begin_date;
+  GDateTime *end_date;
 
-  g_print ("get_events\n");
-  g_print (" date_begin = %" G_GINT64_FORMAT "\n", date_begin);
-  g_print (" date_end = %" G_GINT64_FORMAT "\n", date_end);
+  g_return_val_if_fail (msec_begin <= msec_end, NULL);
 
   result = NULL;
 
-  gint64 event_time = 1295931631000 + 32 * 3600 * 1000;
-  if (event_time >= date_begin && event_time <= date_end)
+  begin_date = g_date_time_new_from_unix_utc (msec_begin / 1000);
+  end_date = g_date_time_new_from_unix_utc (msec_end / 1000);
+  cur_date = g_date_time_ref (begin_date);
+  do
     {
-      ShellEvolutionEvent *event;
-      event = shell_evolution_event_new ("Stuff", FALSE, event_time);
-      result = g_list_prepend (result, event);
+      gint year, mon, day;
+      GDateTime *next_date;
+
+      g_date_time_get_ymd (cur_date, &year, &mon, &day);
+      /* g_print ("y=%04d m=%02d d=%02d: ", year, mon, day); */
+
+      /* Silently drop events not in range (see comment in
+       * shell_evolution_event_source_request_range() above)
+       */
+      if (!(year == source->req_year && mon == source->req_mon))
+        {
+          /* g_print ("skipping day\n"); */
+        }
+      else
+        {
+          GSList *events;
+          GSList *l;
+          calendar_client_select_day (source->client, day);
+          events = calendar_client_get_events (source->client, CALENDAR_EVENT_APPOINTMENT);
+          /* g_print ("num_events: %d\n", g_slist_length (events)); */
+          for (l = events; l; l = l->next)
+            {
+              CalendarAppointment *appointment = l->data;
+              ShellEvolutionEvent *event;
+              event = shell_evolution_event_new (appointment->summary,
+                                                 appointment->is_all_day,
+                                                 appointment->start_time * G_GINT64_CONSTANT (1000));
+              result = g_list_prepend (result, event);
+            }
+          g_slist_foreach (events, (GFunc) calendar_event_free, NULL);
+          g_slist_free (events);
+        }
+
+      next_date = g_date_time_add_days (cur_date, 1);
+      g_date_time_unref (cur_date);
+      cur_date = next_date;
     }
+  while (g_date_time_difference (end_date, cur_date) > 0);
+  g_date_time_unref (begin_date);
+  g_date_time_unref (end_date);
+
+  result = g_list_sort (result, event_cmp);
 
   return result;
 }
@@ -109,12 +234,12 @@ shell_evolution_event_copy (ShellEvolutionEvent *event)
 ShellEvolutionEvent *
 shell_evolution_event_new (const gchar *summary,
                            gboolean     all_day,
-                           gint64       date)
+                           gint64       msec_begin)
 {
   ShellEvolutionEvent *event;
   event = g_new0 (ShellEvolutionEvent, 1);
   event->summary = g_strdup (summary);
   event->all_day = all_day;
-  event->date = date;
+  event->msec_begin = msec_begin;
   return event;
 }
diff --git a/src/shell-evolution-event-source.h b/src/shell-evolution-event-source.h
index 0fe9b80..4866b45 100644
--- a/src/shell-evolution-event-source.h
+++ b/src/shell-evolution-event-source.h
@@ -12,13 +12,13 @@ struct _ShellEvolutionEvent
 {
   gchar    *summary;
   gboolean  all_day;
-  gint64    date;
+  gint64    msec_begin;
 };
 
 GType                shell_evolution_event_get_type (void) G_GNUC_CONST;
 ShellEvolutionEvent *shell_evolution_event_new      (const gchar *summary,
                                                      gboolean     all_day,
-                                                     gint64       date);
+                                                     gint64       msec_begin);
 ShellEvolutionEvent *shell_evolution_event_copy     (ShellEvolutionEvent *event);
 void                 shell_evolution_event_free     (ShellEvolutionEvent *event);
 
@@ -32,11 +32,14 @@ typedef struct _ShellEvolutionEventSourceClass ShellEvolutionEventSourceClass;
 #define SHELL_IS_EVOLUTION_EVENT_SOURCE_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_EVOLUTION_EVENT_SOURCE))
 #define SHELL_EVOLUTION_EVENT_SOURCE_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_EVOLUTION_EVENT_SOURCE, ShellEvolutionEventSourceClass))
 
-GType                      shell_evolution_event_source_get_type    (void) G_GNUC_CONST;
-ShellEvolutionEventSource *shell_evolution_event_source_new         (void);
-GList                     *shell_evolution_event_source_get_events  (ShellEvolutionEventSource *source,
-                                                                     gint64                     date_begin,
-                                                                     gint64                     date_end);
+GType                      shell_evolution_event_source_get_type      (void) G_GNUC_CONST;
+ShellEvolutionEventSource *shell_evolution_event_source_new           (void);
+void                       shell_evolution_event_source_request_range (ShellEvolutionEventSource *source,
+                                                                       gint64                     msec_begin,
+                                                                       gint64                     msec_end);
+GList                     *shell_evolution_event_source_get_events    (ShellEvolutionEventSource *source,
+                                                                       gint64                     msec_begin,
+                                                                       gint64                     msec_end);
 G_END_DECLS
 
 #endif /* __SHELL_EVOLUTION_EVENT_SOURCE_H__ */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]