From e9e30138bd617a410a60b45ac0a5576094ed63d4 Mon Sep 17 00:00:00 2001 From: David Zeuthen Date: Fri, 25 Feb 2011 17:42:25 -0500 Subject: [PATCH] Move calendar handling out-of-process Unfortunately the evolution-data-server client-side libraries seem to block the calling thread. This is a major problem as we must never ever block the main thread (doing so causes animations to flicker etc.). In the worst case, this problem causes login to hang (without falling back to fall-back mode) and in the best case it slows down login until a network connection is acquired. Additionally, in order to sanely use these evolution-data-server libraries, GConf has to be involved and GConf is not thread-safe. So it's not really feasible just moving the code to a separate thread. Therefore, move all calendar IO out of process and use a simple (and private) D-Bus interface for the shell to communicate with the out-of-process helper. For simplification, remove existing in-process code since internal interfaces have been slightly revised. This means that the shell is no longer using any native code for drawing the calendar dropdown. https://bugzilla.gnome.org/show_bug.cgi?id=641396 Signed-off-by: David Zeuthen --- configure.ac | 18 +- js/ui/calendar.js | 257 +- js/ui/dateMenu.js | 19 +- js/ui/main.js | 4 + src/Makefile-calendar-client.am | 22 - src/Makefile-calendar-server.am | 34 + src/Makefile.am | 8 +- src/calendar-client/calendar-client.c | 2169 ----------------- src/calendar-client/calendar-client.h | 149 -- .../README | 0 .../calendar-debug.h | 0 .../calendar-sources.c | 0 .../calendar-sources.h | 0 .../gnome-shell-calendar-server.c | 1109 +++++++++ .../org.gnome.Shell.CalendarServer.service.in | 3 + src/gnome-shell.in | 3 +- src/shell-evolution-event-source.c | 265 -- src/shell-evolution-event-source.h | 45 - src/shell-global.c | 54 + src/shell-global.h | 2 + 20 files changed, 1365 insertions(+), 2796 deletions(-) delete mode 100644 src/Makefile-calendar-client.am create mode 100644 src/Makefile-calendar-server.am delete mode 100644 src/calendar-client/calendar-client.c delete mode 100644 src/calendar-client/calendar-client.h rename src/{calendar-client => calendar-server}/README (100%) rename src/{calendar-client => calendar-server}/calendar-debug.h (100%) rename src/{calendar-client => calendar-server}/calendar-sources.c (100%) rename src/{calendar-client => calendar-server}/calendar-sources.h (100%) create mode 100644 src/calendar-server/gnome-shell-calendar-server.c create mode 100644 src/calendar-server/org.gnome.Shell.CalendarServer.service.in delete mode 100644 src/shell-evolution-event-source.c delete mode 100644 src/shell-evolution-event-source.h 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__ */