diff --git a/configure.ac b/configure.ac index c3dc6397e..8155bae4a 100644 --- a/configure.ac +++ b/configure.ac @@ -66,10 +66,10 @@ GJS_MIN_VERSION=0.7.11 MUTTER_MIN_VERSION=2.91.90 GTK_MIN_VERSION=3.0.0 GIO_MIN_VERSION=2.25.9 -LIBECAL_REQUIRED=1.6.0 -LIBEDATASERVER_REQUIRED=1.2.0 -LIBEDATASERVERUI2_REQUIRED=1.2.0 -LIBEDATASERVERUI3_REQUIRED=2.91.6 +LIBECAL_MIN_VERSION=1.6.0 +LIBEDATASERVER_MIN_VERSION=1.2.0 +LIBEDATASERVERUI2_MIN_VERSION=1.2.0 +LIBEDATASERVERUI3_MIN_VERSION=2.91.6 TELEPATHY_GLIB_MIN_VERSION=0.13.12 POLKIT_MIN_VERSION=0.100 @@ -125,12 +125,12 @@ PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 2.90.0], # Default to libedataserverui-3.0, but allow falling back to 1.2 PKG_CHECK_EXISTS(libedataserverui-3.0, [EDS_API=3.0 - LIBEDATASERVERUI_REQUIRED=$LIBEDATASERVERUI3_REQUIRED], + LIBEDATASERVERUI_MIN_VERSION=$LIBEDATASERVERUI3_MIN_VERSION], [EDS_API=1.2 - LIBEDATASERVERUI_REQUIRED=$LIBEDATASERVERUI2_REQUIRED]) -PKG_CHECK_MODULES(LIBECAL, libecal-1.2 >= $LIBECAL_REQUIRED libedataserver-1.2 >= $LIBEDATASERVER_REQUIRED libedataserverui-$EDS_API >= $LIBEDATASERVERUI_REQUIRED) -AC_SUBST(LIBECAL_CFLAGS) -AC_SUBST(LIBECAL_LIBS) + LIBEDATASERVERUI_MIN_VERSION=$LIBEDATASERVERUI2_MIN_VERSION]) +PKG_CHECK_MODULES(CALENDAR_SERVER, libecal-1.2 >= $LIBECAL_MIN_VERSION libedataserver-1.2 >= $LIBEDATASERVER_MIN_VERSION libedataserverui-$EDS_API >= $LIBEDATASERVERUI_MIN_VERSION gio-2.0) +AC_SUBST(CALENDAR_SERVER_CFLAGS) +AC_SUBST(CALENDAR_SERVER_LIBS) MUTTER_BIN_DIR=`$PKG_CONFIG --variable=exec_prefix mutter-plugins`/bin # FIXME: metacity-plugins.pc should point directly to its .gir file diff --git a/js/ui/calendar.js b/js/ui/calendar.js index 643a66a57..abedb2650 100644 --- a/js/ui/calendar.js +++ b/js/ui/calendar.js @@ -1,5 +1,6 @@ /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ +const DBus = imports.dbus; const Clutter = imports.gi.Clutter; const Gio = imports.gi.Gio; const Lang = imports.lang; @@ -158,13 +159,14 @@ function _getEventDayAbbreviation(dayNumber) { // Abstraction for an appointment/event in a calendar -function CalendarEvent(date, summary, allDay) { - this._init(date, summary, allDay); +function CalendarEvent(date, end, summary, allDay) { + this._init(date, end, summary, allDay); } CalendarEvent.prototype = { - _init: function(date, summary, allDay) { + _init: function(date, end, summary, allDay) { this.date = date; + this.end = end; this.summary = summary; this.allDay = allDay; } @@ -196,139 +198,136 @@ EmptyEventSource.prototype = { }; Signals.addSignalMethods(EmptyEventSource.prototype); -// Second, wrap native Evolution event source -function EvolutionEventSource() { - this._init(); -} - -EvolutionEventSource.prototype = { - _init: function() { - this._native = new Shell.EvolutionEventSource(); - this._native.connect('changed', Lang.bind(this, function() { - this.emit('changed'); - })); - }, - - 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.msec_begin), nativeEvent.summary, nativeEvent.all_day)); - } - return result; - }, - - hasEvents: function(day) { - let dayBegin = _getBeginningOfDay(day); - let dayEnd = _getEndOfDay(day); - - let events = this.getEvents(dayBegin, dayEnd); - - if (events.length == 0) - return false; - - return true; - } +const CalendarServerIface = { + name: 'org.gnome.Shell.CalendarServer', + methods: [{ name: 'GetEvents', + inSignature: 'xxb', + outSignature: 'a(sssbxxa{sv})' }], + signals: [{ name: 'Changed', + inSignature: '' }] }; -Signals.addSignalMethods(EvolutionEventSource.prototype); -// Finally, an implementation with fake events -function FakeEventSource() { +const CalendarServer = function () { this._init(); +}; + +CalendarServer.prototype = { + _init: function() { + DBus.session.proxifyObject(this, 'org.gnome.Shell.CalendarServer', '/org/gnome/Shell/CalendarServer'); + } +}; + +DBus.proxifyPrototype(CalendarServer.prototype, CalendarServerIface); + +// an implementation that reads data from a session bus service +function DBusEventSource(owner) { + this._init(owner); } -FakeEventSource.prototype = { - _init: function() { +function _datesEqual(a, b) { + if (a < b) + return false; + else if (a > b) + return false; + return true; +} - this._fakeEvents = []; +function _dateIntervalsOverlap(a0, a1, b0, b1) +{ + if (a1 <= b0) + return false; + else if (b1 <= a0) + return false; + else + return true; +} - // Generate fake events - // - let midnightToday = _getBeginningOfDay(new Date()); - let summary = ''; - // '10-oclock pow-wow' is an event occuring IN THE PAST every four days at 10am - for (let n = 0; n < 10; n++) { - let t = new Date(midnightToday.getTime() - n * 4 * 86400 * 1000); - t.setHours(10); - summary = '10-oclock pow-wow (n=' + n + ')'; - this._fakeEvents.push(new CalendarEvent(t, summary, false)); - } +DBusEventSource.prototype = { + _init: function(owner) { + this._resetCache(); - // '11-oclock thing' is an event occuring every three days at 11am - for (let n = 0; n < 10; n++) { - let t = new Date(midnightToday.getTime() + n * 3 * 86400 * 1000); - t.setHours(11); - summary = '11-oclock thing (n=' + n + ')'; - this._fakeEvents.push(new CalendarEvent(t, summary, false)); - } + this._dbusProxy = new CalendarServer(owner); + this._dbusProxy.connect('Changed', Lang.bind(this, this._onChanged)); - // 'Weekly Meeting' is an event occuring every seven days at 1:45pm (two days displaced) - for (let n = 0; n < 5; n++) { - let t = new Date(midnightToday.getTime() + (n * 7 + 2) * 86400 * 1000); - t.setHours(13); - t.setMinutes(45); - summary = 'Weekly Meeting (n=' + n + ')'; - this._fakeEvents.push(new CalendarEvent(t, summary, false)); - } - - // 'Fun All Day' is an all-day event occuring every fortnight (three days displayed) - for (let n = 0; n < 10; n++) { - let t = new Date(midnightToday.getTime() + (n * 14 + 3) * 86400 * 1000); - summary = 'Fun All Day (n=' + n + ')'; - this._fakeEvents.push(new CalendarEvent(t, summary, true)); - } - - // 'Get Married' is an event that actually reflects reality (Dec 4, 2010) :-) - this._fakeEvents.push(new CalendarEvent(new Date(2010, 11, 4, 16, 0), 'Get Married', false)); - - // ditto for 'NE Patriots vs NY Jets' - this._fakeEvents.push(new CalendarEvent(new Date(2010, 11, 6, 20, 30), 'NE Patriots vs NY Jets', false)); - - // An event for tomorrow @6:30pm that is added/removed every five - // seconds (to check that the ::changed signal works) - let transientEventDate = new Date(midnightToday.getTime() + 86400 * 1000); - transientEventDate.setHours(18); - transientEventDate.setMinutes(30); - transientEventDate.setSeconds(0); - Mainloop.timeout_add(5000, Lang.bind(this, this._updateTransientEvent)); - this._includeTransientEvent = false; - this._transientEvent = new CalendarEvent(transientEventDate, 'A Transient Event', false); - this._transientEventCounter = 1; + DBus.session.watch_name('org.gnome.Shell.CalendarServer', + false, // do not launch a name-owner if none exists + Lang.bind(this, this._onNameAppeared), + Lang.bind(this, this._onNameVanished)); }, - _updateTransientEvent: function() { - this._includeTransientEvent = !this._includeTransientEvent; - this._transientEventCounter = this._transientEventCounter + 1; - this._transientEvent.summary = 'A Transient Event (' + this._transientEventCounter + ')'; + _resetCache: function() { + this._events = []; + this._lastRequestBegin = null; + this._lastRequestEnd = null; + }, + + _onNameAppeared: function(owner) { + this._resetCache(); + this._loadEvents(true); + }, + + _onNameVanished: function(oldOwner) { + this._resetCache(); this.emit('changed'); - Mainloop.timeout_add(5000, Lang.bind(this, this._updateTransientEvent)); }, - requestRange: function(begin, end) { + _onChanged: function() { + this._loadEvents(false); + }, + + _onEventsReceived: function(appointments) { + let newEvents = []; + if (appointments != null) { + for (let n = 0; n < appointments.length; n++) { + let a = appointments[n]; + let date = new Date(a[4] * 1000); + let end = new Date(a[5] * 1000); + let summary = a[1]; + let allDay = a[3]; + let event = new CalendarEvent(date, end, summary, allDay); + newEvents.push(event); + } + newEvents.sort(function(event1, event2) { + return event1.date.getTime() - event2.date.getTime(); + }); + } + + this._events = newEvents; + this.emit('changed'); + }, + + _loadEvents: function(forceReload) { + if (this._curRequestBegin && this._curRequestEnd){ + let callFlags = 0; + if (forceReload) + callFlags |= DBus.CALL_FLAG_START; + this._dbusProxy.GetEventsRemote(this._curRequestBegin.getTime() / 1000, + this._curRequestEnd.getTime() / 1000, + forceReload, + Lang.bind(this, this._onEventsReceived), + callFlags); + } + }, + + requestRange: function(begin, end, forceReload) { + if (forceReload || !(_datesEqual(begin, this._lastRequestBegin) && _datesEqual(end, this._lastRequestEnd))) { + this._lastRequestBegin = begin; + this._lastRequestEnd = end; + this._curRequestBegin = begin; + this._curRequestEnd = end; + this._loadEvents(forceReload); + } }, getEvents: function(begin, end) { let result = []; - //log('begin:' + begin); - //log('end: ' + end); - for(let n = 0; n < this._fakeEvents.length; n++) { - let event = this._fakeEvents[n]; - if (event.date >= begin && event.date <= end) { + for(let n = 0; n < this._events.length; n++) { + let event = this._events[n]; + if (_dateIntervalsOverlap (event.date, event.end, begin, end)) { result.push(event); } - //log('when:' + event.date + ' summary:' + event.summary); } - if (this._includeTransientEvent && this._transientEvent.date >= begin && this._transientEvent.date <= end) - result.push(this._transientEvent); - result.sort(function(event1, event2) { - return event1.date.getTime() - event2.date.getTime(); - }); return result; }, @@ -343,9 +342,8 @@ FakeEventSource.prototype = { return true; } - }; -Signals.addSignalMethods(FakeEventSource.prototype); +Signals.addSignalMethods(DBusEventSource.prototype); // Calendar: // @eventSource: is an object implementing the EventSource API, e.g. the @@ -358,7 +356,10 @@ Calendar.prototype = { _init: function(eventSource) { this._eventSource = eventSource; - this._eventSource.connect('changed', Lang.bind(this, this._update)); + this._eventSource.connect('changed', Lang.bind(this, + function() { + this._update(false); + })); // FIXME: This is actually the fallback method for GTK+ for the week start; // GTK+ by preference uses nl_langinfo (NL_TIME_FIRST_WEEKDAY). We probably @@ -407,15 +408,17 @@ Calendar.prototype = { Lang.bind(this, this._onScroll)); this._buildHeader (); - this._update(); }, // Sets the calendar to show a specific date - setDate: function(date) { + setDate: function(date, forceReload) { if (!_sameDay(date, this._selectedDate)) { this._selectedDate = date; - this._update(); + this._update(forceReload); this.emit('selected-date-changed', new Date(this._selectedDate)); + } else { + if (forceReload) + this._update(forceReload); } }, @@ -510,7 +513,7 @@ Calendar.prototype = { } } - this.setDate(newDate); + this.setDate(newDate, false); }, _onNextMonthButtonClicked: function() { @@ -532,16 +535,16 @@ Calendar.prototype = { } } - this.setDate(newDate); + this.setDate(newDate, false); }, _onSettingsChange: function() { this._useWeekdate = this._settings.get_boolean(SHOW_WEEKDATE_KEY); this._buildHeader(); - this._update(); + this._update(false); }, - _update: function() { + _update: function(forceReload) { let now = new Date(); if (_sameYear(this._selectedDate, now)) @@ -570,7 +573,7 @@ Calendar.prototype = { let iterStr = iter.toUTCString(); button.connect('clicked', Lang.bind(this, function() { let newlySelectedDate = new Date(iterStr); - this.setDate(newlySelectedDate); + this.setDate(newlySelectedDate, false); })); let hasEvents = this._eventSource.hasEvents(iter); @@ -620,7 +623,7 @@ Calendar.prototype = { } // Signal to the event source that we are interested in events // only from this date range - this._eventSource.requestRange(beginDate, iter); + this._eventSource.requestRange(beginDate, iter, forceReload); } }; @@ -698,7 +701,7 @@ EventsList.prototype = { if (events.length == 0 && showNothingScheduled) { let now = new Date(); /* Translators: Text to show if there are no events */ - let nothingEvent = new CalendarEvent(now, _("Nothing Scheduled"), true); + let nothingEvent = new CalendarEvent(now, now, _("Nothing Scheduled"), true); let timeString = _formatEventTime(nothingEvent, clockFormat); this._addEvent(dayNameBox, timeBox, eventTitleBox, false, "", timeString, nothingEvent.summary); } diff --git a/js/ui/dateMenu.js b/js/ui/dateMenu.js index 1f193aa7c..6dee534b6 100644 --- a/js/ui/dateMenu.js +++ b/js/ui/dateMenu.js @@ -52,9 +52,7 @@ DateMenuButton.prototype = { let hbox; let vbox; - //this._eventSource = new Calendar.EmptyEventSource(); - //this._eventSource = new Calendar.FakeEventSource(); - this._eventSource = new Calendar.EvolutionEventSource(); + this._eventSource = new Calendar.DBusEventSource(); let menuAlignment = 0.25; if (St.Widget.get_default_direction() == St.TextDirection.RTL) @@ -117,7 +115,20 @@ DateMenuButton.prototype = { this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) { if (isOpen) { let now = new Date(); - this._calendar.setDate(now); + /* Passing true to setDate() forces events to be reloaded. We + * want this behavior, because + * + * o It will cause activation of the calendar server which is + * useful if it has crashed + * + * o It will cause the calendar server to reload events which + * is useful if dynamic updates are not supported or not + * properly working + * + * Since this only happens when the menu is opened, the cost + * isn't very big. + */ + this._calendar.setDate(now, true); // No need to update this._eventList as ::selected-date-changed // signal will fire } diff --git a/js/ui/main.js b/js/ui/main.js index 5c6b952d0..873980d8c 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -95,6 +95,10 @@ function start() { // back into sync ones. DBus.session.flush(); + // Load the calendar server. Note that we are careful about + // not loading any events until the user presses the clock + global.launch_calendar_server(); + Environment.init(); // Ensure ShellWindowTracker and ShellAppUsage are initialized; this will diff --git a/src/Makefile-calendar-client.am b/src/Makefile-calendar-client.am deleted file mode 100644 index cac046f57..000000000 --- a/src/Makefile-calendar-client.am +++ /dev/null @@ -1,22 +0,0 @@ - -noinst_LTLIBRARIES += libcalendar-client.la - -libcalendar_client_la_SOURCES = \ - calendar-client/calendar-client.h calendar-client/calendar-client.c \ - calendar-client/calendar-debug.h \ - calendar-client/calendar-sources.c calendar-client/calendar-sources.h \ - $(NULL) - -libcalendar_client_la_CFLAGS = \ - -I$(top_srcdir)/src \ - -DPREFIX=\""$(prefix)"\" \ - -DLIBDIR=\""$(libdir)"\" \ - -DDATADIR=\""$(datadir)"\" \ - -DG_DISABLE_DEPRECATED \ - -DG_LOG_DOMAIN=\"CalendarClient\" \ - $(LIBECAL_CFLAGS) \ - $(NULL) - -libcalendar_client_la_LIBADD = $(LIBECAL_LIBS) - -EXTRA_DIST += calendar-client/README diff --git a/src/Makefile-calendar-server.am b/src/Makefile-calendar-server.am new file mode 100644 index 000000000..e617668ee --- /dev/null +++ b/src/Makefile-calendar-server.am @@ -0,0 +1,34 @@ + +servicedir = $(datadir)/dbus-1/services +service_in_files = calendar-server/org.gnome.Shell.CalendarServer.service.in +service_DATA = $(service_in_files:.service.in=.service) + +$(service_DATA): $(service_in_files) Makefile + @sed -e "s|\@libexecdir\@|$(libexecdir)|" $< > $@.tmp && mv $@.tmp $@ + +libexec_PROGRAMS += gnome-shell-calendar-server + +gnome_shell_calendar_server_SOURCES = \ + calendar-server/calendar-debug.h \ + calendar-server/calendar-sources.c calendar-server/calendar-sources.h \ + calendar-server/gnome-shell-calendar-server.c \ + $(NULL) + +gnome_shell_calendar_server_CFLAGS = \ + -I$(top_srcdir)/src \ + -DPREFIX=\""$(prefix)"\" \ + -DLIBDIR=\""$(libdir)"\" \ + -DDATADIR=\""$(datadir)"\" \ + -DG_DISABLE_DEPRECATED \ + -DG_LOG_DOMAIN=\"ShellCalendarServer\" \ + $(CALENDAR_SERVER_CFLAGS) \ + $(NULL) + +gnome_shell_calendar_server_LDFLAGS = \ + $(CALENDAR_SERVER_LIBS) \ + $(NULL) + +EXTRA_DIST += \ + calendar-server/README \ + calendar-server/org.gnome.Shell.CalendarServer.service.in \ + $(NULL) diff --git a/src/Makefile.am b/src/Makefile.am index ea12a2b90..71cb01811 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -27,13 +27,14 @@ include Makefile-gdmuser.am include Makefile-st.am include Makefile-tray.am include Makefile-gvc.am -include Makefile-calendar-client.am +include Makefile-calendar-server.am gnome_shell_cflags = \ $(MUTTER_PLUGIN_CFLAGS) \ $(LIBGNOMEUI_CFLAGS) \ -I$(srcdir)/tray \ -DLOCALEDIR=\"$(datadir)/locale\" \ + -DGNOME_SHELL_LIBEXECDIR=\"$(libexecdir)\" \ -DGNOME_SHELL_DATADIR=\"$(pkgdatadir)\" \ -DGNOME_SHELL_PKGLIBDIR=\"$(pkglibdir)\" \ -DJSDIR=\"$(pkgdatadir)/js\" @@ -88,8 +89,6 @@ libgnome_shell_la_SOURCES = \ shell-arrow.c \ shell-doc-system.c \ shell-embedded-window.c \ - shell-evolution-event-source.h \ - shell-evolution-event-source.c \ shell-generic-container.c \ shell-gtk-embed.c \ shell-global.c \ @@ -216,10 +215,9 @@ libgnome_shell_la_LIBADD = \ libgdmuser-1.0.la \ libtray.la \ libgvc.la \ - libcalendar-client.la \ $(NULL) -libgnome_shell_la_CPPFLAGS = $(gnome_shell_cflags) +libgnome_shell_la_CPPFLAGS = $(gnome_shell_cflags) $(LIBECAL_CFLAGS) typelibdir = $(pkglibdir) typelib_DATA = Shell-0.1.typelib St-1.0.typelib Gdm-1.0.typelib Gvc-1.0.typelib diff --git a/src/calendar-client/calendar-client.c b/src/calendar-client/calendar-client.c deleted file mode 100644 index bbdb47327..000000000 --- a/src/calendar-client/calendar-client.c +++ /dev/null @@ -1,2169 +0,0 @@ -/* - * 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 - * William Jon McCann - * Martin Grimme - * Christian Kellner - */ - -#include - -#include "calendar-client.h" - -#include -#include -#define HANDLE_LIBICAL_MEMORY -#include -#include -#include - -#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 deleted file mode 100644 index 3ae3b2fc8..000000000 --- a/src/calendar-client/calendar-client.h +++ /dev/null @@ -1,149 +0,0 @@ -/* - * 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 - * William Jon McCann - * Martin Grimme - * Christian Kellner - */ - -#ifndef __CALENDAR_CLIENT_H__ -#define __CALENDAR_CLIENT_H__ - -#include - -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/README b/src/calendar-server/README similarity index 100% rename from src/calendar-client/README rename to src/calendar-server/README diff --git a/src/calendar-client/calendar-debug.h b/src/calendar-server/calendar-debug.h similarity index 100% rename from src/calendar-client/calendar-debug.h rename to src/calendar-server/calendar-debug.h diff --git a/src/calendar-client/calendar-sources.c b/src/calendar-server/calendar-sources.c similarity index 100% rename from src/calendar-client/calendar-sources.c rename to src/calendar-server/calendar-sources.c diff --git a/src/calendar-client/calendar-sources.h b/src/calendar-server/calendar-sources.h similarity index 100% rename from src/calendar-client/calendar-sources.h rename to src/calendar-server/calendar-sources.h diff --git a/src/calendar-server/gnome-shell-calendar-server.c b/src/calendar-server/gnome-shell-calendar-server.c new file mode 100644 index 000000000..4d4d3a50c --- /dev/null +++ b/src/calendar-server/gnome-shell-calendar-server.c @@ -0,0 +1,1109 @@ +/* + * Copyright (C) 2011 Red Hat, 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. + * + * Author: David Zeuthen + * + * Based on code from gnome-panel's clock-applet, file calendar-client.c, with Authors: + * + * Mark McLoughlin + * William Jon McCann + * Martin Grimme + * Christian Kellner + * + */ + +#include "config.h" + +#include +#include +#include + +#include + +#define HANDLE_LIBICAL_MEMORY +#include +#include +#include +#include + +#define CALENDAR_CONFIG_PREFIX "/apps/evolution/calendar" +#define CALENDAR_CONFIG_TIMEZONE CALENDAR_CONFIG_PREFIX "/display/timezone" + +#include "calendar-sources.h" + +/* Set the environment variable CALENDAR_SERVER_DEBUG to show debug */ +static void print_debug (const gchar *str, ...); + +#define BUS_NAME "org.gnome.Shell.CalendarServer" + +static const gchar introspection_xml[] = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; +static GDBusNodeInfo *introspection_data = NULL; + +struct _App; +typedef struct _App App; + +static GMainLoop *loop = NULL; +static gboolean opt_replace = FALSE; +static GOptionEntry opt_entries[] = { + {"replace", 0, 0, G_OPTION_ARG_NONE, &opt_replace, "Replace existing daemon", NULL}, + {NULL } +}; +static App *_global_app = NULL; + +/* ---------------------------------------------------------------------------------------------------- */ + +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; + +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 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 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_free (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, + ECal *cal, + icaltimezone *default_zone) +{ + appointment->uid = get_ical_uid (ical); + appointment->rid = get_ical_rid (ical); + appointment->uri = get_source_uri (cal); + appointment->summary = get_ical_summary (ical); + appointment->description = get_ical_description (ical); + appointment->color_string = get_source_color (cal); + 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_occurrences (CalendarAppointment *appointment, + icalcomponent *ical, + ECal *cal, + 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, + cal, + default_zone); + + g_object_unref (ecal); + + appointment->occurrences = g_slist_reverse (appointment->occurrences); +} + +static CalendarAppointment * +calendar_appointment_new (icalcomponent *ical, + ECal *cal, + icaltimezone *default_zone) +{ + CalendarAppointment *appointment; + + appointment = g_new0 (CalendarAppointment, 1); + + calendar_appointment_init (appointment, + ical, + cal, + default_zone); + return appointment; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +struct _App +{ + GDBusConnection *connection; + + time_t since; + time_t until; + + icaltimezone *zone; + + CalendarSources *sources; + gulong sources_signal_id; + + guint zone_listener; + GConfClient *gconf_client; + + /* hash from uid to CalendarAppointment objects */ + GHashTable *appointments; + + gchar *timezone_location; + + guint changed_timeout_id; + + gboolean cache_invalid; + + GList *live_views; +}; + +static void +app_update_timezone (App *app) +{ + gchar *location; + + location = e_cal_system_timezone_get_location (); + if (g_strcmp0 (location, app->timezone_location) != 0) + { + if (location == NULL) + app->zone = icaltimezone_get_utc_timezone (); + else + app->zone = icaltimezone_get_builtin_timezone (location); + g_free (app->timezone_location); + app->timezone_location = location; + print_debug ("Using timezone %s", app->timezone_location); + } +} + +static gboolean +on_app_schedule_changed_cb (gpointer user_data) +{ + App *app = user_data; + print_debug ("Emitting changed"); + g_dbus_connection_emit_signal (app->connection, + NULL, /* destination_bus_name */ + "/org/gnome/Shell/CalendarServer", + "org.gnome.Shell.CalendarServer", + "Changed", + NULL, /* no params */ + NULL); + app->changed_timeout_id = 0; + return FALSE; +} + +static void +app_schedule_changed (App *app) +{ + print_debug ("Scheduling changed"); + if (app->changed_timeout_id == 0) + { + app->changed_timeout_id = g_timeout_add (2000, + on_app_schedule_changed_cb, + app); + } +} + +static void +invalidate_cache (App *app) +{ + app->cache_invalid = TRUE; +} + +static void +on_objects_added (ECalView *view, + GList *objects, + gpointer user_data) +{ + App *app = user_data; + GList *l; + + print_debug ("%s for calendar", G_STRFUNC); + + for (l = objects; l != NULL; l = l->next) + { + icalcomponent *ical = l->data; + const char *uid; + + uid = icalcomponent_get_uid (ical); + + if (g_hash_table_lookup (app->appointments, uid) == NULL) + { + /* new appointment we don't know about => changed signal */ + invalidate_cache (app); + app_schedule_changed (app); + } + } +} + +static void +on_objects_modified (ECalView *view, + GList *objects, + gpointer user_data) +{ + App *app = user_data; + print_debug ("%s for calendar", G_STRFUNC); + invalidate_cache (app); + app_schedule_changed (app); +} + +static void +on_objects_removed (ECalView *view, + GList *uids, + gpointer user_data) +{ + App *app = user_data; + print_debug ("%s for calendar", G_STRFUNC); + invalidate_cache (app); + app_schedule_changed (app); +} + +static void +app_load_events (App *app) +{ + GSList *sources; + GSList *l; + GList *ll; + gchar *since_iso8601; + gchar *until_iso8601; + + /* out with the old */ + g_hash_table_remove_all (app->appointments); + /* nuke existing views */ + for (ll = app->live_views; ll != NULL; ll = ll->next) + { + ECalView *view = E_CAL_VIEW (ll->data); + g_signal_handlers_disconnect_by_func (view, on_objects_added, app); + g_signal_handlers_disconnect_by_func (view, on_objects_modified, app); + g_signal_handlers_disconnect_by_func (view, on_objects_removed, app); + e_cal_view_stop (view); + g_object_unref (view); + } + g_list_free (app->live_views); + app->live_views = NULL; + + /* timezone could have changed */ + app_update_timezone (app); + + since_iso8601 = isodate_from_time_t (app->since); + until_iso8601 = isodate_from_time_t (app->until); + + print_debug ("Loading events since %s until %s", + since_iso8601, + until_iso8601); + + sources = calendar_sources_get_appointment_sources (app->sources); + for (l = sources; l != NULL; l = l->next) + { + ECal *cal = E_CAL (l->data); + GError *error; + gchar *query; + GList *objects; + GList *j; + ECalView *view; + + error = NULL; + if (!e_cal_set_default_timezone (cal, app->zone, &error)) + { + g_printerr ("Error setting timezone on calendar: %s\n", error->message); + g_error_free (error); + continue; + } + + error = NULL; + if (!e_cal_open (cal, TRUE, &error)) + { + g_printerr ("Error opening calendar: %s\n", error->message); + g_error_free (error); + continue; + } + + query = g_strdup_printf ("occur-in-time-range? (make-time \"%s\") " + "(make-time \"%s\")", + since_iso8601, + until_iso8601); + error = NULL; + objects = NULL; + if (!e_cal_get_object_list (cal, + query, + &objects, + &error)) + { + g_printerr ("Error querying calendar: %s\n", error->message); + g_error_free (error); + g_free (query); + continue; + } + + for (j = objects; j != NULL; j = j->next) + { + icalcomponent *ical = j->data; + CalendarAppointment *appointment; + + appointment = calendar_appointment_new (ical, cal, app->zone); + if (appointment == NULL) + continue; + + calendar_appointment_generate_occurrences (appointment, + ical, + cal, + app->since, + app->until, + app->zone); + g_hash_table_insert (app->appointments, g_strdup (appointment->uid), appointment); + } + + e_cal_free_object_list (objects); + + error = NULL; + if (!e_cal_get_query (cal, + query, + &view, + &error)) + { + g_printerr ("Error setting up live-query on calendar: %s\n", error->message); + g_error_free (error); + } + else + { + g_signal_connect (view, + "objects-added", + G_CALLBACK (on_objects_added), + app); + g_signal_connect (view, + "objects-modified", + G_CALLBACK (on_objects_modified), + app); + g_signal_connect (view, + "objects-removed", + G_CALLBACK (on_objects_removed), + app); + e_cal_view_start (view); + app->live_views = g_list_prepend (app->live_views, view); + } + + g_free (query); + } + g_free (since_iso8601); + g_free (until_iso8601); + app->cache_invalid = FALSE; +} + +static void +on_appointment_sources_changed (CalendarSources *sources, + gpointer user_data) +{ + App *app = user_data; + + print_debug ("Sources changed\n"); + app_load_events (app); +} + +static App * +app_new (GDBusConnection *connection) +{ + App *app; + + app = g_new0 (App, 1); + app->connection = g_object_ref (connection); + app->sources = calendar_sources_get (); + app->sources_signal_id = g_signal_connect (app->sources, + "appointment-sources-changed", + G_CALLBACK (on_appointment_sources_changed), + app); + + app->appointments = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) calendar_appointment_free); + + app_update_timezone (app); + + return app; +} + +static void +app_free (App *app) +{ + GList *ll; + for (ll = app->live_views; ll != NULL; ll = ll->next) + { + ECalView *view = E_CAL_VIEW (ll->data); + g_signal_handlers_disconnect_by_func (view, on_objects_added, app); + g_signal_handlers_disconnect_by_func (view, on_objects_modified, app); + g_signal_handlers_disconnect_by_func (view, on_objects_removed, app); + e_cal_view_stop (view); + g_object_unref (view); + } + g_list_free (app->live_views); + + g_free (app->timezone_location); + + g_hash_table_unref (app->appointments); + + g_object_unref (app->connection); + g_signal_handler_disconnect (app->sources, + app->sources_signal_id); + g_object_unref (app->sources); + + if (app->changed_timeout_id != 0) + g_source_remove (app->changed_timeout_id); + + g_free (app); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +handle_method_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + App *app = user_data; + + if (g_strcmp0 (method_name, "GetEvents") == 0) + { + GVariantBuilder builder; + GHashTableIter hash_iter; + CalendarAppointment *a; + gint64 since; + gint64 until; + gboolean force_reload; + gboolean window_changed; + + g_variant_get (parameters, + "(xxb)", + &since, + &until, + &force_reload); + + if (until < since) + { + g_dbus_method_invocation_return_dbus_error (invocation, + "org.gnome.Shell.CalendarServer.Error.Failed", + "until cannot be before since"); + goto out; + } + + print_debug ("Handling GetEvents (since=%" G_GINT64_FORMAT ", until=%" G_GINT64_FORMAT ", force_reload=%s)", + since, + until, + force_reload ? "true" : "false"); + + window_changed = FALSE; + if (!(app->until == until && app->since == since)) + { + GVariantBuilder *builder; + GVariantBuilder *invalidated_builder; + + app->until = until; + app->since = since; + window_changed = TRUE; + + builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); + invalidated_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); + g_variant_builder_add (builder, "{sv}", + "Until", g_variant_new_int64 (app->until)); + g_variant_builder_add (builder, "{sv}", + "Since", g_variant_new_int64 (app->since)); + g_dbus_connection_emit_signal (app->connection, + NULL, /* destination_bus_name */ + "/org/gnome/Shell/CalendarServer", + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + g_variant_new ("(sa{sv}as)", + "org.gnome.Shell.CalendarServer", + builder, + invalidated_builder), + NULL); /* GError** */ + } + + /* reload events if necessary */ + if (window_changed || force_reload || app->cache_invalid) + { + app_load_events (app); + } + + /* The a{sv} is used as an escape hatch in case we want to provide more + * information in the future without breaking ABI + */ + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sssbxxa{sv})")); + g_hash_table_iter_init (&hash_iter, app->appointments); + while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &a)) + { + GVariantBuilder extras_builder; + GSList *l; + + for (l = a->occurrences; l; l = l->next) + { + CalendarOccurrence *o = l->data; + time_t start_time = o->start_time; + time_t end_time = o->end_time; + + if ((start_time >= app->since && + start_time < app->until) || + (start_time <= app->since && + (end_time - 1) > app->since)) + { + g_variant_builder_init (&extras_builder, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (&builder, + "(sssbxxa{sv})", + a->uid, + a->summary != NULL ? a->summary : "", + a->description != NULL ? a->description : "", + (gboolean) a->is_all_day, + (gint64) start_time, + (gint64) end_time, + extras_builder); + } + } + } + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(a(sssbxxa{sv}))", &builder)); + } + else + { + g_assert_not_reached (); + } + + out: + ; +} + +static GVariant * +handle_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + App *app = user_data; + GVariant *ret; + + ret = NULL; + if (g_strcmp0 (property_name, "Since") == 0) + { + ret = g_variant_new_int64 (app->since); + } + else if (g_strcmp0 (property_name, "Until") == 0) + { + ret = g_variant_new_int64 (app->until); + } + else + { + g_assert_not_reached (); + } + return ret; +} + +static const GDBusInterfaceVTable interface_vtable = +{ + handle_method_call, + handle_get_property, + NULL /* handle_set_property */ +}; + +static void +on_bus_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + GError *error; + guint registration_id; + + _global_app = app_new (connection); + + error = NULL; + registration_id = g_dbus_connection_register_object (connection, + "/org/gnome/Shell/CalendarServer", + introspection_data->interfaces[0], + &interface_vtable, + _global_app, + NULL, /* user_data_free_func */ + &error); + if (registration_id == 0) + { + g_printerr ("Error exporting object: %s (%s %d)", + error->message, + g_quark_to_string (error->domain), + error->code); + g_error_free (error); + _exit (1); + } + + print_debug ("Connected to the session bus"); + +} + +static void +on_name_lost (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_print ("gnome-shell-calendar-server[%d]: Lost (or failed to acquire) the name " BUS_NAME " - exiting\n", + (gint) getpid ()); + g_main_loop_quit (loop); +} + +static void +on_name_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + print_debug ("Acquired the name " BUS_NAME); +} + +static gboolean +stdin_channel_io_func (GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + if (condition & G_IO_HUP) + { + g_print ("gnome-shell-calendar-server[%d]: Got HUP on stdin - exiting\n", + (gint) getpid ()); + g_main_loop_quit (loop); + } + else + { + g_warning ("Unhandled condition %d on GIOChannel for stdin", condition); + } + return FALSE; /* remove source */ +} + +int +main (int argc, + char **argv) +{ + GError *error; + GOptionContext *opt_context; + gint ret; + guint name_owner_id; + GIOChannel *stdin_channel; + + ret = 1; + loop = NULL; + opt_context = NULL; + name_owner_id = 0; + + g_type_init (); + + introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL); + g_assert (introspection_data != NULL); + + opt_context = g_option_context_new ("gnome-shell calendar server"); + g_option_context_add_main_entries (opt_context, opt_entries, NULL); + error = NULL; + if (!g_option_context_parse (opt_context, &argc, &argv, &error)) + { + g_printerr ("Error parsing options: %s", error->message); + g_error_free (error); + goto out; + } + + stdin_channel = g_io_channel_unix_new (STDIN_FILENO); + g_io_add_watch (stdin_channel, + G_IO_HUP, + stdin_channel_io_func, + NULL); + + loop = g_main_loop_new (NULL, FALSE); + + name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, + BUS_NAME, + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | + (opt_replace ? G_BUS_NAME_OWNER_FLAGS_REPLACE : 0), + on_bus_acquired, + on_name_acquired, + on_name_lost, + NULL, + NULL); + + g_main_loop_run (loop); + + ret = 0; + + out: + if (stdin_channel != NULL) + g_io_channel_unref (stdin_channel); + if (_global_app != NULL) + app_free (_global_app); + if (name_owner_id != 0) + g_bus_unown_name (name_owner_id); + if (loop != NULL) + g_main_loop_unref (loop); + if (opt_context != NULL) + g_option_context_free (opt_context); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +print_debug (const gchar *format, ...) +{ + gchar *s; + va_list ap; + gchar timebuf[64]; + GTimeVal now; + time_t now_t; + struct tm broken_down; + static volatile gsize once_init_value = 0; + static gboolean show_debug = FALSE; + static guint pid = 0; + + if (g_once_init_enter (&once_init_value)) + { + show_debug = (g_getenv ("CALENDAR_SERVER_DEBUG") != NULL); + pid = getpid (); + g_once_init_leave (&once_init_value, 1); + } + + if (!show_debug) + goto out; + + g_get_current_time (&now); + now_t = now.tv_sec; + localtime_r (&now_t, &broken_down); + strftime (timebuf, sizeof timebuf, "%H:%M:%S", &broken_down); + + va_start (ap, format); + s = g_strdup_vprintf (format, ap); + va_end (ap); + + g_print ("gnome-shell-calendar-server[%d]: %s.%03d: %s\n", pid, timebuf, (gint) (now.tv_usec / 1000), s); + g_free (s); + out: + ; +} diff --git a/src/calendar-server/org.gnome.Shell.CalendarServer.service.in b/src/calendar-server/org.gnome.Shell.CalendarServer.service.in new file mode 100644 index 000000000..5addce6d0 --- /dev/null +++ b/src/calendar-server/org.gnome.Shell.CalendarServer.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=org.gnome.Shell.CalendarServer +Exec=@libexecdir@/gnome-shell-calendar-server diff --git a/src/gnome-shell.in b/src/gnome-shell.in index dd5e9674c..e748ac65f 100755 --- a/src/gnome-shell.in +++ b/src/gnome-shell.in @@ -197,7 +197,8 @@ def start_shell(perf_output=None): if running_from_source_tree: if os.environ.has_key('GI_TYPELIB_PATH'): typelib_dir = typelib_dir + ":" + os.environ.get('GI_TYPELIB_PATH') - env.update({'GNOME_SHELL_DATADIR' : data_dir, + env.update({'GNOME_SHELL_BINDIR' : bin_dir, + 'GNOME_SHELL_DATADIR' : data_dir, 'GI_TYPELIB_PATH' : typelib_dir, 'GSETTINGS_SCHEMA_DIR' : data_dir }) else: diff --git a/src/shell-evolution-event-source.c b/src/shell-evolution-event-source.c deleted file mode 100644 index 2982ddc4d..000000000 --- a/src/shell-evolution-event-source.c +++ /dev/null @@ -1,265 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ - -#include "config.h" - -#include "calendar-client/calendar-client.h" -#include "shell-evolution-event-source.h" - - -struct _ShellEvolutionEventSourceClass -{ - GObjectClass parent_class; -}; - -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 */ -enum -{ - CHANGED_SIGNAL, - LAST_SIGNAL -}; - -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), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); - -} - -ShellEvolutionEventSource * -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_utc, *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_utc = g_date_time_new_from_unix_utc ((msec_begin + msec_end) / 2 / 1000); - /* CalendarClient uses localtime rather than UTC */ - middle = g_date_time_to_local (middle_utc); - g_date_time_unref (middle_utc); - 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. - * @msec_begin: Start date (milli-seconds since Epoch). - * @msec_end: End date (milli-seconds since Epoch). - * - * 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 msec_begin, - gint64 msec_end) -{ - GList *result; - GDateTime *cur_date; - GDateTime *begin_date_utc, *begin_date; - GDateTime *end_date_utc, *end_date; - - g_return_val_if_fail (msec_begin <= msec_end, NULL); - - result = NULL; - - begin_date_utc = g_date_time_new_from_unix_utc (msec_begin / 1000); - end_date_utc = g_date_time_new_from_unix_utc (msec_end / 1000); - - /* CalendarClient uses localtime rather than UTC */ - begin_date = g_date_time_to_local (begin_date_utc); - end_date = g_date_time_to_local (end_date_utc); - g_date_time_unref (begin_date_utc); - g_date_time_unref (end_date_utc); - - cur_date = g_date_time_ref (begin_date); - do - { - 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\n", 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; - gint64 start_time; - - if (appointment->is_all_day) - { - start_time = g_date_time_to_unix (cur_date) * G_GINT64_CONSTANT (1000); - } - else - { - start_time = appointment->start_time * G_GINT64_CONSTANT (1000); - } - event = shell_evolution_event_new (appointment->summary, - appointment->is_all_day, - start_time); - 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; -} - -G_DEFINE_BOXED_TYPE (ShellEvolutionEvent, - shell_evolution_event, - shell_evolution_event_copy, - shell_evolution_event_free); - -void -shell_evolution_event_free (ShellEvolutionEvent *event) -{ - g_free (event->summary); - g_free (event); -} - -ShellEvolutionEvent * -shell_evolution_event_copy (ShellEvolutionEvent *event) -{ - ShellEvolutionEvent *copy; - copy = g_memdup (event, sizeof (ShellEvolutionEvent)); - copy->summary = g_strdup (event->summary); - return copy; -} - -ShellEvolutionEvent * -shell_evolution_event_new (const gchar *summary, - gboolean all_day, - gint64 msec_begin) -{ - ShellEvolutionEvent *event; - event = g_new0 (ShellEvolutionEvent, 1); - event->summary = g_strdup (summary); - event->all_day = all_day; - event->msec_begin = msec_begin; - return event; -} diff --git a/src/shell-evolution-event-source.h b/src/shell-evolution-event-source.h deleted file mode 100644 index 4866b4575..000000000 --- a/src/shell-evolution-event-source.h +++ /dev/null @@ -1,45 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -#ifndef __SHELL_EVOLUTION_EVENT_SOURCE_H__ -#define __SHELL_EVOLUTION_EVENT_SOURCE_H__ - -#include - -G_BEGIN_DECLS - -typedef struct _ShellEvolutionEvent ShellEvolutionEvent; - -struct _ShellEvolutionEvent -{ - gchar *summary; - gboolean all_day; - 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 msec_begin); -ShellEvolutionEvent *shell_evolution_event_copy (ShellEvolutionEvent *event); -void shell_evolution_event_free (ShellEvolutionEvent *event); - -typedef struct _ShellEvolutionEventSource ShellEvolutionEventSource; -typedef struct _ShellEvolutionEventSourceClass ShellEvolutionEventSourceClass; - -#define SHELL_TYPE_EVOLUTION_EVENT_SOURCE (shell_evolution_event_source_get_type ()) -#define SHELL_EVOLUTION_EVENT_SOURCE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_EVOLUTION_EVENT_SOURCE, ShellEvolutionEventSource)) -#define SHELL_EVOLUTION_EVENT_SOURCE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_EVOLUTION_EVENT_SOURCE, ShellEvolutionEventSourceClass)) -#define SHELL_IS_EVOLUTION_EVENT_SOURCE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SHELL_TYPE_EVOLUTION_EVENT_SOURCE)) -#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); -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__ */ diff --git a/src/shell-global.c b/src/shell-global.c index f42237e63..4f08bcaab 100644 --- a/src/shell-global.c +++ b/src/shell-global.c @@ -1984,3 +1984,57 @@ shell_get_tp_contacts (TpConnection *self, shell_global_get_tp_contacts_cb, callback, NULL, NULL); } + +/** + * shell_global_launch_calendar_server: + * @global: The #ShellGlobal. + * + * Launch the gnome-shell-calendar-server helper. + */ +void +shell_global_launch_calendar_server (ShellGlobal *global) +{ + const gchar *bin_dir; + gchar *calendar_server_exe; + GError *error; + gchar *argv[2]; + gint child_standard_input; + + /* launch calendar-server */ + bin_dir = g_getenv ("GNOME_SHELL_BINDIR"); + if (bin_dir != NULL) + calendar_server_exe = g_strdup_printf ("%s/gnome-shell-calendar-server", bin_dir); + else + calendar_server_exe = g_strdup_printf (GNOME_SHELL_LIBEXECDIR "/gnome-shell-calendar-server"); + + argv[0] = calendar_server_exe; + argv[1] = NULL; + error = NULL; + if (!g_spawn_async_with_pipes (NULL, /* working_directory */ + argv, + NULL, /* envp */ + 0, /* GSpawnFlags */ + NULL, /* child_setup */ + NULL, /* user_data */ + NULL, /* GPid *child_pid */ + &child_standard_input, + NULL, /* gint *stdout */ + NULL, /* gint *stderr */ + &error)) + { + g_warning ("Error launching `%s': %s (%s %d)", + calendar_server_exe, + error->message, + g_quark_to_string (error->domain), + error->code); + g_error_free (error); + } + /* Note that gnome-shell-calendar-server exits whenever its stdin + * file descriptor is HUP'ed. This means that whenever the the shell + * process exits or is being replaced, the calendar server is also + * exits...and if the shell is being replaced, a new copy of the + * calendar server is launched... + */ + + g_free (calendar_server_exe); +} diff --git a/src/shell-global.h b/src/shell-global.h index 0adb32c25..66b912735 100644 --- a/src/shell-global.h +++ b/src/shell-global.h @@ -156,6 +156,8 @@ void shell_get_tp_contacts (TpConnection *self, const TpContactFeature *features, ShellGetTpContactCb callback); +void shell_global_launch_calendar_server (ShellGlobal *global); + G_END_DECLS #endif /* __SHELL_GLOBAL_H__ */